From 227c64d346f1d8f9b80dc11515333a549333d70f Mon Sep 17 00:00:00 2001 From: xiaomai Date: Fri, 8 May 2026 16:25:42 +0800 Subject: [PATCH] refactor(ui): standardize page layouts and component styling Introduce structural CSS classes for page shells, headers, and surface cards Update primary theme color to red and neutral to zinc across the application --- AGENTS.md | 11 ++ app.config.ts | 4 +- app/assets/css/main.css | 160 +++++++++++++++++++++++++- app/layouts/default.vue | 52 +++++---- app/pages/bookings/index.vue | 42 +++---- app/pages/confirmation/[token].vue | 18 +-- app/pages/index.vue | 161 +++++++++++++++++---------- app/pages/login/index.vue | 86 ++++++++++---- app/pages/management/users/index.vue | 16 +-- app/pages/receipt/[token].vue | 46 ++++---- app/pages/seat/[token].vue | 30 ++--- app/pages/security/index.vue | 22 ++-- 12 files changed, 454 insertions(+), 194 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index f141c38..7c6bfe2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -29,6 +29,17 @@ The Git history follows Conventional Commit-style messages, commonly `feat(scope Create local configuration from `.env.example`. Do not commit secrets, session keys, database URLs with credentials, or WhatsApp tokens. Keep `NUXT_PUBLIC_APP_URL` aligned with the browser origin because WebAuthn requires a stable relying-party origin. +## UI Safety Rules (CRITICAL) + +User-facing UI must NEVER contain: + +- prompts +- remarks +- planning notes +- debug messages +- explanations of what was changed +- internal field names like `debug`, `meta`, `internal` + ## Agent-Specific Instructions Keep implementations concise and scoped to the requested behavior. Prefer the smallest code change that fits existing patterns, and avoid unrelated refactors, formatting churn, or broad rewrites. diff --git a/app.config.ts b/app.config.ts index 4a20171..1e21566 100644 --- a/app.config.ts +++ b/app.config.ts @@ -1,8 +1,8 @@ export default defineAppConfig({ ui: { colors: { - primary: 'amber', - neutral: 'stone' + primary: 'red', + neutral: 'zinc' } } }) diff --git a/app/assets/css/main.css b/app/assets/css/main.css index 16031fb..2ae40ca 100644 --- a/app/assets/css/main.css +++ b/app/assets/css/main.css @@ -1,11 +1,169 @@ @import "tailwindcss"; @import "@nuxt/ui"; +:root { + color-scheme: light dark; +} + html { scroll-behavior: smooth; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; } body { min-height: 100dvh; - background: var(--ui-bg); + background: + linear-gradient(180deg, color-mix(in srgb, var(--ui-primary) 7%, transparent) 0, transparent 22rem), + var(--ui-bg-muted); + font-feature-settings: "cv02", "cv03", "cv04", "cv11"; +} + +button, +a, +input, +textarea, +select { + touch-action: manipulation; +} + +::selection { + background: color-mix(in srgb, var(--ui-primary) 18%, transparent); +} + +.page-shell { + width: min(100%, 80rem); + margin-inline: auto; + padding: 1.5rem 1rem 2.5rem; +} + +.page-shell-narrow { + width: min(100%, 64rem); + margin-inline: auto; + padding: 1.5rem 1rem 2.5rem; +} + +.page-shell-compact { + width: min(100%, 48rem); + margin-inline: auto; + padding: 1.5rem 1rem 2.5rem; +} + +.page-header { + display: flex; + flex-direction: column; + gap: 0.75rem; + margin-bottom: 1.25rem; +} + +.page-eyebrow { + display: inline-flex; + width: fit-content; + align-items: center; + gap: 0.5rem; + border-radius: 999px; + border: 1px solid color-mix(in srgb, var(--ui-primary) 18%, var(--ui-border)); + background: color-mix(in srgb, var(--ui-primary) 8%, var(--ui-bg)); + padding: 0.25rem 0.625rem; + color: var(--ui-primary); + font-size: 0.75rem; + font-weight: 650; +} + +.page-title { + max-width: 44rem; + color: var(--ui-text-highlighted); + font-size: clamp(1.875rem, 1.5rem + 1.25vw, 3rem); + font-weight: 760; + line-height: 1.05; +} + +.page-description { + max-width: 42rem; + color: var(--ui-text-muted); + font-size: 1rem; + line-height: 1.65; +} + +.surface-card { + border: 1px solid color-mix(in srgb, var(--ui-border) 86%, transparent); + background: color-mix(in srgb, var(--ui-bg) 95%, transparent); + box-shadow: 0 1px 2px color-mix(in srgb, black 4%, transparent), 0 18px 48px color-mix(in srgb, black 7%, transparent); +} + +.surface-panel { + border: 1px solid color-mix(in srgb, var(--ui-border) 88%, transparent); + background: color-mix(in srgb, var(--ui-bg-elevated) 76%, var(--ui-bg)); + box-shadow: inset 0 1px 0 color-mix(in srgb, white 48%, transparent); +} + +.metric-card { + position: relative; + overflow: hidden; + border: 1px solid color-mix(in srgb, var(--ui-border) 82%, transparent); + background: linear-gradient(180deg, color-mix(in srgb, var(--ui-bg) 98%, white) 0, color-mix(in srgb, var(--ui-bg-elevated) 82%, var(--ui-bg)) 100%); + box-shadow: 0 1px 2px color-mix(in srgb, black 4%, transparent); +} + +.metric-card::before { + content: ""; + position: absolute; + inset: 0 auto 0 0; + width: 3px; + background: var(--ui-primary); + opacity: 0.82; +} + +.compact-table { + border-radius: 1rem; +} + +.compact-table :where(th) { + background: var(--ui-bg-muted); + color: var(--ui-text-muted); + font-size: 0.75rem; + font-weight: 650; + text-transform: uppercase; +} + +.form-grid { + display: grid; + gap: 1rem; +} + +.action-row { + display: flex; + flex-direction: column-reverse; + gap: 0.625rem; +} + +@media (min-width: 640px) { + .action-row { + flex-direction: row; + justify-content: flex-end; + } +} + +@media (min-width: 768px) { + .page-shell, + .page-shell-narrow, + .page-shell-compact { + padding: 2rem 1.5rem 3rem; + } + + .page-header { + margin-bottom: 1.5rem; + } +} + +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + scroll-behavior: auto !important; + transition-duration: 0.01ms !important; + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + } } diff --git a/app/layouts/default.vue b/app/layouts/default.vue index 68272ca..279cbb7 100644 --- a/app/layouts/default.vue +++ b/app/layouts/default.vue @@ -1,7 +1,5 @@