From 5bc3dc9d083f9265930dae5e5d74411ab4504fcd Mon Sep 17 00:00:00 2001 From: Nicolas Date: Sun, 16 Aug 2026 19:24:30 +0200 Subject: [PATCH] Apply "Mise en Place" visual identity to auth/home screens MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fills in the design tokens proposed for the app (palette, type scale, shadows/radii, and a real dark theme) and applies them to the existing auth and home pages: - _theme.scss: full token set — basil/vermillion/turmeric/raspberry palette, heading type scale (was missing above --font-size-base), elevation/radius scale, and a working dark theme via prefers-color-scheme (color-scheme: light dark was declared but unused). Includes a 3-tier allergen/intolerance color scale, kept distinct from --color-error, for when the recipe/allergy feature lands — not yet consumed by any component. - global.scss: box-sizing reset, headings on the display font stack, visible focus ring. - auth-form.scss / HomePage.scss(+tsx): both screens now render their content on a raised card (--color-surface, radius, shadow) instead of directly on the page background, so login/signup and home read as one coherent app. Also adds .claude/launch.json (pnpm --filter web dev, port 5173) used to preview the change locally. Verified: `vite build` passes; computed styles checked live against the token values in both themes. --- .claude/launch.json | 11 ++ apps/web/src/features/auth/auth-form.scss | 29 +++- apps/web/src/pages/HomePage.scss | 28 +++- apps/web/src/pages/HomePage.tsx | 12 +- apps/web/src/styles/_theme.scss | 158 ++++++++++++++++++---- apps/web/src/styles/global.scss | 40 +++++- 6 files changed, 243 insertions(+), 35 deletions(-) create mode 100644 .claude/launch.json diff --git a/.claude/launch.json b/.claude/launch.json new file mode 100644 index 0000000..28829a9 --- /dev/null +++ b/.claude/launch.json @@ -0,0 +1,11 @@ +{ + "version": "0.0.1", + "configurations": [ + { + "name": "web", + "runtimeExecutable": "pnpm", + "runtimeArgs": ["--filter", "web", "dev"], + "port": 5173 + } + ] +} diff --git a/apps/web/src/features/auth/auth-form.scss b/apps/web/src/features/auth/auth-form.scss index f7e840d..dd42435 100644 --- a/apps/web/src/features/auth/auth-form.scss +++ b/apps/web/src/features/auth/auth-form.scss @@ -17,16 +17,30 @@ align-items: center; justify-content: center; padding: var(--space-md); + background: var(--color-background); } // The form itself: a vertically-stacked card, capped width so it stays -// readable on wide screens. +// readable on wide screens. Raised on its own surface (rather than sitting +// directly on the page background) so it reads as one focused task. .auth-card { display: flex; flex-direction: column; gap: var(--space-xs); width: 100%; max-width: var(--max-width-form); + padding: var(--space-xl); + background: var(--color-surface); + border-radius: var(--radius-md); + box-shadow: var(--shadow-md); + + // Card title — smaller than the global page-title size (h1 defaults to + // --font-size-2xl), since here it's heading a card, not the page. + h1 { + font-size: var(--font-size-lg); + text-align: center; + margin-bottom: var(--space-sm); + } // Field labels sit directly above their input, with a little breathing // room from the previous field. @@ -38,7 +52,10 @@ input { padding: var(--space-sm); + font-family: var(--font-body); font-size: var(--font-size-base); + color: var(--color-text); + background: var(--color-background); border: 1px solid var(--color-border); border-radius: var(--radius-base); } @@ -47,12 +64,14 @@ button { margin-top: var(--space-md); padding: 0.6rem; + font-family: var(--font-body); font-size: var(--font-size-base); + font-weight: 600; cursor: pointer; border-radius: var(--radius-base); border: none; background: var(--color-primary); - color: white; + color: #fff; &:hover:not(:disabled) { background: var(--color-primary-hover); @@ -83,6 +102,12 @@ // "Already have an account? / No account yet?" link row under the form. .auth-switch { font-size: var(--font-size-sm); + color: var(--color-text-muted); margin-top: var(--space-md); text-align: center; + + a { + color: var(--color-primary); + font-weight: 600; + } } diff --git a/apps/web/src/pages/HomePage.scss b/apps/web/src/pages/HomePage.scss index dd6c5fe..a2e8260 100644 --- a/apps/web/src/pages/HomePage.scss +++ b/apps/web/src/pages/HomePage.scss @@ -13,15 +13,35 @@ .home-page { min-height: 100vh; display: flex; - flex-direction: column; align-items: center; justify-content: center; - gap: var(--space-md); padding: var(--space-md); + background: var(--color-background); +} + +// The greeting itself sits on its own surface, same treatment as the auth +// card, so the two screens read as one coherent app rather than two. +.home-card { + display: flex; + flex-direction: column; + align-items: center; + gap: var(--space-md); + padding: var(--space-xl); + background: var(--color-surface); + border-radius: var(--radius-md); + box-shadow: var(--shadow-md); + text-align: center; + + p { + color: var(--color-text-muted); + font-size: var(--font-size-md); + } button { - padding: 0.6rem var(--space-md); + padding: 0.6rem var(--space-lg); + font-family: var(--font-body); font-size: var(--font-size-base); + font-weight: 600; cursor: pointer; border-radius: var(--radius-base); border: 1px solid var(--color-border); @@ -29,7 +49,7 @@ color: var(--color-text); &:hover { - background: var(--color-background); + background: var(--color-surface-alt); } } } diff --git a/apps/web/src/pages/HomePage.tsx b/apps/web/src/pages/HomePage.tsx index 895abca..525ba72 100644 --- a/apps/web/src/pages/HomePage.tsx +++ b/apps/web/src/pages/HomePage.tsx @@ -21,11 +21,13 @@ export function HomePage() { return (
-

batchCooking

-

{t("home.greeting", { firstName: user?.firstName, lastName: user?.lastName })}

- +
+

batchCooking

+

{t("home.greeting", { firstName: user?.firstName, lastName: user?.lastName })}

+ +
); } diff --git a/apps/web/src/styles/_theme.scss b/apps/web/src/styles/_theme.scss index 386743b..b062d73 100644 --- a/apps/web/src/styles/_theme.scss +++ b/apps/web/src/styles/_theme.scss @@ -3,29 +3,61 @@ // and other reusable values across the whole app. // // Exposed as CSS custom properties on :root (not plain SCSS variables) so -// they're available at *runtime*, not just compile time — this is what -// would let a future dark-mode toggle (or any theme switch) just redefine -// these variables instead of rebuilding the stylesheet. Every other .scss -// file should reference `var(--token-name)`, never a hardcoded color/size. +// they're available at *runtime*, not just compile time — this is what lets +// dark mode work below by simply redefining these variables instead of +// rebuilding the stylesheet. Every other .scss file should reference +// `var(--token-name)`, never a hardcoded color/size. +// +// Palette name: "Mise en Place" — a kitchen-operations identity (batch +// cooking as logistics: everything labeled and in its place before you +// start) rather than a food-blog one. See the design proposal for the full +// rationale: https://claude.ai/code/artifact/1db63af0-cfd1-4f77-9369-71ca6accd06f // // Import this partial once, globally (see global.scss) — never re-import it // from a component-level .scss file, `:root` only needs to be declared once. // ============================================================================= :root { - // --- Color palette -------------------------------------------------------- - // Neutral surface: page background vs. the "card" surface content sits on. - --color-background: #ffffff; + // --- Surfaces & ink --------------------------------------------------- + // Neutral surface: page background ("porcelaine") vs. the card surface + // content sits on, plus a recessed variant for panels/table headers. + --color-background: #eef2ed; --color-surface: #ffffff; + --color-surface-alt: #e2e8e0; // Text. - --color-text: #1a1a1a; - --color-text-muted: #555555; - // Brand/accent — used for primary buttons and links. - --color-primary: #2f6f4f; - --color-primary-hover: #24573e; - // Feedback. - --color-error: #c0392b; - --color-border: #888888; + --color-text: #1f2a22; + --color-text-muted: #57685a; + --color-border: #c7d0c4; + + // --- Brand accents, each with one job -------------------------------- + // Basil — primary actions, links, brand presence. + --color-primary: #2e6b4a; + --color-primary-hover: #244f38; + // Vermillion — secondary accent for urgency/strong calls to action + // (e.g. a timer, "start session"). Never reused for allergen alerts + // below — those need their own, unambiguous color. + --color-accent: #cc4b26; + --color-accent-hover: #a83c1c; + // Turmeric — category/classification tags. + --color-tag: #c98a1b; + --color-tag-ink: #3a2c05; // pairs with a solid --color-tag fill only. + + // --- Feedback ----------------------------------------------------------- + --color-success: #2e6b4a; + --color-warning: #c98a1b; + --color-error: #b3271e; + + // --- Allergens / intolerances -------------------------------------------- + // A 3-tier food-safety scale, kept distinct from --color-error so an + // allergen warning is never confused with a form validation error: + // - critical (declared allergen): its own color, solid/inverted fill + // - moderate (intolerance): reuses --color-warning, tinted fill + // - trace ("may contain traces of…"): neutral, dashed outline + // The severity is carried by the FILL TREATMENT, not the hue alone, so + // it stays legible for color-blind users. See the design proposal's + // "Alertes & allergènes" section for the full component set. + --color-allergen: #a8123f; + --color-allergen-ink: #ffe9ef; // pairs with a solid --color-allergen fill only. // --- Spacing scale --------------------------------------------------------- // Multiples of a 4px base unit — use these instead of ad hoc px values so @@ -35,21 +67,101 @@ --space-md: 1rem; // 16px --space-lg: 1.5rem; // 24px --space-xl: 2rem; // 32px + --space-2xl: 3rem; // 48px — inter-section spacing // --- Typography -------------------------------------------------------- - --font-family-base: system-ui, sans-serif; - --font-size-base: 1rem; - --font-size-sm: 0.875rem; - --font-size-xs: 0.8rem; + // System font stacks only — no remote webfont, so there's zero loading + // latency and no flash of unstyled text, which fits an app meant to be + // used quickly under time pressure. Three roles: a condensed "label" + // face for headings/eyebrows, a humanist face for body copy, and a + // monospace for anything that lines up in columns (times, quantities). + --font-display: "Bahnschrift", "Arial Narrow", "Segoe UI", sans-serif; + --font-body: "Segoe UI", "Helvetica Neue", Arial, sans-serif; + --font-mono: "Cascadia Mono", Consolas, "SF Mono", "Liberation Mono", monospace; - // --- Shape / misc -------------------------------------------------------- - --radius-base: 4px; + --font-size-xs: 0.75rem; // 12px — captions, meta + --font-size-sm: 0.875rem; // 14px — labels, secondary text + --font-size-base: 1rem; // 16px — body + --font-size-md: 1.125rem; // 18px — lead paragraph + --font-size-lg: 1.375rem; // 22px — H3 / card titles + --font-size-xl: 1.75rem; // 28px — H2 / section titles + --font-size-2xl: 2.25rem; // 36px — H1 / page titles + --font-size-3xl: 3rem; // 48px — display, exceptional use only + + // --- Shape / elevation -------------------------------------------------- + --radius-base: 4px; // controls (inputs, buttons) — deliberately flat + --radius-md: 10px; // cards + --radius-lg: 18px; // panels, modals + --radius-pill: 999px; // tags, badges --max-width-form: 22rem; + + --shadow-sm: 0 1px 2px rgba(31, 42, 34, 0.08), 0 1px 1px rgba(31, 42, 34, 0.06); + --shadow-md: 0 6px 16px rgba(31, 42, 34, 0.12), 0 2px 6px rgba(31, 42, 34, 0.08); } // Lets the browser pick sensible default colors (form controls, scrollbars) -// for whichever mode (light/dark) the user's OS is in, until this app has -// its own explicit dark theme wired to the tokens above. +// for whichever mode the user ends up in. :root { color-scheme: light dark; } + +// --- Dark mode --------------------------------------------------------- +// Follows the OS/browser preference by default. Guarded with +// `:root:not([data-theme="light"])` so that, if a manual theme switch is +// ever added, an explicit "light" choice can override a dark OS setting — +// today nothing sets `data-theme`, so this simply tracks system preference. +@media (prefers-color-scheme: dark) { + :root:not([data-theme="light"]) { + --color-background: #14181a; + --color-surface: #1c221e; + --color-surface-alt: #262e27; + --color-text: #edf1ea; + --color-text-muted: #a9b5a6; + --color-border: #384038; + + --color-primary: #5fae7e; + --color-primary-hover: #7cc496; + --color-accent: #ea7a48; + --color-accent-hover: #f0946c; + --color-tag: #e8b84b; + --color-tag-ink: #2a2005; + + --color-success: #5fae7e; + --color-warning: #e8b84b; + --color-error: #e5675a; + + --color-allergen: #e2547b; + --color-allergen-ink: #3a0416; + + --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.35); + --shadow-md: 0 8px 20px rgba(0, 0, 0, 0.45); + } +} + +// Mirrors the block above for a future explicit "dark" choice (e.g. a +// theme toggle), so it wins over the OS setting in both directions. +:root[data-theme="dark"] { + --color-background: #14181a; + --color-surface: #1c221e; + --color-surface-alt: #262e27; + --color-text: #edf1ea; + --color-text-muted: #a9b5a6; + --color-border: #384038; + + --color-primary: #5fae7e; + --color-primary-hover: #7cc496; + --color-accent: #ea7a48; + --color-accent-hover: #f0946c; + --color-tag: #e8b84b; + --color-tag-ink: #2a2005; + + --color-success: #5fae7e; + --color-warning: #e8b84b; + --color-error: #e5675a; + + --color-allergen: #e2547b; + --color-allergen-ink: #3a0416; + + --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.35); + --shadow-md: 0 8px 20px rgba(0, 0, 0, 0.45); +} diff --git a/apps/web/src/styles/global.scss b/apps/web/src/styles/global.scss index 94c22ff..efce8ca 100644 --- a/apps/web/src/styles/global.scss +++ b/apps/web/src/styles/global.scss @@ -7,11 +7,49 @@ @use "./theme"; +// Include borders/padding in an element's declared width/height everywhere, +// rather than the browser default of adding them on top. +*, +*::before, +*::after { + box-sizing: border-box; +} + // Minimal reset: remove the default body margin so pages can control their // own layout without fighting the browser's default 8px margin. body { margin: 0; - font-family: var(--font-family-base); + font-family: var(--font-body); + font-size: var(--font-size-base); + line-height: 1.55; color: var(--color-text); background: var(--color-background); } + +// Headings use the condensed "label" face app-wide — see _theme.scss for +// the rationale. `text-wrap: balance` avoids a lone short word wrapping +// onto its own line in multi-line titles. +h1, +h2, +h3, +h4, +h5, +h6 { + margin: 0; + font-family: var(--font-display); + font-weight: 700; + text-wrap: balance; +} + +// Default to the page-title size; a heading used as a smaller component +// title (e.g. the auth card's

) overrides this in its own stylesheet. +h1 { + font-size: var(--font-size-2xl); +} + +// A visible, consistent focus ring for keyboard navigation — the browser +// default varies a lot between elements and browsers. +:focus-visible { + outline: 2px solid var(--color-accent); + outline-offset: 2px; +}