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:
parent
de63be7ba4
commit
6302310dba
6 changed files with 243 additions and 35 deletions
11
.claude/launch.json
Normal file
11
.claude/launch.json
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"version": "0.0.1",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "web",
|
||||||
|
"runtimeExecutable": "pnpm",
|
||||||
|
"runtimeArgs": ["--filter", "web", "dev"],
|
||||||
|
"port": 5173
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -17,16 +17,30 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: var(--space-md);
|
padding: var(--space-md);
|
||||||
|
background: var(--color-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The form itself: a vertically-stacked card, capped width so it stays
|
// 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 {
|
.auth-card {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: var(--space-xs);
|
gap: var(--space-xs);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: var(--max-width-form);
|
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
|
// Field labels sit directly above their input, with a little breathing
|
||||||
// room from the previous field.
|
// room from the previous field.
|
||||||
|
|
@ -38,7 +52,10 @@
|
||||||
|
|
||||||
input {
|
input {
|
||||||
padding: var(--space-sm);
|
padding: var(--space-sm);
|
||||||
|
font-family: var(--font-body);
|
||||||
font-size: var(--font-size-base);
|
font-size: var(--font-size-base);
|
||||||
|
color: var(--color-text);
|
||||||
|
background: var(--color-background);
|
||||||
border: 1px solid var(--color-border);
|
border: 1px solid var(--color-border);
|
||||||
border-radius: var(--radius-base);
|
border-radius: var(--radius-base);
|
||||||
}
|
}
|
||||||
|
|
@ -47,12 +64,14 @@
|
||||||
button {
|
button {
|
||||||
margin-top: var(--space-md);
|
margin-top: var(--space-md);
|
||||||
padding: 0.6rem;
|
padding: 0.6rem;
|
||||||
|
font-family: var(--font-body);
|
||||||
font-size: var(--font-size-base);
|
font-size: var(--font-size-base);
|
||||||
|
font-weight: 600;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border-radius: var(--radius-base);
|
border-radius: var(--radius-base);
|
||||||
border: none;
|
border: none;
|
||||||
background: var(--color-primary);
|
background: var(--color-primary);
|
||||||
color: white;
|
color: #fff;
|
||||||
|
|
||||||
&:hover:not(:disabled) {
|
&:hover:not(:disabled) {
|
||||||
background: var(--color-primary-hover);
|
background: var(--color-primary-hover);
|
||||||
|
|
@ -83,6 +102,12 @@
|
||||||
// "Already have an account? / No account yet?" link row under the form.
|
// "Already have an account? / No account yet?" link row under the form.
|
||||||
.auth-switch {
|
.auth-switch {
|
||||||
font-size: var(--font-size-sm);
|
font-size: var(--font-size-sm);
|
||||||
|
color: var(--color-text-muted);
|
||||||
margin-top: var(--space-md);
|
margin-top: var(--space-md);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--color-primary);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,15 +13,35 @@
|
||||||
.home-page {
|
.home-page {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: var(--space-md);
|
|
||||||
padding: 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 {
|
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-size: var(--font-size-base);
|
||||||
|
font-weight: 600;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border-radius: var(--radius-base);
|
border-radius: var(--radius-base);
|
||||||
border: 1px solid var(--color-border);
|
border: 1px solid var(--color-border);
|
||||||
|
|
@ -29,7 +49,7 @@
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: var(--color-background);
|
background: var(--color-surface-alt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,11 +21,13 @@ export function HomePage() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="home-page">
|
<main className="home-page">
|
||||||
<h1>batchCooking</h1>
|
<div className="home-card">
|
||||||
<p>{t("home.greeting", { firstName: user?.firstName, lastName: user?.lastName })}</p>
|
<h1>batchCooking</h1>
|
||||||
<button type="button" onClick={handleLogout}>
|
<p>{t("home.greeting", { firstName: user?.firstName, lastName: user?.lastName })}</p>
|
||||||
{t("home.logout")}
|
<button type="button" onClick={handleLogout}>
|
||||||
</button>
|
{t("home.logout")}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,29 +3,61 @@
|
||||||
// and other reusable values across the whole app.
|
// and other reusable values across the whole app.
|
||||||
//
|
//
|
||||||
// Exposed as CSS custom properties on :root (not plain SCSS variables) so
|
// Exposed as CSS custom properties on :root (not plain SCSS variables) so
|
||||||
// they're available at *runtime*, not just compile time — this is what
|
// they're available at *runtime*, not just compile time — this is what lets
|
||||||
// would let a future dark-mode toggle (or any theme switch) just redefine
|
// dark mode work below by simply redefining these variables instead of
|
||||||
// these variables instead of rebuilding the stylesheet. Every other .scss
|
// rebuilding the stylesheet. Every other .scss file should reference
|
||||||
// file should reference `var(--token-name)`, never a hardcoded color/size.
|
// `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
|
// 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.
|
// from a component-level .scss file, `:root` only needs to be declared once.
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
// --- Color palette --------------------------------------------------------
|
// --- Surfaces & ink ---------------------------------------------------
|
||||||
// Neutral surface: page background vs. the "card" surface content sits on.
|
// Neutral surface: page background ("porcelaine") vs. the card surface
|
||||||
--color-background: #ffffff;
|
// content sits on, plus a recessed variant for panels/table headers.
|
||||||
|
--color-background: #eef2ed;
|
||||||
--color-surface: #ffffff;
|
--color-surface: #ffffff;
|
||||||
|
--color-surface-alt: #e2e8e0;
|
||||||
// Text.
|
// Text.
|
||||||
--color-text: #1a1a1a;
|
--color-text: #1f2a22;
|
||||||
--color-text-muted: #555555;
|
--color-text-muted: #57685a;
|
||||||
// Brand/accent — used for primary buttons and links.
|
--color-border: #c7d0c4;
|
||||||
--color-primary: #2f6f4f;
|
|
||||||
--color-primary-hover: #24573e;
|
// --- Brand accents, each with one job --------------------------------
|
||||||
// Feedback.
|
// Basil — primary actions, links, brand presence.
|
||||||
--color-error: #c0392b;
|
--color-primary: #2e6b4a;
|
||||||
--color-border: #888888;
|
--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 ---------------------------------------------------------
|
// --- Spacing scale ---------------------------------------------------------
|
||||||
// Multiples of a 4px base unit — use these instead of ad hoc px values so
|
// Multiples of a 4px base unit — use these instead of ad hoc px values so
|
||||||
|
|
@ -35,21 +67,101 @@
|
||||||
--space-md: 1rem; // 16px
|
--space-md: 1rem; // 16px
|
||||||
--space-lg: 1.5rem; // 24px
|
--space-lg: 1.5rem; // 24px
|
||||||
--space-xl: 2rem; // 32px
|
--space-xl: 2rem; // 32px
|
||||||
|
--space-2xl: 3rem; // 48px — inter-section spacing
|
||||||
|
|
||||||
// --- Typography --------------------------------------------------------
|
// --- Typography --------------------------------------------------------
|
||||||
--font-family-base: system-ui, sans-serif;
|
// System font stacks only — no remote webfont, so there's zero loading
|
||||||
--font-size-base: 1rem;
|
// latency and no flash of unstyled text, which fits an app meant to be
|
||||||
--font-size-sm: 0.875rem;
|
// used quickly under time pressure. Three roles: a condensed "label"
|
||||||
--font-size-xs: 0.8rem;
|
// 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 --------------------------------------------------------
|
--font-size-xs: 0.75rem; // 12px — captions, meta
|
||||||
--radius-base: 4px;
|
--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;
|
--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)
|
// 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
|
// for whichever mode the user ends up in.
|
||||||
// its own explicit dark theme wired to the tokens above.
|
|
||||||
:root {
|
:root {
|
||||||
color-scheme: light dark;
|
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);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,49 @@
|
||||||
|
|
||||||
@use "./theme";
|
@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
|
// Minimal reset: remove the default body margin so pages can control their
|
||||||
// own layout without fighting the browser's default 8px margin.
|
// own layout without fighting the browser's default 8px margin.
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
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);
|
color: var(--color-text);
|
||||||
background: var(--color-background);
|
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;
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue