feat(design): v2 opt-in (atmospheric dusks) — Settings toggle, cookie-mirrored
CI / test (push) Has been cancelled

Lets users opt into the new atmospheric design without affecting users on v1.
Adds a beta-flag toggle in Settings → Design. Server-side preference persists
across devices; a cookie mirrors it so unauthenticated Twig pages do correct
first-paint without an extra DB roundtrip.

Backend:
- User.designVersion column (nullable VARCHAR(10); null defaults to 'v1')
- Migration Version20260515120000
- PATCH /api/user/design endpoint accepting 'v1'|'v2', sets wevisto_design cookie
- SpaController injects data-design on <html> + refreshes the cookie on every
  SPA load (keeps cross-device pref in sync)
- Twig templates (base, login, register, help, setup, token-*) read the
  cookie via {{ app.request.cookies.get('wevisto_design')|default('v1') }}
  so login/setup pages also respect the user's design choice

Frontend:
- design-v2.scss — opt-in overlay scoped under [data-design="v2"]. Overrides
  --color-* tokens to dusk variants per theme (warm-craft → amber, ocean-dusk
  stays, etc.), adds harbor photo backdrop via body::before with theme tint
  via body::after. Glass-card blur on existing surfaces. v1 untouched.
- harbor.jpg shipped as a public asset (270KB, single-fetch, cached)
- User type gains designVersion ('v1' | 'v2')
- SettingsView toggle (Original / Atmospheric) calls the API, updates the
  data-design attribute optimistically, reverts on failure

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-15 12:28:44 -04:00
parent 5bb8289a54
commit a302ac09b4
36 changed files with 367 additions and 58 deletions
+117
View File
@@ -0,0 +1,117 @@
// ─── Design v2 — atmospheric dusks ──────────────────────────────────────
// Opt-in overlay activated via [data-design="v2"] on <html>. The user's
// theme preference (warm-craft, ocean-dusk, etc.) chooses *which* dusk;
// all six get a tinted-photo backdrop and a darker, glass-friendly token set.
//
// The yellow V (brand) stays #f0d000 across every dusk.
[data-design="v2"] {
// Brand constants — survive every theme/dusk
--brand-yellow: #f0d000;
// Default dusk = warm-craft → "amber dusk"
--color-bg: #1a0d05;
--color-surface: rgba(50, 22, 8, 0.55);
--color-surface-2: rgba(80, 38, 14, 0.55);
--color-border: rgba(230, 180, 130, 0.20);
--color-text: #faecd0;
--color-text-muted: #c8a880;
--color-primary: #e89048;
--color-primary-fg: #1a0d05;
--color-secondary: rgba(80, 38, 14, 0.55);
--color-secondary-fg:#faecd0;
--color-destructive: #e08070;
--color-destructive-fg: #ffffff;
--color-focus-ring: var(--brand-yellow);
// Per-theme dusk overrides — only the bg-tint + accent change between dusks
&[data-theme="ocean-dusk"] {
--color-bg: #06121f;
--color-surface: rgba(10, 28, 48, 0.55);
--color-surface-2: rgba(18, 42, 70, 0.55);
--color-border: rgba(180, 210, 235, 0.18);
--color-text: #f4eed8;
--color-text-muted: #b0c4d8;
--color-primary: #4e9fc8;
--color-primary-fg: #06121f;
}
&[data-theme="sage-cream"] {
--color-bg: #081208;
--color-surface: rgba(18, 38, 22, 0.55);
--color-surface-2: rgba(28, 60, 32, 0.55);
--color-border: rgba(180, 220, 180, 0.18);
--color-text: #ecf3e0;
--color-text-muted: #a8c0a0;
--color-primary: #88c068;
--color-primary-fg: #081208;
}
&[data-theme="playful-pop"] {
--color-bg: #1a060f;
--color-surface: rgba(48, 14, 36, 0.55);
--color-surface-2: rgba(72, 22, 54, 0.55);
--color-border: rgba(230, 180, 200, 0.20);
--color-text: #f8e8ec;
--color-text-muted: #d0a0b8;
--color-primary: #d878a0;
--color-primary-fg: #1a060f;
}
&[data-theme="dusty-mauve"] {
--color-bg: #100618;
--color-surface: rgba(36, 14, 50, 0.55);
--color-surface-2: rgba(54, 22, 74, 0.55);
--color-border: rgba(210, 190, 230, 0.18);
--color-text: #f0e8f8;
--color-text-muted: #c0b0d0;
--color-primary: #b890d8;
--color-primary-fg: #100618;
}
&[data-theme="honey-slate"] {
--color-bg: #18120a;
--color-surface: rgba(42, 32, 12, 0.55);
--color-surface-2: rgba(62, 50, 22, 0.55);
--color-border: rgba(232, 200, 130, 0.22);
--color-text: #faf0d8;
--color-text-muted: #d0b888;
--color-primary: #e8c050;
--color-primary-fg: #18120a;
}
// Harbor photo backdrop — fixed under everything, theme-tinted
body {
position: relative;
}
body::before {
content: '';
position: fixed;
inset: 0;
background: url('/build/assets/harbor.jpg') center/cover no-repeat;
filter: brightness(0.55) saturate(0.9);
z-index: -3;
}
body::after {
content: '';
position: fixed;
inset: 0;
background: var(--color-bg);
opacity: 0.55;
mix-blend-mode: multiply;
z-index: -2;
pointer-events: none;
}
// Frosted-glass surfaces — anywhere v1 used --color-surface as a solid bg
// becomes a backdrop-blurred semi-translucent panel.
// (The v1 components don't change their structure; they just inherit
// the new token values + glass effect via this overlay.)
.frame-card,
.library__tile,
.settings__section-card,
.home-view__empty-card {
backdrop-filter: saturate(160%) blur(20px);
-webkit-backdrop-filter: saturate(160%) blur(20px);
}
// The yellow V in the wordmark needs to survive even when the V is
// technically part of the brand glyph (no Vue change required — handled
// by tokens).
}