Apply "Mise en Place" visual identity to auth/home screens
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
5bc3dc9d08
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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,11 +21,13 @@ export function HomePage() {
|
|||
|
||||
return (
|
||||
<main className="home-page">
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue