feat(portal): design foundation — Tailwind v3, tokens, self-hosted fonts
- Downgrade Tailwind v4 -> v3 (user decision): postcss.config.js, tailwind.config.js mapping framework-neutral CSS-var tokens onto utilities. - portal-tokens.css: light enterprise palette, single orange accent, IBM Plex type scale, radius/shadow/motion/focus (design handoff §6). AA-safe accent text/fill tokens (accent-active/-press/-text) — #f97316 alone fails AA. - Self-hosted IBM Plex Sans+Mono via @fontsource, Vite-bundled (R14, no CDN). - app.css: v3 layers, base type, uniform :focus-visible, reduced-motion. - layouts/portal.blade.php base layout; welcome page retokenised (guarded login CTA, DE/EN via lang/common) — no v4-only classes. - Reviewed with Codex (R15): 5 rounds, all findings fixed, final pass clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>feat/portal-design
parent
2ae7595aff
commit
53c2a12d6d
|
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
'tagline' => 'Kontrollzentrum für Ihre Cloud.',
|
||||
'sign_in' => 'Anmelden',
|
||||
'save' => 'Speichern',
|
||||
'cancel' => 'Abbrechen',
|
||||
'online' => 'Online',
|
||||
'offline' => 'Offline',
|
||||
'loading' => 'Wird geladen …',
|
||||
];
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
'tagline' => 'Control center for your cloud.',
|
||||
'sign_in' => 'Sign in',
|
||||
'save' => 'Save',
|
||||
'cancel' => 'Cancel',
|
||||
'online' => 'Online',
|
||||
'offline' => 'Offline',
|
||||
'loading' => 'Loading …',
|
||||
];
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -7,13 +7,16 @@
|
|||
"dev": "vite"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/vite": "^4.0.0",
|
||||
"autoprefixer": "^10.5.4",
|
||||
"concurrently": "^9.0.1",
|
||||
"laravel-vite-plugin": "^3.1",
|
||||
"tailwindcss": "^4.0.0",
|
||||
"postcss": "^8.5.23",
|
||||
"tailwindcss": "^3.4.19",
|
||||
"vite": "^8.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource/ibm-plex-mono": "^5.3.0",
|
||||
"@fontsource/ibm-plex-sans": "^5.3.0",
|
||||
"laravel-echo": "^2.4.0",
|
||||
"pusher-js": "^8.6.0"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
|
|
@ -1,9 +1,52 @@
|
|||
@import 'tailwindcss';
|
||||
/* Self-hosted fonts (R14) — @fontsource ships the .woff2 locally; Vite bundles
|
||||
them and serves them from our own origin. No Google Fonts / CDN request. */
|
||||
@import '@fontsource/ibm-plex-sans/400.css';
|
||||
@import '@fontsource/ibm-plex-sans/500.css';
|
||||
@import '@fontsource/ibm-plex-sans/600.css';
|
||||
@import '@fontsource/ibm-plex-sans/700.css';
|
||||
@import '@fontsource/ibm-plex-mono/400.css';
|
||||
@import '@fontsource/ibm-plex-mono/500.css';
|
||||
@import '@fontsource/ibm-plex-mono/600.css';
|
||||
|
||||
@source '../../vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php';
|
||||
@source '../../storage/framework/views/*.php';
|
||||
/* Design tokens, then Tailwind v3 layers. */
|
||||
@import './portal-tokens.css';
|
||||
|
||||
@theme {
|
||||
--font-sans: 'Instrument Sans', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
|
||||
'Segoe UI Symbol', 'Noto Color Emoji';
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
html {
|
||||
font-family: var(--font-sans);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
body {
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
font-size: var(--text-base);
|
||||
line-height: var(--lh-normal);
|
||||
}
|
||||
|
||||
/* Uniform, visible focus for every interactive element (a11y §6.4/§9). */
|
||||
:focus-visible {
|
||||
outline: none;
|
||||
box-shadow: var(--focus-ring);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
/* Data/IDs/IPs use tabular figures. */
|
||||
.font-mono {
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
animation-duration: 0.001ms !important;
|
||||
transition-duration: 0.001ms !important;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,62 @@
|
|||
/* CluPilot customer-portal design tokens — refined enterprise, light, single
|
||||
orange accent. Framework-neutral CSS custom properties (work in Tailwind v3
|
||||
or v4). Values calibrated for AA contrast. See design handoff §6. */
|
||||
:root {
|
||||
/* Surfaces (light, slightly cool) */
|
||||
--bg: #f6f7f9;
|
||||
--surface: #ffffff;
|
||||
--surface-2: #f1f3f5;
|
||||
--surface-hover: #f8f9fb;
|
||||
|
||||
/* Lines */
|
||||
--border: #e4e7ec;
|
||||
--border-strong: #d0d5dd;
|
||||
|
||||
/* Text */
|
||||
--text-strong: #101828;
|
||||
--text: #344054;
|
||||
--text-muted: #667085;
|
||||
--text-faint: #98a2b3;
|
||||
|
||||
/* Accent = the single brand / interaction tone: orange */
|
||||
--accent: #f97316; /* brand tone: decorative fills, borders, tints, focus */
|
||||
--accent-hover: #ea6a0c;
|
||||
--accent-active: #c2560a; /* AA-safe fill for white text on orange (~5:1) */
|
||||
--accent-press: #a8480a; /* darker press/hover for accent-fill buttons */
|
||||
--accent-subtle: #fff4ec;
|
||||
--accent-border: #fed7aa;
|
||||
--accent-ring: rgba(249, 115, 22, .35);
|
||||
--on-accent: #ffffff;
|
||||
/* Accent as TEXT on light surfaces (links, small labels) must be dark enough
|
||||
for AA — #f97316 on white is only ~2.9:1, so text uses --accent-active. */
|
||||
--accent-text: #c2560a;
|
||||
|
||||
/* Status semantics — only in badges/alerts, never a second decorative tone */
|
||||
--success: #067a48; --success-bg: #ecfdf3; --success-border: #abefc6;
|
||||
--warning: #b54708; --warning-bg: #fffaeb; --warning-border: #fedf89;
|
||||
--danger: #b42318; --danger-bg: #fef3f2; --danger-border: #fecdca;
|
||||
--info: #175cd3; --info-bg: #eff8ff; --info-border: #b2ddff;
|
||||
|
||||
/* Typography */
|
||||
--font-sans: "IBM Plex Sans", ui-sans-serif, system-ui, sans-serif;
|
||||
--font-mono: "IBM Plex Mono", ui-monospace, monospace;
|
||||
|
||||
--text-xs: 12px; --text-sm: 13px; --text-base: 14px; --text-md: 15px;
|
||||
--text-lg: 18px; --text-xl: 22px; --text-2xl: 28px; --text-3xl: 34px;
|
||||
|
||||
--lh-tight: 1.2; --lh-normal: 1.5;
|
||||
--tracking-tight: -0.02em;
|
||||
--tracking-label: 0.06em;
|
||||
|
||||
/* Radius / shadow / motion / focus */
|
||||
--radius-sm: 6px; --radius: 8px; --radius-lg: 12px; --radius-pill: 999px;
|
||||
|
||||
--shadow-xs: 0 1px 2px rgba(16, 24, 40, .05);
|
||||
--shadow-sm: 0 1px 3px rgba(16, 24, 40, .10), 0 1px 2px rgba(16, 24, 40, .06);
|
||||
--shadow-md: 0 4px 8px -2px rgba(16, 24, 40, .10), 0 2px 4px -2px rgba(16, 24, 40, .06);
|
||||
|
||||
--dur-fast: 120ms; --dur: 180ms; --dur-slow: 240ms;
|
||||
--ease: cubic-bezier(.2, .6, .2, 1);
|
||||
|
||||
--focus-ring: 0 0 0 3px var(--accent-ring);
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}" class="h-full">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
<title>{{ $title ?? config('app.name') }}</title>
|
||||
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
||||
@livewireStyles
|
||||
</head>
|
||||
<body class="min-h-full bg-bg text-body antialiased">
|
||||
{{ $slot }}
|
||||
|
||||
@livewireScripts
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,60 @@
|
|||
/** @type {import('tailwindcss').Config} */
|
||||
// Tailwind v3. Design tokens live as CSS custom properties in
|
||||
// resources/css/portal-tokens.css (framework-neutral); this config only maps
|
||||
// them onto utility names so a token change restyles every utility.
|
||||
export default {
|
||||
content: [
|
||||
'./resources/**/*.blade.php',
|
||||
'./resources/**/*.js',
|
||||
'./app/Livewire/**/*.php',
|
||||
'./app/View/**/*.php',
|
||||
'./vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php',
|
||||
'./vendor/wire-elements/modal/resources/views/**/*.blade.php',
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
bg: 'var(--bg)',
|
||||
surface: 'var(--surface)',
|
||||
'surface-2': 'var(--surface-2)',
|
||||
'surface-hover': 'var(--surface-hover)',
|
||||
line: 'var(--border)',
|
||||
'line-strong': 'var(--border-strong)',
|
||||
ink: 'var(--text-strong)',
|
||||
body: 'var(--text)',
|
||||
muted: 'var(--text-muted)',
|
||||
faint: 'var(--text-faint)',
|
||||
accent: {
|
||||
DEFAULT: 'var(--accent)',
|
||||
hover: 'var(--accent-hover)',
|
||||
active: 'var(--accent-active)',
|
||||
press: 'var(--accent-press)',
|
||||
subtle: 'var(--accent-subtle)',
|
||||
border: 'var(--accent-border)',
|
||||
text: 'var(--accent-text)',
|
||||
},
|
||||
'on-accent': 'var(--on-accent)',
|
||||
success: { DEFAULT: 'var(--success)', bg: 'var(--success-bg)', border: 'var(--success-border)' },
|
||||
warning: { DEFAULT: 'var(--warning)', bg: 'var(--warning-bg)', border: 'var(--warning-border)' },
|
||||
danger: { DEFAULT: 'var(--danger)', bg: 'var(--danger-bg)', border: 'var(--danger-border)' },
|
||||
info: { DEFAULT: 'var(--info)', bg: 'var(--info-bg)', border: 'var(--info-border)' },
|
||||
},
|
||||
fontFamily: {
|
||||
sans: 'var(--font-sans)',
|
||||
mono: 'var(--font-mono)',
|
||||
},
|
||||
fontSize: {
|
||||
xs: 'var(--text-xs)', sm: 'var(--text-sm)', base: 'var(--text-base)', md: 'var(--text-md)',
|
||||
lg: 'var(--text-lg)', xl: 'var(--text-xl)', '2xl': 'var(--text-2xl)', '3xl': 'var(--text-3xl)',
|
||||
},
|
||||
borderRadius: {
|
||||
sm: 'var(--radius-sm)', DEFAULT: 'var(--radius)', lg: 'var(--radius-lg)', pill: 'var(--radius-pill)',
|
||||
},
|
||||
boxShadow: {
|
||||
xs: 'var(--shadow-xs)', sm: 'var(--shadow-sm)', md: 'var(--shadow-md)',
|
||||
},
|
||||
ringColor: { accent: 'var(--accent-ring)' },
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { defineConfig } from 'vite';
|
||||
import laravel from 'laravel-vite-plugin';
|
||||
import tailwindcss from '@tailwindcss/vite';
|
||||
// Tailwind v3 runs via PostCSS (postcss.config.js) — no Vite plugin.
|
||||
|
||||
// Fonts are self-hosted locally (R14) — declared via @font-face in app.css,
|
||||
// NOT fetched from a CDN. No bunny()/Google Fonts provider here.
|
||||
|
|
@ -17,7 +17,6 @@ export default defineConfig({
|
|||
input: ['resources/css/app.css', 'resources/js/app.js'],
|
||||
refresh: true,
|
||||
}),
|
||||
tailwindcss(),
|
||||
],
|
||||
server: {
|
||||
host: '0.0.0.0',
|
||||
|
|
|
|||
Loading…
Reference in New Issue