Apply "Mise en Place" visual identity to auth/home screens (#8)

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.
This commit is contained in:
kyuno053 2026-08-16 19:43:13 +02:00 committed by GitHub
parent de63be7ba4
commit 6302310dba
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 243 additions and 35 deletions

11
.claude/launch.json Normal file
View file

@ -0,0 +1,11 @@
{
"version": "0.0.1",
"configurations": [
{
"name": "web",
"runtimeExecutable": "pnpm",
"runtimeArgs": ["--filter", "web", "dev"],
"port": 5173
}
]
}

View file

@ -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;
}
}

View file

@ -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);
}
}
}

View file

@ -21,11 +21,13 @@ export function HomePage() {
return (
<main className="home-page">
<h1>batchCooking</h1>
<p>{t("home.greeting", { firstName: user?.firstName, lastName: user?.lastName })}</p>
<button type="button" onClick={handleLogout}>
{t("home.logout")}
</button>
<div className="home-card">
<h1>batchCooking</h1>
<p>{t("home.greeting", { firstName: user?.firstName, lastName: user?.lastName })}</p>
<button type="button" onClick={handleLogout}>
{t("home.logout")}
</button>
</div>
</main>
);
}

View file

@ -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);
}

View file

@ -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 <h1>) 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;
}