/* ==========================================================================
   The Cooking Cliff Notes: the foundation ("recipe box, in full colour")
   The family recipe tin, with its dividers' colours turned up. Warm
   card-stock paper, white index cards, warm ink, a blue-black fountain
   pen, a red pencil and one highlighter; eight bright category dividers,
   each a fill, a pale tint and a deeper edge, standing in a teal enamel
   tin. Every recipe card wears its category's tab on its top edge and a
   stripe of the same colour along it; sections sit on a soft wash of one
   category's tint. Few words: short labels, figures, glyphs and photos.
   Besley for headings and figures, Source Sans 3 for everything you read
   and press. Light only. One stylesheet for the shared parts, no build
   step, CSP-safe: classes only, never style="".

   Each surface owns its own stylesheet on top of this one: home.css (home,
   our story), recipe.css (recipe page, forms, discussion, Test Kitchen,
   My recipes), account.css and flags.css (account, auth, join, errors,
   reports), cookbook.css and print.css (My Cookbook, printing), admin.css
   (admin). Each section below documents its classes; the surface
   stylesheets use them and never restyle them or redefine a token.
   BRAND.md sets out the system behind them: the mark, the type, the
   shapes, the colours and their jobs, the divider tabs, the tin and the
   journal, the washes and the highlighter.

   Contents
   01  Fonts (self-hosted, OFL; licences in /static/fonts/)
   02  Tokens: colour, the eight categories (fill, tint, edge), the tin
       and the journal, type, space, shape, motion; the category classes
       (.cat-*) and the section grounds (.ground-*)
   03  Base & typography; the index card and ruled text; the category word
   04  Layout primitives: container, sections, the section opener
   05  Masthead, navigation, menus, site banners
   06  Icons & buttons, the clip toggle
   07  Form controls, error summary, divider tabs (filters, the crumb, the
       card's tab), tabs
   08  Status marks, tally, figures and their legend, score readout, scale
   09  Recipe cards: the photo-card grid
   10  Category dividers: the tin
   11  Tables, pager, empty states, notices
   12  Footer: the bottom of the box
   13  Status words
   14  Utilities
   15  Responsive (980 / 720 / 480 / 400 / 359 breakpoints, coarse pointers)
   16  Motion, forced colours, print
   17  Languages and scripts: Arabic (right to left), Japanese

   The grammar: every colour has exactly one job.
   Ink          the recipe as written, and the interface; the only text
                on a category's fill
   Pen          blue-black fountain pen: handwriting and the testers'
                figures (their notes, ticks, names, cook counts, the
                averages and the scale's pin) and the reader's own (their
                cookbook notes, what a cook writes on the cook-report
                sheet). Never interface text nobody wrote by hand. Its one
                other job is the focus ring (never text); buttons hover to
                ink-2, not pen.
   Red pencil   corrections: Needs work, errors, destructive actions,
                Hidden. Errors keep a bar, a glyph and words; Needs work
                its correction glyph and wavy underline. "Cooked with
                changes" is NOT red pencil.
   Ochre        warnings only, with a glyph or words. Never a status word.
   Card red     the index card's 2px header line: a heading sits on it
                (page titles, section heads, card heads, the masthead, the
                footer name). Structure only, never text, never a fill.
   Ruled blue   the card's lines (--rule): every row is written on one.
                Structure only, never text, never an input border.
   Highlighter  four uses only (BRAND.md): annotated step numerals, the
                pass zone, the Proven row in the log, admin counts that
                need a human.
   Categories   eight dividers, each three colours: the fill (a divider,
                a tab, a card's stripe, a photo's mount; ink on it), the
                tint (a section's ground, a page's header) and the edge
                (a fill's outline, a category glyph). Always on or beside
                the category's word; never a status, never text.
   Tin, journal illustration only: the enamel tin the dividers stand in,
                the founder's blue journal. Never text, never interface.

   Paper is open and ruled: rows on blue lines, headings on red lines.
   Index cards (white stock, 1px edge, 4px corners, no shadow) hold what
   IS a card: a recipe in a grid, the test record, the ingredients, the
   method, the cook-report sheet. Radius 4px on cards, buttons and
   controls, 2px on photos; only a divider's tab is rounder (6px), and
   never a capsule. No shadows, no pills, no gradients, no icons in discs.

   Right to left: spacing, borders, offsets and alignment use logical
   properties, which mirror under <html dir="rtl">. What cannot be logical
   (transforms, background positions) has a [dir="rtl"] override beside it.
   ========================================================================== */

/* 01  Fonts ---------------------------------------------------------------
   Besley (Clarendon, headings and figures) and Source Sans 3 (text),
   variable, latin subset. Only the two upright files are preloaded by the
   layouts; the Source Sans italic loads on demand (testers' short notes). */
@font-face {
    font-family: "Besley";
    src: url("../fonts/besley-latin-wght-normal.woff2?v=f6acafeade510fcd") format("woff2");
    font-weight: 400 900;
    font-style: normal;
    font-display: swap;
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: "Source Sans 3";
    src: url("../fonts/source-sans-3-latin-wght-normal.woff2?v=7a19a7027e125257") format("woff2");
    font-weight: 200 900;
    font-style: normal;
    font-display: swap;
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: "Source Sans 3";
    src: url("../fonts/source-sans-3-latin-wght-italic.woff2?v=9a15dafc2c2b2414") format("woff2");
    font-weight: 200 900;
    font-style: italic;
    font-display: swap;
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* 02  Tokens --------------------------------------------------------------
   All colour lives here; no hex anywhere else (print.css and the emails
   keep their own copies). Surfaces never redefine a token. The site is
   light only: color-scheme: light keeps form controls and scrollbars
   light whatever the OS's setting. Ratios are WCAG 2 contrast, measured
   (v0.10 notes, contrast.py): text against paper / card / paper-2 /
   paper-3, in that order, unless noted. Body text needs 4.5:1, large
   text 3:1, control borders and the focus ring 3:1 against their ground. */
:root {
    color-scheme: light;

    /* Type families. The webfonts are latin only: section 17 swaps --serif
       and --sans for Arabic and Japanese. The --latin-* stacks stay latin
       in every language (the word mark, language codes). */
    --latin-serif: "Besley", "Rockwell", "Clarendon", Georgia, serif;
    --latin-sans: "Source Sans 3", "Segoe UI", system-ui, -apple-system, "Helvetica Neue", Arial, sans-serif;
    --serif: var(--latin-serif);
    --sans: var(--latin-sans);
    --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

    /* Type scale. Nothing smaller than 14px anywhere. */
    --t-xs: 0.875rem;                                  /* 14: meta, captions, table heads, the legend */
    --t-s: 0.9375rem;                                  /* 15: UI, buttons, nav, labels, tabs, status marks */
    --t-m: 1.0625rem;                                  /* 17: body, ingredients, testers' notes */
    --t-l: 1.25rem;                                    /* 20 (19 on phones): step text, leads, card titles */
    --t-xl: clamp(1.5rem, 1.2rem + 1vw, 1.875rem);     /* 24-30: a card's or a form's heading */
    --t-head: clamp(1.75rem, 0.875rem + 2.2vw, 2.375rem); /* 28-38: section openers */
    --t-2xl: clamp(2.25rem, 1.5rem + 2.6vw, 3.75rem);  /* 36-60: page titles */
    --leading: 1.55;
    --leading-tight: 1.15;

    /* Space (4px base) */
    --s-1: 4px;
    --s-2: 8px;
    --s-3: 12px;
    --s-4: 16px;
    --s-5: 24px;
    --s-6: 32px;
    --s-7: 48px;
    --s-8: 72px;
    --s-9: 96px;

    /* Paper, card stock and ink. Paper is warm card stock (never cream
       and gold); index cards are pure white and lift off it by value
       (1.08:1) plus their 1px edge, with no shadow. */
    --paper: #FAF6EE;          /* the page; the masthead and the nav drawer */
    --card: #FFFFFF;           /* index card stock, inputs, menus */
    --paper-2: #EAF0F4;        /* one ruled line picked out: the announcement strip, the Test Kitchen's tab, a picked row */
    --paper-3: #F3EEE4;        /* the bottom of the box: footer ground, image placeholders, sunk areas */
    --card-edge: #CBC3B7;      /* 1px card edge (decorative): 1.6 paper, 1.7 card */
    --ink: #221E1A;            /* 15.4 / 16.6 / 14.4 / 14.3; 13.8 or more on every tint; 6.1 or more on every fill */
    --ink-2: #4E4842;          /* 8.4 / 9.0 / 7.8 / 7.8; 7.5 or more on every tint. Never on a fill (dinner 3.3) */
    --ink-3: #665F58;          /* 5.8 / 6.3 / 5.5 / 5.4; 5.2 or more on every tint: meta, captions; the faintest text */

    /* The pen (handwriting and the testers' figures), the red pencil, the
       warning. On the tints: pen 9.6, pen-2 6.1, red 5.2, ochre 5.3 or
       more. None of them is text on a fill. */
    --pen: #213A5C;            /* 10.7 / 11.5 / 10.0 / 10.0: the testers' hand and figures, the reader's own */
    --pen-2: #3D5878;          /* 6.8 / 7.3 / 6.4 / 6.3: the figures' glyphs, testers' meta and signatures */
    --red: #B4301F;            /* 5.8 / 6.2 / 5.4 / 5.4: red pencil: Needs work, errors, destructive, Hidden */
    --ochre: #8A5300;          /* 5.9 / 6.3 / 5.5 / 5.5: warnings only */

    /* Structure: lines that are never text */
    --card-red: #E2646A;       /* the 2px header line a heading sits on: 3.1 paper, 3.4 card, 2.8 on a tint (decorative) */
    --rule: #C2D6E8;           /* ruled blue: the 1px lines rows sit on (1.4 paper, 1.5 card) */
    --rule-2: #97B1C8;         /* darker ruled blue: link underlines, dashed boxes, the scale's line (2.1 / 2.2) */
    --hairline: #E3DDD3;       /* neutral hairline: the journal drawing's page edges (home.css; decorative) */
    --photo-edge: color-mix(in srgb, var(--ink) 12%, transparent);   /* photos are prints: an inset hairline */
    --hover: color-mix(in srgb, var(--ink) 6%, transparent);          /* an outline button or a menu row under the pointer, on any ground */

    /* The highlighter: four uses only, always behind --on-mark. Softened
       from acid yellow so it sits on warm paper (1.25 paper). */
    --mark: #F8DD6A;
    --on-mark: #221E1A;        /* 12.3; pen on it 8.5 */

    /* Buttons and controls */
    --button: #221E1A;         /* primary fill; --on-button on it 16.6 */
    --button-hover: #4E4842;   /* ink eases to ink-2 (the pen stays handwriting); --on-button on it 9.0 */
    --on-button: #FFFFFF;
    --control: #7F7871;        /* borders that must be seen: inputs, checkboxes, selects, clip: 4.0 / 4.4 / 3.8 / 3.8; 3.6 or more on every tint */
    --focus: #213A5C;          /* 3px ring, 2px offset: 10.7 / 11.5 / 10.0 / 10.0; 9.6 or more on a tint, 4.2 or more on a fill */
    --focus-width: 3px;

    /* Images can't read tokens: the select caret is drawn in --ink-2, and
       the tick knocked out of a pen or ink box in --card. */
    --caret: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%234E4842' stroke-width='1.5'/%3E%3C/svg%3E");
    --tick-mark: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M2.6 6.3l2.2 2.2 4.6-5' fill='none' stroke='%23FFFFFF' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");

    /* The eight dividers, turned up. Three colours each:
         --{slug}        the fill: a divider, a tab, a card's stripe, a
                         photo's mount. Ink on it (the ratio below); no
                         other text colour.
         --{slug}-tint   a pale wash of it: a section's ground, a page's
                         header. Every text token keeps its ratio on it
                         (see the tokens above).
         --{slug}-edge   a deeper shade: the fill's 1px outline and the
                         category's glyph. 4.3 or more on paper, 4.0 or
                         more on its tint (3:1 is the bar for graphics).
                         Never text.
       Measured apart from the colours with a job (CIE76): every fill is
       ΔE 24 or more from the red pencil, card red, pen, ochre and the
       highlighter, and the fills ΔE 25 or more from each other. Two edges
       sit near a job colour (breakfast near ochre, drinks near pen-2), so
       an edge only ever outlines its own fill or draws the glyph beside
       the word: never alone, never a status. */
    --breakfast: #F8B978;  --breakfast-tint: #FDE9D4;  --breakfast-edge: #9C631D;   /* apricot: ink 9.6, edge 4.6 paper / 4.2 tint */
    --lunch: #88D7B9;      --lunch-tint: #DCF3E9;      --lunch-edge: #318267;       /* mint: ink 9.8, edge 4.3 / 4.0 */
    --dinner: #EB8054;     --dinner-tint: #FDE6DB;     --dinner-edge: #A65A3B;      /* tomato: ink 6.1, edge 4.7 / 4.2 */
    --sides: #B4D58A;      --sides-tint: #E8F3DA;      --sides-edge: #607D35;       /* leaf green: ink 10.1, edge 4.3 / 4.1 */
    --snacks: #C1B3EF;     --snacks-tint: #EDE8FD;     --snacks-edge: #75679E;      /* lavender: ink 8.6, edge 4.6 / 4.2 */
    --dessert: #EFA3C7;    --dessert-tint: #FCE4EF;    --dessert-edge: #9D587A;     /* rose: ink 8.5, edge 4.7 / 4.3 */
    --baking: #E3CEAD;     --baking-tint: #F8EBD7;     --baking-edge: #826E50;      /* oat: ink 10.8, edge 4.5 / 4.2 */
    --drinks: #84BFE9;     --drinks-tint: #DDEFFB;     --drinks-edge: #3F789E;      /* sky: ink 8.4, edge 4.4 / 4.1 */
    /* "All", and anything not filed behind a category: plain card stock
       with a card edge darkened to 3.3 on paper. */
    --cat-all-fill: #FFFFFF;   /* ink 16.6 */
    --cat-all-edge: #8F877C;   /* 3.3 paper */

    /* Illustration only (never text, never interface): the teal enamel
       tin the dividers stand in, and the founder's blue journal. Card
       stock on the tin 7.1 (the tin's label); every fill on the tin 2.6
       or more (decorative). */
    --tin: #1F6260;
    --tin-edge: #124645;       /* its rolled rim */
    --journal: #284D83;        /* the cloth cover */
    --journal-spine: #1C3C6B;
    --journal-band: #142D53;   /* the elastic band */
    --journal-tint: #DFECFA;   /* "Our story"'s ground: ink 13.8, ink-2 7.5, ink-3 5.2, pen 9.6, pen-2 6.1, red 5.2, ochre 5.3, control 3.6 */

    /* Shape */
    --corner: 4px;             /* cards, controls and buttons */
    --photo-corner: 2px;       /* a photo inside a card or a mount */
    --tab-corner: 6px;         /* a divider tab's top corners, and nothing else */
    --head-line: 2px;          /* the card-red header line under a heading */
    --stripe: 6px;             /* the category's stripe along a card's top edge */
    --card-pad: clamp(16px, 1.8vw, 24px);

    /* The ground an element sits on: paper, card stock inside an index
       card, or a section's tint. Halos and knocked-out shapes read it
       (an empty tally box). */
    --ground: var(--paper);

    /* Layout */
    --container: 1240px;
    --container-narrow: 820px;
    --gutter: clamp(16px, 4vw, 48px);
    --gap: clamp(16px, 2.4vw, 32px);
    --section: clamp(56px, 7vw, 104px);
    --measure: 62ch;
    --masthead-h: 76px;

    /* Motion: colour, underline and a tab's or a divider's rise only */
    --ease: cubic-bezier(0.2, 0.7, 0.2, 1);
    --dur: 140ms;
}

/* The category classes. Templates put .cat-{slug} (the catClass func) on
   anything filed behind a category: a recipe card, a divider, a filter
   tab, the recipe page, the catWord func's word. It sets three variables
   for whatever is inside to read:
     --c        the fill     --c-tint   the tint     --c-edge   the edge
   and, under their v0.8 names, the same three for the divider tabs and
   the swatch (--tab-swatch, --tab-fill, --tab-edge). Slugs: breakfast,
   lunch, dinner, sides, snacks, dessert, baking, drinks. The defaults
   (the All tab, a card or a word with no category) are in :where(), so
   any .cat-* class wins whatever its order. Only elements that carry their
   own .cat-* class take the defaults: a recipe card's tab, or an index
   card inside a recipe page filed behind dinner, inherits its colours from
   the card or the page, and falls back in var() when there is none. */
:where(.cat, .pill, .divider-tab, .divider) {
    --c: var(--cat-all-fill);
    --c-tint: var(--paper-3);
    --c-edge: var(--cat-all-edge);
    --tab-swatch: var(--cat-all-fill);
    --tab-fill: var(--cat-all-fill);
    --tab-edge: var(--cat-all-edge);
}

.cat-breakfast { --c: var(--breakfast); --c-tint: var(--breakfast-tint); --c-edge: var(--breakfast-edge); }
.cat-lunch     { --c: var(--lunch);     --c-tint: var(--lunch-tint);     --c-edge: var(--lunch-edge); }
.cat-dinner    { --c: var(--dinner);    --c-tint: var(--dinner-tint);    --c-edge: var(--dinner-edge); }
.cat-sides     { --c: var(--sides);     --c-tint: var(--sides-tint);     --c-edge: var(--sides-edge); }
.cat-snacks    { --c: var(--snacks);    --c-tint: var(--snacks-tint);    --c-edge: var(--snacks-edge); }
.cat-dessert   { --c: var(--dessert);   --c-tint: var(--dessert-tint);   --c-edge: var(--dessert-edge); }
.cat-baking    { --c: var(--baking);    --c-tint: var(--baking-tint);    --c-edge: var(--baking-edge); }
.cat-drinks    { --c: var(--drinks);    --c-tint: var(--drinks-tint);    --c-edge: var(--drinks-edge); }

/* The Test Kitchen is filed behind its own tab: the pen's pale blue (a
   picked-out ruled line), not a category colour. */
.tab-kitchen   { --c: var(--paper-2); --c-tint: var(--paper-2); --c-edge: var(--rule-2); }

:is(.cat-breakfast, .cat-lunch, .cat-dinner, .cat-sides, .cat-snacks, .cat-dessert, .cat-baking, .cat-drinks, .tab-kitchen) {
    --tab-swatch: var(--c);
    --tab-fill: var(--c);
    --tab-edge: var(--c-edge);
}

/* Section grounds: a full-width section on a soft wash of one category's
   tint (or the journal's, for "Our story"). The wash is the section's
   ground, so --ground follows it; cards on it stay white. At most one
   wash per section and never two of the same side by side; text on a
   wash is ink, ink-2, ink-3, pen, pen-2, red or ochre (all 4.5:1 or more,
   above). .ground-cat washes in the tint of the category the element (or
   an ancestor) is filed behind: the recipe page's header. */
.ground-breakfast { --ground: var(--breakfast-tint); background: var(--breakfast-tint); }
.ground-lunch     { --ground: var(--lunch-tint);     background: var(--lunch-tint); }
.ground-dinner    { --ground: var(--dinner-tint);    background: var(--dinner-tint); }
.ground-sides     { --ground: var(--sides-tint);     background: var(--sides-tint); }
.ground-snacks    { --ground: var(--snacks-tint);    background: var(--snacks-tint); }
.ground-dessert   { --ground: var(--dessert-tint);   background: var(--dessert-tint); }
.ground-baking    { --ground: var(--baking-tint);    background: var(--baking-tint); }
.ground-drinks    { --ground: var(--drinks-tint);    background: var(--drinks-tint); }
.ground-journal   { --ground: var(--journal-tint);   background: var(--journal-tint); }
.ground-cat       { --ground: var(--c-tint, var(--paper-3)); background: var(--c-tint, var(--paper-3)); }

/* 03  Base & typography --------------------------------------------------- */
*,
*::before,
*::after {
    box-sizing: border-box;
}

/* Besley ships upright only: a leftover font-style: italic renders upright
   instead of a slanted fake. Source Sans keeps its real italic. Only a
   line's closing stop may hang (Safari): hanging an opening bracket or a
   quote ("first", "last") pushed text past the rules and out of line with
   the rows around it. Japanese hangs nothing (section 17). */
html {
    -webkit-text-size-adjust: 100%;
    text-size-adjust: 100%;
    hanging-punctuation: allow-end;
    scroll-padding-top: var(--s-5);
    font-synthesis-style: none;
}

body {
    margin: 0;
    min-height: 100vh;
    background: var(--paper);
    color: var(--ink);
    font-family: var(--sans);
    font-size: var(--t-m);
    line-height: var(--leading);
    font-weight: 400;
    font-kerning: normal;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    overflow-wrap: break-word;
}

h1, h2, h3, h4 {
    margin: 0;
    font-family: var(--serif);
    font-weight: 600;
    line-height: var(--leading-tight);
    font-variant-numeric: lining-nums proportional-nums;
    color: var(--ink);
    text-wrap: balance;
}

/* The two big sizes are tightened a hair; section 17 undoes it for
   Arabic and Japanese. */
h1 { font-size: var(--t-2xl); letter-spacing: -0.012em; }
h2 { font-size: var(--t-xl); letter-spacing: -0.006em; }
h3 { font-size: var(--t-l); }
h4 { font-size: var(--t-m); }

p, ul, ol, dl, dd, figure, blockquote, fieldset {
    margin: 0;
}

p, li, dd, figcaption {
    text-wrap: pretty;
}

ul, ol {
    padding: 0;
}

img, svg, video {
    display: block;
    max-width: 100%;
}

img {
    height: auto;
}

/* Links: underlined in a darker ruled blue that turns to the text's own
   colour on hover. */
a {
    color: inherit;
    text-decoration-line: underline;
    text-decoration-thickness: 1px;
    text-decoration-color: var(--rule-2);
    text-underline-offset: 0.22em;
    transition: text-decoration-color var(--dur) var(--ease), color var(--dur) var(--ease);
}

a:hover {
    text-decoration-color: currentColor;
}

strong, b {
    font-weight: 700;
}

em, i, cite {
    font-style: normal;
}

em {
    font-weight: 600;
}

button, input, select, textarea {
    font: inherit;
    color: inherit;
}

button {
    cursor: pointer;
}

/* The focus ring is the pen's blue-black: 3:1 or more on paper, card
   stock, every tab fill and the highlighter's neighbours. It is the pen's
   one job outside handwriting: a ring is never text, and a blue ring is
   what keyboard users look for. Components inside a scroller draw it
   inset (the tabs). */
:focus-visible {
    outline: var(--focus-width) solid var(--focus);
    outline-offset: 2px;
}

::selection {
    background: color-mix(in srgb, var(--pen) 22%, var(--card));
    color: var(--ink);
}

hr {
    border: 0;
    border-top: 1px solid var(--rule);
    margin: var(--s-6) 0;
}

blockquote {
    white-space: pre-line;
}

/* Every figure lines up: scores, times, counts, quantities, tables. */
.num,
time,
td,
th,
dd {
    font-variant-numeric: tabular-nums lining-nums;
}

/* Besley's tabular figures don't follow its weight axis: a 0 or a 1 comes
   out thin beside bold digits ("5.0", "100%", step 10). Every figure set in
   Besley uses its proportional lining figures instead (headings, readouts,
   step numbers, the log's averages); Source Sans keeps tabular ones. */
.logo-name,
.footer-name {
    font-variant-numeric: lining-nums proportional-nums;
}

/* The highlighter, for the four uses in BRAND.md only: annotated step
   numerals, the pass zone, the Proven row of the log (the Proven moment),
   admin counts that need a human. Always behind --on-mark text; flat, no
   rounded corners. */
mark {
    padding-inline: 0.12em;
    background: var(--mark);
    color: var(--on-mark);
    -webkit-box-decoration-break: clone;
    box-decoration-break: clone;
}

/* The Proven moment (the proven_moment partial): "Proven after cook 3" on
   the highlighter in bold, kept whole. */
.moment-mark {
    padding: 3px 0.35em;
    font-weight: 700;
    white-space: nowrap;
}

.lead {
    font-size: var(--t-l);
    line-height: 1.5;
    color: var(--ink-2);
    max-width: 46ch;
}

.muted { color: var(--ink-3); }

.visually-hidden {
    position: absolute !important;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

.skip-link {
    position: absolute;
    inset-inline-start: var(--s-3);
    top: -80px;
    z-index: 100;
    padding: 10px 16px;
    border-radius: var(--corner);
    background: var(--button);
    color: var(--on-button);
    font-weight: 700;
    text-decoration: none;
}

.skip-link:focus {
    top: var(--s-3);
}

main:focus {
    outline: none;
}

/* The index card: white stock, a 1px edge, 4px corners, no shadow, for
   things that ARE cards (a recipe's test record, ingredients and method,
   the cook-report sheet, a block beside a page's head). Recipe cards in a
   grid are their own component (section 09). .striped adds the stripe of
   the category the card is filed behind (.cat-{slug} on it or an
   ancestor: the recipe page) along its top edge, as on the recipe cards.
   Its parts run edge to edge, as the lines on real card stock do:
     .index-card-head   the title, written above the red header line
                        (an h2/h3 with optional meta beside it)
     .index-card-line   a row written on a ruled blue line (the last
                        card line drops its rule; the card's edge closes it)
     .index-card-bleed  anything else that runs to the edges (a table, a
                        list); give its first and last cells
                        padding-inline: var(--card-pad)
   Inside a card --ground is card stock, so halos and empty boxes match. */
.index-card {
    --ground: var(--card);
    padding: var(--card-pad);
    border: 1px solid var(--card-edge);
    border-radius: var(--corner);
    background: var(--card);
}

.index-card.striped {
    border-top: var(--stripe) solid var(--c, var(--card-edge));
}

.index-card-head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--s-1) var(--s-4);
    margin-inline: calc(-1 * var(--card-pad));
    padding: 0 var(--card-pad) 10px;
    border-bottom: var(--head-line) solid var(--card-red);
}

/* A head at the top of the card takes the card's top padding itself, so
   the red line sits the same distance under every card's title. */
.index-card > .index-card-head:first-child {
    margin-top: calc(-1 * var(--card-pad));
    padding-top: var(--s-4);
}

.index-card-head :is(h2, h3) {
    font-size: clamp(1.25rem, 1rem + 0.8vw, 1.625rem);   /* 20-26 */
}

.index-card-line,
.index-card-bleed {
    margin-inline: calc(-1 * var(--card-pad));
}

.index-card-line {
    padding: 10px var(--card-pad);
    border-bottom: 1px solid var(--rule);
}

.index-card-line:last-child {
    margin-bottom: calc(-1 * var(--card-pad));
    border-bottom: 0;
}

/* Ruled text: handwritten text only (testers' step notes, the author's
   note), never leads, descriptions or verdicts. The lines are a background
   at the text's own line pitch, so each wrapped line sits on one; the
   pitch is a variable that sets the line-height too, so they can't drift
   apart. Set --ruled-pitch (a length) to change both. No vertical padding
   on the element: the lines start at the top of its content box.
   Arabic and Japanese drop the lines (section 17): their leading is looser
   and their marks cross a rule. */
.ruled {
    --ruled-pitch: 1.65em;
    line-height: var(--ruled-pitch);
    background-image: linear-gradient(to bottom, transparent calc(var(--ruled-pitch) - 1px), var(--rule) calc(var(--ruled-pitch) - 1px));
    background-size: 100% var(--ruled-pitch);
    background-origin: content-box;
    background-repeat: repeat-y;
}

/* Photos are prints: a 12% ink hairline inset, so pale food on white card
   stock still has an edge. Surfaces add their own photo classes to this
   list's pattern (outline: 1px solid var(--photo-edge); outline-offset:
   -1px). */
.recipe-card-photo img {
    outline: 1px solid var(--photo-edge);
    outline-offset: -1px;
}

/* A photo mounted on its category's fill, as a print is mounted on card:
   12px of the fill round it, a 1px edge in the category's edge, the
   photo's own 2px corners (the recipe page's photo, a lead pick). The
   category comes from .cat-{slug} on it or an ancestor; with none it is
   mounted on the bottom of the box (--paper-3). */
.photo-mount {
    padding: 12px;
    border: 1px solid var(--c-edge, var(--card-edge));
    border-radius: var(--corner);
    background: var(--c, var(--paper-3));
}

.photo-mount img {
    display: block;
    width: 100%;
    border: 1px solid var(--c-edge, var(--card-edge));
    border-radius: var(--photo-corner);
}

/* The category word: a little divider tab in the category's fill and
   edge, then the word (the catWord func: "Dinner · by Ana" lines, the
   results summary). The tab is decorative: the word always carries the
   meaning. */
.cat {
    white-space: nowrap;
}

/* The words after the first (catWord puts them in .cat-rest) may wrap, so
   "Accompagnements et salades" folds on a phone instead of overflowing it;
   the swatch stays with the first word. */
.cat-rest {
    white-space: normal;
}

.cat::before {
    content: "";
    display: inline-block;
    width: 14px;
    height: 10px;
    margin-inline-end: 6px;
    border: 1px solid var(--tab-edge);
    border-bottom: 0;
    border-radius: 3px 3px 0 0;
    background: var(--tab-swatch);
    vertical-align: 0;
}

/* 04  Layout primitives ---------------------------------------------------- */
.container {
    width: 100%;
    max-width: calc(var(--container) + 2 * var(--gutter));
    margin-inline: auto;
    padding-inline: var(--gutter);
}

.container.narrow {
    max-width: calc(var(--container-narrow) + 2 * var(--gutter));
}

/* A section of a page. On a wash, add a .ground-* class (section 02):
   the section runs full width and its .container keeps the content in
   line with the rest of the page. */
.section {
    padding-block: var(--section);
}

/* The section opener, the ONLY section header: the heading (Besley 600,
   28-38px, sentence case) written above a 2px card-red line, the way you
   title an index card, with optional meta, a legend or a link at the end,
   on its foot. The section's own padding spaces it from what comes
   before. On a wash the red line stays (2.8:1 on a tint: it is structure,
   not text). */
.section-head {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    justify-content: space-between;
    gap: var(--s-2) var(--s-5);
    padding-bottom: var(--s-3);
    margin-bottom: clamp(28px, 3.5vw, 44px);
    border-bottom: var(--head-line) solid var(--card-red);
}

.section-title {
    font-size: var(--t-head);
    line-height: 1.1;
    letter-spacing: -0.01em;
}

.section-meta,
.section-link {
    font-size: var(--t-s);
    font-weight: 400;
    color: var(--ink-2);
}

/* The link at the end of an opener: the line is the red one under it, so
   the link keeps a plain height (a 44px box would push the line down). */
a.section-link {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    min-height: 32px;
    color: var(--ink);
    font-weight: 600;
}

.page-head {
    padding-block: clamp(32px, 5vw, 56px) clamp(24px, 3vw, 32px);
}

/* A page title sits on the red line too: the page is titled like a card. */
.page-head h1,
.page-title {
    padding-bottom: var(--s-3);
    border-bottom: var(--head-line) solid var(--card-red);
}

.page-head h1 + .lead,
.page-head .lead {
    margin-top: var(--s-4);
}

.page-head-row {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    justify-content: space-between;
    gap: var(--s-5) var(--s-6);
}

/* A single-column page for a form (share, cook report, report content). */
.form-page {
    padding-block: clamp(28px, 5vw, 56px) var(--section);
}

.form-page > .container > h1,
.form-page .page-title {
    margin-top: var(--s-2);
}

.form-page > .container > .lead,
.form-page .page-title + .lead {
    margin-top: var(--s-4);
    margin-bottom: clamp(24px, 3vw, 32px);
}

.form-page > .container > h2 {
    margin: clamp(32px, 4vw, 48px) 0 var(--s-3);
    font-size: var(--t-xl);
}

.form-page > .container > p + p {
    margin-top: var(--s-3);
}

.back-link {
    display: flex;
    width: fit-content;
    margin-bottom: var(--s-3);
    align-items: center;
    gap: 6px;
    min-height: 44px;
    font-size: var(--t-s);
    font-weight: 600;
    color: var(--ink-2);
}

.back-link:hover {
    color: var(--ink);
}

/* 05  Masthead, navigation, menus, site banners ---------------------------
   The masthead is the top of the card: paper, the seal and word mark, the
   nav (words, then the one ink button: Join, or Share a recipe for a
   member), the language (globe, code, caret) and account menus, and the
   2px card-red header line under it all. Below 980px the nav folds behind
   a Menu button (section 15). */
.masthead {
    position: relative;
    z-index: 50;
    background: var(--paper);
    border-bottom: var(--head-line) solid var(--card-red);
}

.masthead-inner {
    position: relative;
    display: flex;
    align-items: center;
    gap: var(--s-5);
    min-height: var(--masthead-h);
}

.logo {
    display: inline-flex;
    align-items: center;
    gap: var(--s-3);
    flex: none;
    padding-block: var(--s-2);
    color: var(--ink);
    text-decoration: none;
}

/* The seal is an image (BRAND.md), the white card stock disc; the word
   mark beside it is live text. It stays latin and left to right in every
   language; in right-to-left pages the seal sits on the right. Section 15
   shrinks it on phones. */
.logo-mark {
    display: block;
    flex: none;
    width: 44px;
    height: 44px;
}

.logo-word {
    display: grid;
    gap: 4px;
    direction: ltr;
    line-height: 1;
    white-space: nowrap;
}

.logo-the {
    font-family: var(--latin-sans);
    font-size: var(--t-s);
    font-weight: 600;
    color: var(--ink-2);
}

.logo-name {
    font-family: var(--latin-serif);
    font-size: 1.625rem;
    font-weight: 700;
    letter-spacing: -0.01em;
    color: var(--ink);
}

.nav {
    display: flex;
    align-items: center;
    gap: var(--s-1);
    margin-inline-start: auto;
}

/* Nav words: the current page is underlined in ink across the word's
   padded box; hover draws a ruled blue line there. */
.nav-link {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    padding-inline: 10px;
    border-block: 2px solid transparent;
    color: var(--ink);
    font-size: 1rem;
    font-weight: 600;
    text-decoration: none;
    white-space: nowrap;
}

.nav-link:hover {
    border-bottom-color: var(--rule-2);
}

.nav-link[aria-current="page"] {
    border-bottom-color: var(--ink);
}

/* The one ink button in the masthead: Join, or Share a recipe. On its own
   page it keeps its ink fill (a greyed button reads as disabled) and
   takes the nav words' current-page mark: a 2px ink line under it, here
   set 4px below the button. */
.nav-cta {
    position: relative;
    min-height: 40px;
    margin-inline: 6px;
}

.nav-cta[aria-current="page"]::after {
    content: "";
    position: absolute;
    inset-inline: -1.5px;
    bottom: -7.5px;
    height: 2px;
    background: var(--ink);
}

.masthead-tools {
    display: flex;
    align-items: center;
    gap: var(--s-1);
}

/* The phone menu is a CSS-only checkbox (#nav-toggle). On wide screens the
   checkbox is removed so it is never a tab stop; on phones it is visually
   hidden but focusable, and its "Menu" label shows the focus ring. */
.nav-toggle {
    display: none;
}

.nav-burger {
    display: none;
    align-items: center;
    justify-content: center;
    gap: 6px;
    min-height: 44px;
    min-width: 44px;
    padding: 0 12px;
    border: 1.5px solid var(--ink);
    border-radius: var(--corner);
    background: transparent;
    color: var(--ink);
    font-size: var(--t-s);
    font-weight: 600;
    white-space: nowrap;
    cursor: pointer;
}

.nav-burger:hover {
    background: var(--hover);
}

.nav-burger-close,
.nav-toggle:checked ~ .masthead-tools .nav-burger-open {
    display: none;
}

.nav-toggle:checked ~ .masthead-tools .nav-burger-close {
    display: inline;
}

/* Its glyphs (three lines; a cross when open) stand in for the words only
   on the smallest phones (section 15). */
.nav-burger .icon {
    display: none;
    width: 22px;
    height: 22px;
}

.nav-toggle:focus-visible ~ .masthead-tools .nav-burger {
    outline: var(--focus-width) solid var(--focus);
    outline-offset: 2px;
}

/* Disclosure menus (language, account, collections): <details>, so they
   work without JavaScript. app.js closes them on Escape or a click
   outside, one at a time. The panel is the one floating thing on the
   site: a slip of card stock with a 1px control edge and the red header
   line on top, no shadow; its items sit on ruled blue lines. */
.menu {
    position: relative;
}

.menu > summary {
    list-style: none;
    cursor: pointer;
}

.menu > summary::-webkit-details-marker {
    display: none;
}

.lang-summary,
.user-summary {
    display: flex;
    align-items: center;
    gap: 6px;
    min-height: 44px;
    padding-inline: 10px 8px;
    border: 1px solid transparent;
    border-radius: var(--corner);
    color: var(--ink);
    font-size: var(--t-s);
    font-weight: 600;
    white-space: nowrap;
}

.lang-summary:hover,
.user-summary:hover,
.menu[open] > .lang-summary,
.menu[open] > .user-summary {
    background: var(--hover);
}

/* The language menu's globe, before the code ("EN"). */
.lang-globe {
    width: 18px;
    height: 18px;
    color: var(--ink-2);
}

.user-summary .icon-user {
    display: none;
}

/* A rem width, not ch: the Arabic and Japanese fallbacks have narrower
   zeros, so 16ch cut "Hannah Lindqvist" short on /ar/. */
.user-name {
    max-width: 12rem;
    overflow: hidden;
    text-overflow: ellipsis;
}

.lang-code {
    min-width: 2ch;
    font-family: var(--latin-sans);
    font-size: var(--t-s);
    font-weight: 600;
    line-height: 1;
}

.menu-caret {
    width: 14px;
    height: 14px;
    color: var(--ink-2);
    transition: transform var(--dur) var(--ease);
}

.menu[open] .menu-caret,
details[open] > summary .menu-caret {
    transform: rotate(180deg);
}

.menu-panel {
    position: absolute;
    inset-inline-end: 0;
    top: calc(100% + 6px);
    z-index: 60;
    min-width: 15rem;
    padding-block: var(--s-1);
    border: 1px solid var(--control);
    border-top: var(--head-line) solid var(--card-red);
    border-radius: 0 0 var(--corner) var(--corner);
    background: var(--card);
}

.menu-panel-left {
    inset-inline-start: 0;
    inset-inline-end: auto;
}

/* The masthead's two menus hang from the masthead's own red line: the
   panel's red top edge lands on it (its summary is 44px, centred in the
   --masthead-h row), so there is one red line, not two parallel ones. */
.masthead-tools .menu-panel {
    top: calc(100% + (var(--masthead-h) - 44px) / 2);
}

.menu-head {
    padding: var(--s-2) var(--s-4) var(--s-3);
    border-bottom: 1px solid var(--rule);
    font-size: var(--t-s);
    line-height: 1.35;
}

.menu-head strong {
    display: block;
    overflow-wrap: anywhere;
}

.menu-head span {
    font-size: var(--t-xs);
    color: var(--ink-3);
}

.menu-item {
    display: flex;
    align-items: center;
    gap: var(--s-3);
    width: 100%;
    min-height: 44px;
    padding: var(--s-2) var(--s-4);
    border: 0;
    border-bottom: 1px solid var(--rule);
    border-radius: 0;
    background: none;
    color: var(--ink);
    font-size: var(--t-s);
    font-weight: 400;
    text-align: start;
    text-decoration: none;
}

/* The last item (or one closing a group) has no line under it: the
   panel's edge, or the separator, closes it. */
.menu-item:last-child,
.menu-item:has(+ .menu-sep),
form:last-child > .menu-item {
    border-bottom: 0;
}

.menu-item:hover {
    background: var(--paper-2);
}

.menu-item .icon {
    width: 16px;
    height: 16px;
    color: var(--ink-2);
}

.menu-item[aria-current="page"],
.menu-item[aria-current="true"] {
    font-weight: 700;
}

/* A group break: an ink hairline, stronger than the ruled lines. */
.menu-sep {
    height: 1px;
    background: var(--ink-3);
}

/* Language menu: each item is written in its own language (lang="..."),
   so section 17 gives it the right script font. The current one is bold
   and carries a check; the others are indented to line up with it. */
.lang-menu .menu-panel {
    min-width: 12rem;
}

.lang-menu .menu-item:not([aria-current="true"]) {
    padding-inline-start: calc(var(--s-4) + 16px + var(--s-3));
}

.lang-menu .menu-item[aria-current="true"] .icon {
    color: var(--ink);
}

/* Site banners (announcement, email confirmation, flash): one short line
   under the masthead, full width, ruled off in blue. The announcement is a
   picked-out line (the paper-2 strip) with an ink-2 glyph and no bar; the
   others keep a 3px bar at the start (ink for news and success, ochre for
   a warning, red pencil for an error), a glyph and a sentence, so none of
   them rely on colour. One sentence, then at most one link. */
.site-banner {
    border-bottom: 1px solid var(--rule);
    background: var(--paper);
    color: var(--ink);
    font-size: var(--t-s);
}

.site-banner .container {
    padding-block: var(--s-3);
}

.banner-line {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding-inline-start: var(--s-3);
    border-inline-start: 3px solid var(--ink);
    line-height: 1.45;
}

.banner-line > .icon {
    flex: none;
    width: 18px;
    height: 18px;
    margin-top: 0.1em;
}

.site-banner a {
    color: inherit;
    font-weight: 600;
}

.announcement {
    background: var(--paper-2);
}

.announcement .container {
    padding-block: 10px;
}

.announcement .banner-line {
    padding-inline-start: 0;
    border-inline-start: 0;
}

.announcement .banner-line > .icon {
    color: var(--ink-2);
}

.verify-banner .banner-line {
    border-inline-start-color: var(--ochre);
}

.verify-banner .banner-line > .icon {
    color: var(--ochre);
}

.flash {
    font-weight: 600;
}

/* A flash that reports a failure or a refusal: red pencil words, bar and
   glyph. */
.flash-error {
    color: var(--red);
}

.flash-error .banner-line {
    border-inline-start-color: var(--red);
}

/* 06  Icons & buttons ------------------------------------------------------ */
.icon {
    display: inline-block;
    flex: none;
    width: 1.125em;
    height: 1.125em;
    vertical-align: -0.2em;
}

.icon-sm { width: 1em; height: 1em; vertical-align: -0.15em; }

/* Buttons: ink, or an ink outline, 4px corners, never a capsule. Primary:
   an ink fill that eases to ink-2 on hover (never the pen, which is
   handwriting), at most one per view region. Secondary (and a bare .btn):
   a 1.5px ink outline on whatever ground it sits on, paper, card or a
   wash. Quiet: underlined words with an optional 16px glyph (Print,
   Download, Report); .btn-ghost is the admin's name for it. Danger: a
   red-pencil outline and words. Hover on the outlined ones lays a 6% ink
   tint over their ground. Labels: three words at most. */
.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--s-2);
    min-height: 44px;
    padding: 0 18px;
    border: 1.5px solid var(--ink);
    border-radius: var(--corner);
    background: transparent;
    color: var(--ink);
    font-family: var(--sans);
    font-size: var(--t-s);
    font-weight: 600;
    line-height: 1.2;
    text-align: center;
    text-decoration: none;
    white-space: nowrap;
    cursor: pointer;
    transition: background-color var(--dur) var(--ease), border-color var(--dur) var(--ease), color var(--dur) var(--ease), text-decoration-color var(--dur) var(--ease);
}

.btn:hover {
    background: var(--hover);
}

.btn .icon {
    width: 18px;
    height: 18px;
}

.btn:disabled,
.btn[aria-disabled="true"],
form.htmx-request .btn[type="submit"] {
    opacity: 0.5;
    cursor: not-allowed;
}

.btn-primary {
    background: var(--button);
    border-color: var(--button);
    color: var(--on-button);
}

.btn-primary:hover {
    background: var(--button-hover);
    border-color: var(--button-hover);
    color: var(--on-button);
}

.btn-secondary {
    background: transparent;
    border-color: var(--ink);
    color: var(--ink);
}

.btn-secondary:hover {
    background: var(--hover);
    color: var(--ink);
}

.btn-quiet,
.btn-ghost {
    padding-inline: var(--s-2);
    border-color: transparent;
    background: transparent;
    color: var(--ink);
    text-decoration: underline;
    text-decoration-thickness: 1px;
    text-decoration-color: var(--rule-2);
    text-underline-offset: 0.25em;
}

.btn-quiet:hover,
.btn-ghost:hover {
    background: transparent;
    text-decoration-color: currentColor;
}

.btn-quiet .icon,
.btn-ghost .icon {
    width: 16px;
    height: 16px;
}

.btn-danger {
    background: transparent;
    border-color: var(--red);
    color: var(--red);
}

.btn-danger:hover {
    background: var(--hover);
    color: var(--red);
}

.btn-lg {
    min-height: 48px;
    padding: 0 24px;
    font-size: var(--t-m);
}

/* Small buttons are 40px on fine pointers only (see section 15). */
.btn-sm {
    min-height: 40px;
    padding: 0 12px;
    gap: 6px;
}

.btn-block {
    display: flex;
    width: 100%;
}

/* Toggle state (Clip on the recipe page, filters): aria-pressed="true".
   The state shows in the words and the glyph too, never in colour alone. */
.btn[aria-pressed="true"] {
    background: var(--paper-2);
    border-color: var(--ink);
    color: var(--ink);
    font-weight: 700;
}

/* A link with an arrow ("Read our story", "See all 17"): 16px words on a
   ruled blue underline that turns ink on hover, the arrow after them
   (pointing the other way right to left). */
.link-arrow {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    min-height: 32px;
    color: var(--ink);
    font-size: 1rem;
    font-weight: 600;
    text-decoration: none;
    box-shadow: inset 0 -1px 0 var(--rule-2);
}

.link-arrow:hover {
    box-shadow: inset 0 -1px 0 var(--ink);
}

.link-arrow .icon {
    width: 18px;
    height: 18px;
}

/* A button that reads as a link (log out inside forms, inline actions) */
.link-button {
    display: inline;
    padding: 0;
    border: 0;
    background: none;
    color: inherit;
    font: inherit;
    font-weight: 600;
    text-decoration: underline;
    text-decoration-thickness: 1px;
    text-decoration-color: var(--rule-2);
    text-underline-offset: 0.2em;
    cursor: pointer;
}

.link-button:hover {
    text-decoration-color: currentColor;
}

/* The clip toggle: a labelled outline button, glyph + "Clip" / "Clipped",
   44px tall, aria-pressed, on card stock. On a recipe card it is a 44px
   square of card stock on the photo's corner, its word read, not shown
   (section 09). Clipped shows a filled glyph, the word and an ink edge on
   the pale ruled blue; hover darkens the edge to ink (never the pen: the
   pen is handwriting). The highlighter is not used for it. */
.clip {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    min-height: 44px;
    min-width: 44px;
    padding: 0 12px;
    border: 1px solid var(--control);   /* 3:1 or more: glyph-only on a card, the border says "button" */
    border-radius: var(--corner);
    background: var(--card);
    color: var(--ink);
    font-size: var(--t-s);
    font-weight: 600;
    line-height: 1.2;
    text-decoration: none;
    white-space: nowrap;
    transition: border-color var(--dur) var(--ease), background-color var(--dur) var(--ease);
}

.clip:hover {
    border-color: var(--ink);
}

.clip .icon {
    width: 18px;
    height: 18px;
}

.clip[aria-pressed="true"] {
    border-color: var(--ink);
    background: var(--paper-2);
}

.clip-toggle {
    margin: 0;
}

/* 07  Form controls -------------------------------------------------------- */
.field {
    display: grid;
    gap: 6px;
    min-width: 0;
}

/* Labels above the control; help text under the label; errors under the
   control. */
.label,
.field > label:not(.check) {
    font-size: var(--t-s);
    font-weight: 600;
    color: var(--ink);
}

.hint {
    font-size: var(--t-xs);
    line-height: 1.45;
    color: var(--ink-3);
}

.optional {
    font-weight: 400;
    color: var(--ink-3);
}

/* Text inputs: white card stock inside a --control edge (3:1 or more on
   paper and on a card). A ruled line never stands in for an input's
   border. .input/.select/.textarea are the component names; inside .field
   and .comment-form, bare controls get the same look. */
.input,
.select,
.textarea,
.field :is(input:not([type]), input[type="text"], input[type="email"], input[type="password"], input[type="search"], input[type="url"], input[type="number"], select, textarea),
.comment-form textarea {
    width: 100%;
    min-height: 48px;
    padding: 10px 14px;
    border: 1px solid var(--control);
    border-radius: var(--corner);
    background-color: var(--card);
    color: var(--ink);
    font-size: var(--t-m);
    line-height: 1.4;
    transition: border-color var(--dur) var(--ease);
}

.textarea,
.field textarea,
.comment-form textarea {
    min-height: 120px;
    resize: vertical;
}

.select,
.field select {
    appearance: none;
    padding-inline-end: 40px;
    background-image: var(--caret);
    background-position: right 15px center;
    background-size: 10px 6px;
    background-repeat: no-repeat;
    cursor: pointer;
}

/* The caret sits at the end edge: the left in right-to-left pages. */
[dir="rtl"] :is(.select, .field select) {
    background-position: left 15px center;
}

:is(.input, .select, .textarea):hover,
.field :is(input, select, textarea):hover,
.comment-form textarea:hover {
    border-color: var(--ink-2);
}

:is(.input, .select, .textarea):focus,
.field :is(input, select, textarea):focus,
.comment-form textarea:focus {
    border-color: var(--ink);
}

:is(.input, .textarea)::placeholder,
:is(.field, .comment-form) :is(input, textarea)::placeholder {
    color: var(--ink-3);
    opacity: 1;
}

/* A field's error, under the control, in red pencil; the control's border
   turns red too. The square keeps it recognisable without colour, and the
   words say what's wrong. */
.field-error {
    display: flex;
    align-items: flex-start;
    gap: 6px;
    color: var(--red);
    font-size: var(--t-xs);
    font-weight: 600;
    line-height: 1.4;
}

/* An error line kept on the page, empty, for a script to fill. */
.field-error[hidden] {
    display: none;
}

.field-error::before {
    content: "";
    flex: none;
    width: 7px;
    height: 7px;
    margin-top: 0.45em;
    background: currentColor;
}

/* A control marked invalid. The bare attribute (0,1,0) lost to the bare
   controls in .field (0,1,1), so a select marked invalid with no
   .field-error beside it kept its --control edge. The second selector is
   0,2,1, which ties the hover and focus rules above and comes after them,
   so neither turns the red back to ink. */
[aria-invalid="true"],
:is(.input, .select, .textarea, .field :is(input, select, textarea), .comment-form textarea)[aria-invalid="true"],
.field:has(.field-error:not([hidden])) :is(input, select, textarea) {
    border-color: var(--red);
}

/* File inputs: the picker button looks like a secondary button. */
input[type="file"] {
    width: 100%;
    max-width: 100%;
    padding: 10px;
    border: 1px dashed var(--control);
    border-radius: var(--corner);
    background: var(--card);
    color: var(--ink-2);
    font-size: var(--t-s);
}

input[type="file"]::file-selector-button {
    margin-inline-end: 12px;
    min-height: 40px;
    padding: 0 14px;
    border: 1px solid var(--ink);
    border-radius: var(--corner);
    background: var(--card);
    color: var(--ink);
    font: inherit;
    font-weight: 600;
    cursor: pointer;
}

input[type="file"]:hover::file-selector-button {
    background: var(--paper-2);
}

/* Checkboxes are square boxes drawn like the tally's (2px corners, a
   1.5px --control edge on card stock); ticked, the box fills with ink and
   a card-stock tick is knocked out of it. Radios are drawn the same way
   but round (the shape says "one of these"): a 1.5px --control ring on
   card stock; chosen, the ring turns ink around an ink dot. A browser's
   own radio was a grey ring and, checked in Safari, a pale disc with no
   3:1 edge. Both 20px. :where() keeps specificity at zero so components
   can restyle them (.switch, .rating do). Forced colours hand both back to
   the system (section 16). */
:where(input[type="checkbox"], input[type="radio"]) {
    flex: none;
    width: 20px;
    height: 20px;
    margin: 0;
    accent-color: var(--ink);
    vertical-align: -0.2em;
    cursor: pointer;
    appearance: none;
    border: 1.5px solid var(--control);
    background: var(--card);
    transition: background-color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}

:where(input[type="checkbox"]) {
    border-radius: var(--corner);
}

:where(input[type="radio"]) {
    border-radius: 50%;
}

:where(input[type="checkbox"], input[type="radio"]):hover {
    border-color: var(--ink);
}

:where(input[type="checkbox"]):checked {
    border-color: var(--ink);
    background: var(--ink) var(--tick-mark) center / 78% no-repeat;
}

/* The dot is 10px in a 17px inner circle; the 0.5px step softens its
   edge. */
:where(input[type="radio"]):checked {
    border-color: var(--ink);
    background: radial-gradient(circle, var(--ink) 0 4.5px, var(--card) 5px);
}

:where(input[type="checkbox"], input[type="radio"]):disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

/* A label wrapping a checkbox or radio. */
.check {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    min-height: 44px;
    padding-block: 10px;
    font-weight: 400;
    line-height: 1.45;
    cursor: pointer;
}

.check input {
    margin-top: 1px;
}

/* Choices: radios presented as slips of card stock to pick from. */
.choices {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    gap: 10px;
}

.choice {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    padding: 14px 16px;
    border: 1px solid var(--control);
    border-radius: var(--corner);
    background: var(--card);
    font-weight: 600;
    line-height: 1.35;
    cursor: pointer;
    transition: border-color var(--dur) var(--ease);
}

.choice:hover {
    border-color: var(--ink-2);
}

/* Checked: a 2px ink border (the padding gives back the extra pixel). */
.choice:has(input:checked) {
    border-width: 2px;
    border-color: var(--ink);
    padding: 13px 15px;
}

.choice:has(input:focus-visible) {
    outline: var(--focus-width) solid var(--focus);
    outline-offset: 2px;
}

.choice input:focus-visible {
    outline: none;
}

.choice small {
    display: block;
    margin-top: 2px;
    font-weight: 400;
    font-size: var(--t-xs);
    color: var(--ink-3);
}

/* Tick-off rows (ingredients, collection pickers): the whole row is the
   label, the box is 22px; a ticked row is struck through and greyed in
   ink-3 (the reader ticked it, not a tester, so never the pen), so what's
   left to do stands out. */
.tick {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    min-height: 44px;
    padding: 10px 0;
    cursor: pointer;
}

.tick input {
    width: 22px;
    height: 22px;
    margin: 1px 0 0;
}

.tick input:checked + span {
    color: var(--ink-3);
    text-decoration: line-through;
    text-decoration-color: var(--ink-3);
    text-decoration-thickness: 1.5px;
}

/* Switch (settings): an on/off track, square-cornered like every control
   (no pill). The knob moves to the end and the track fills with ink. */
.switch {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--s-4);
    min-height: 44px;
    cursor: pointer;
}

.switch input {
    position: relative;
    flex: none;
    appearance: none;
    width: 46px;
    height: 26px;
    margin: 0;
    border: 1px solid var(--control);
    border-radius: var(--corner);
    background: var(--card);
    transition: background-color var(--dur) var(--ease);
}

.switch input::after {
    content: "";
    position: absolute;
    top: 3px;
    inset-inline-start: 3px;
    width: 18px;
    height: 18px;
    border-radius: 1px;
    background: var(--ink-2);
    transition: transform var(--dur) var(--ease);
}

.switch input:checked {
    border-color: var(--ink);
    background: var(--ink);
}

.switch input:checked::after {
    background: var(--card);
    transform: translateX(20px);
}

[dir="rtl"] .switch input:checked::after {
    transform: translateX(-20px);
}

/* Rating: five numbered radios as a 1-5 scale (the cook form), each a
   square of card stock. Options in the pass zone (the configured pass
   mark to 5) carry .is-pass and sit on the highlighter band. */
.rating {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s-2);
    margin-top: var(--s-1);
}

.rating-option {
    position: relative;
}

.rating-option input {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    opacity: 0;
    cursor: pointer;
}

.rating-option span {
    display: grid;
    place-items: center;
    width: 52px;
    height: 52px;
    border: 1px solid var(--control);
    border-radius: var(--corner);
    background: var(--card);
    color: var(--ink);
    font-family: var(--serif);
    font-size: 1.375rem;
    font-weight: 600;
    font-variant-numeric: lining-nums;
    transition: background-color var(--dur) var(--ease), border-color var(--dur) var(--ease), color var(--dur) var(--ease);
}

.rating-option.is-pass::after {
    content: "";
    position: absolute;
    inset-inline: 0;
    bottom: -8px;
    height: 4px;
    background: var(--mark);
}

.rating-option:hover span {
    border-color: var(--ink);
}

.rating-option input:checked + span {
    background: var(--button);
    border-color: var(--button);
    color: var(--on-button);
}

.rating-option input:focus-visible + span {
    outline: var(--focus-width) solid var(--focus);
    outline-offset: 2px;
}

.rating-scale {
    display: flex;
    justify-content: space-between;
    gap: var(--s-4);
    max-width: 292px;
    margin-top: 14px;
    font-size: var(--t-xs);
    color: var(--ink-3);
}

/* Search field: glyph, input, submit (an ink button), in one box of card
   stock with a 1.5px ink edge. */
.search {
    display: flex;
    align-items: center;
    gap: var(--s-2);
    min-height: 56px;
    padding-block: 4px;
    padding-inline: 16px 4px;
    border: 1.5px solid var(--ink);
    border-radius: var(--corner);
    background: var(--card);
}

.search:focus-within {
    outline: var(--focus-width) solid var(--focus);
    outline-offset: 2px;
}

.search > .icon {
    width: 20px;
    height: 20px;
    color: var(--ink-2);
}

.search input {
    flex: 1;
    min-width: 0;
    height: 44px;
    border: 0;
    background: transparent;
    font-size: 1.125rem;
    color: var(--ink);
}

.search .btn {
    min-height: 44px;
    padding-inline: 20px;
}

.search input:focus,
.search input:focus-visible {
    outline: none;
}

.search input::placeholder {
    color: var(--ink-3);
    opacity: 1;
}

.search input::-webkit-search-cancel-button {
    cursor: pointer;
}

/* Error summary at the top of a form with errors: a slip of card stock
   with a 4px red-pencil bar at its start, a sentence (an h2, or an h3
   inside a section) after the red square every error carries, then links
   to the fields. Short and specific: the sentence says what didn't
   happen, each link what to fix. */
.error-summary {
    display: grid;
    gap: var(--s-2);
    margin-bottom: var(--s-5);
    padding: var(--s-3) var(--s-4);
    border: 1px solid var(--card-edge);
    border-inline-start: 4px solid var(--red);
    border-radius: var(--corner);
    background: var(--card);
    color: var(--red);
    font-size: var(--t-s);
    font-weight: 600;
    line-height: 1.45;
}

.error-summary :is(h2, h3) {
    display: flex;
    align-items: baseline;
    gap: var(--s-2);
    font-family: var(--sans);
    font-size: var(--t-m);
    font-weight: 700;
    letter-spacing: normal;
    color: var(--red);
}

.error-summary :is(h2, h3)::before {
    content: "";
    flex: none;
    width: 8px;
    height: 8px;
    background: currentColor;
}

.error-summary ul {
    display: grid;
    gap: 4px;
    list-style: none;
    font-weight: 400;
}

.error-summary a {
    color: var(--red);
    text-decoration-color: currentColor;
}

/* Form layout: each section is titled on a 1px ink line (the ledger line
   under a table's heads), then rows, then the actions on a ruled line.
   Not the red line: a form is one card's worth of sections, and a card has
   one red header line (the page title's, or the sheet's .index-card-head);
   repeated over five legends it turned into a ruler. (The cook-report
   sheet puts its sections on an index card: recipe.css.) */
.form-card {
    display: grid;
    gap: var(--s-5);
    padding: 0;
    border: 0;
    border-radius: 0;
    background: none;
}

.form-card > :is(h2, h3):first-child {
    padding-bottom: 10px;
    border-bottom: 1px solid var(--ink);
}

.form-card + .form-card,
.form-card + .form-actions {
    margin-top: var(--s-6);
}

/* A fieldset's line is drawn by its legend: Safari ignores the float that
   takes a rendered legend out of the fieldset's border, and would paint
   the legend over a line on the fieldset. */
fieldset.form-card {
    min-width: 0;
    margin: 0;
    padding-inline: 0;
    padding-block: 0;
}

fieldset.form-card > legend {
    font-variant-numeric: lining-nums proportional-nums;
    float: inline-start;
    width: 100%;
    padding: 0 0 10px;
    border-bottom: 1px solid var(--ink);
    margin-bottom: var(--s-1);
    font-family: var(--serif);
    font-size: var(--t-l);
    font-weight: 600;
    line-height: var(--leading-tight);
}

fieldset.form-card > legend + * {
    clear: both;
}

.form-row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
    gap: var(--s-4);
}

.form-row > div {
    display: grid;
    align-content: start;
    gap: 6px;
}

.form-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: flex-start;
    gap: 10px;
    padding-top: var(--s-4);
    border-top: 1px solid var(--rule);
}

.form-thumb {
    width: 160px;
    aspect-ratio: 4 / 3;
    object-fit: cover;
    background: var(--paper-3);
    outline: 1px solid var(--photo-edge);
    outline-offset: -1px;
}

/* Divider tabs: the category filter (.pills/.pill are the names app.js
   and the templates use; there are no pill shapes). The strip is the top
   of the box's dividers: each tab in its category's fill with its edge
   (.cat-{slug} on the label; the All tab is plain card stock), ink words
   (6.1:1 or more on every fill), standing on the 1.5px
   ink edge drawn along the strip's foot. The chosen tab stands forward:
   taller, outlined in ink, bold, and its own fill covers the edge under
   it, so the choice never relies on colour. Hover lifts a tab by 3px.
   The strip never wraps: when the tabs don't fit (phones, long French
   words) it scrolls sideways inside itself, so the page never does (app.js
   keeps the chosen tab in view). Focus is drawn inside the tab, where the
   scroller can't clip it. Radios (inside the label) or links
   (aria-current="page" / aria-pressed="true") both work. */
.pills {
    display: flex;
    flex-wrap: nowrap;
    align-items: flex-end;
    gap: 0 3px;
    min-width: 0;
    min-inline-size: 0;
    margin: 0;
    padding: 6px 2px 0;
    border: 0;
    list-style: none;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scrollbar-width: none;
    background: linear-gradient(var(--ink), var(--ink)) left bottom / 100% 1.5px no-repeat;
}

.pills::-webkit-scrollbar {
    display: none;
}

/* The scroll cue. A strip that scrolls fades out over 28px at the end it
   can still scroll toward (and at its start, once scrolled), so a tab cut
   by the edge reads as more to come, not as the last one. No script: the
   strip's own sideways scroll drives the fades (a scroll-driven
   animation, scroll(self inline)), and the mask exists only while it
   runs. A strip that fits has no scroll range, so its timeline is
   inactive and it has no mask at all (no fade, and nothing clipped: My
   Cookbook's wide shelf is a .pills that doesn't scroll). The two layers
   are each 28px wider than the strip and slide by that much: the first
   carries the start fade (off the edge at 0%), the second the end fade
   (off the edge at 100%), and they intersect. The mask reads alpha only,
   so black is opacity here, not a colour. Right to left the strip starts
   on the right, so the layers swap sides. Browsers without scroll
   timelines show no cue (the @supports); app.js still brings the chosen
   tab into view, clear of the fades, and the scroll padding keeps a tab
   that takes focus clear of them too. */
@supports (animation-timeline: scroll()) {
    .pills {
        scroll-padding-inline: 32px;
        mask-size: calc(100% + 28px) 100%;
        mask-repeat: no-repeat;
        mask-composite: intersect;
        animation: pills-cue linear both;
        animation-timeline: scroll(self inline);
    }

    [dir="rtl"] .pills {
        animation-name: pills-cue-rtl;
    }
}

@keyframes pills-cue {
    0%       { mask-image: linear-gradient(to right, transparent, black 28px), linear-gradient(to left, transparent, black 28px); mask-position: -28px 0, -28px 0; }
    10%, 90% { mask-image: linear-gradient(to right, transparent, black 28px), linear-gradient(to left, transparent, black 28px); mask-position: 0 0, -28px 0; }
    100%     { mask-image: linear-gradient(to right, transparent, black 28px), linear-gradient(to left, transparent, black 28px); mask-position: 0 0, 0 0; }
}

@keyframes pills-cue-rtl {
    0%       { mask-image: linear-gradient(to left, transparent, black 28px), linear-gradient(to right, transparent, black 28px); mask-position: 0 0, 0 0; }
    10%, 90% { mask-image: linear-gradient(to left, transparent, black 28px), linear-gradient(to right, transparent, black 28px); mask-position: -28px 0, 0 0; }
    100%     { mask-image: linear-gradient(to left, transparent, black 28px), linear-gradient(to right, transparent, black 28px); mask-position: -28px 0, -28px 0; }
}

.pill {
    position: relative;
    display: inline-flex;
    flex: none;
    align-items: center;
    justify-content: center;
    gap: 6px;
    min-height: 42px;
    margin-bottom: 1.5px;
    padding: 0 14px;
    border: 1px solid var(--tab-edge);
    border-bottom: 0;
    border-radius: var(--tab-corner) var(--tab-corner) 0 0;
    background: var(--tab-fill);
    color: var(--ink);
    font-size: var(--t-s);
    font-weight: 600;
    line-height: 1.2;
    text-decoration: none;
    white-space: nowrap;
    cursor: pointer;
    transition: min-height var(--dur) var(--ease);
}

.pill input {
    position: absolute;
    opacity: 0;
    width: 1px;
    height: 1px;
}

.pill:hover {
    min-height: 45px;
    color: var(--ink);
    text-decoration: none;
}

.pill:has(input:checked),
.pill[aria-current="page"],
.pill[aria-pressed="true"] {
    z-index: 1;
    min-height: 48px;
    margin-bottom: 0;
    padding-bottom: 1.5px;
    border: 1.5px solid var(--ink);
    border-bottom: 0;
    font-weight: 700;
}

.pill:has(input:focus-visible) {
    outline: var(--focus-width) solid var(--focus);
    outline-offset: -6px;
}

/* A count after a filter word (the admin's email filter). */
.pill-count {
    font-weight: 400;
    color: var(--ink-3);
}

/* One standing divider tab, outside a filter strip: the recipe page's
   crumb is the divider the recipe is filed behind (.divider-tab with
   .cat-{slug}, or .tab-kitchen for a recipe in testing): its fill and
   edge, ink words. The line it stands on is the container's bottom edge
   (the crumb's, .recipe-crumbs in recipe.css), which its fill covers. */
.divider-tab {
    display: inline-flex;
    align-items: center;
    min-height: 36px;
    margin-bottom: -1.5px;
    padding: 0 14px 1.5px;
    border: 1px solid var(--tab-edge);
    border-bottom: 0;
    border-radius: var(--tab-corner) var(--tab-corner) 0 0;
    background: var(--tab-fill);
    color: var(--ink);
    font-size: var(--t-s);
    font-weight: 600;
    line-height: 1.2;
    text-decoration: none;
    white-space: nowrap;
}

a.divider-tab:hover {
    text-decoration: underline;
    text-decoration-color: var(--ink);
    text-underline-offset: 0.2em;
}

/* The category's tab: its word on its fill (ink, 6.1:1 or more), outlined
   in its edge, standing on the top edge of what it labels: a recipe card
   (section 09), a hand of cards, a mini card. Put it in the DOM after the
   title it belongs to and stand it up with position: absolute; bottom:
   100%. A <p> or a <span>; never a link of its own inside a card whose
   title link covers it. .cat-{slug} on it or an ancestor gives its
   colour. */
.cat-tab {
    display: inline-flex;
    align-items: center;
    min-height: 30px;
    margin: 0;
    padding: 0 12px;
    border: 1px solid var(--c-edge, var(--cat-all-edge));
    border-bottom: 0;
    border-radius: var(--tab-corner) var(--tab-corner) 0 0;
    background: var(--c, var(--card));
    color: var(--ink);
    font-size: var(--t-s);
    font-weight: 600;
    line-height: 1.2;
    white-space: nowrap;
}

/* Segmented filters: words, grouped (admin lists, cookbook); the current
   one underlined in ink. Not divider tabs: tab colours are categories,
   never statuses. */
.segmented {
    display: inline-flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0 var(--s-4);
}

.segmented > label,
.segmented > a {
    position: relative;
    display: inline-flex;
    align-items: center;
    min-height: 40px;
    color: var(--ink-2);
    font-size: var(--t-s);
    font-weight: 600;
    text-decoration: none;
    cursor: pointer;
}

.segmented input {
    position: absolute;
    opacity: 0;
    width: 1px;
    height: 1px;
}

.segmented > label:hover,
.segmented > a:hover {
    color: var(--ink);
    text-decoration: underline;
    text-decoration-color: var(--rule-2);
    text-decoration-thickness: 2px;
    text-underline-offset: 0.4em;
}

.segmented > label:has(input:checked),
.segmented > a[aria-current="page"] {
    color: var(--ink);
    text-decoration: underline;
    text-decoration-color: var(--ink);
    text-decoration-thickness: 2px;
    text-underline-offset: 0.4em;
}

.segmented > label:has(input:focus-visible) {
    outline: var(--focus-width) solid var(--focus);
    outline-offset: 2px;
}

/* Tabs (the admin's flag statuses): words on a ruled blue line; the
   current one stands on a 2px ink underline. */
.tabs {
    display: flex;
    gap: var(--s-5);
    overflow-x: auto;
    border-bottom: 1px solid var(--rule);
    scrollbar-width: none;
}

.tabs::-webkit-scrollbar {
    display: none;
}

.tab {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: var(--s-2);
    min-height: 44px;
    border-bottom: 2px solid transparent;
    color: var(--ink-2);
    font-weight: 600;
    font-size: var(--t-s);
    text-decoration: none;
    white-space: nowrap;
}

.tab:hover {
    color: var(--ink);
    border-bottom-color: var(--rule-2);
}

.tab[aria-current="page"],
.tab[aria-selected="true"] {
    border-bottom-color: var(--ink);
    color: var(--ink);
}

/* Inside a scroller the ring would be clipped: draw it inside. */
.tab:focus-visible {
    outline-offset: -3px;
}

/* 08  Status marks, tally, figures, score readout, score scale ---------------
   A status mark is a glyph and a word (15px, 600), never a filled badge, a
   chip or a glyph in a disc, and a recipe card carries exactly one (the
   card_status partial). The .badge class names stay because templates
   and tests use them:
   Proven            tick-box glyph in the testers' pen (they ticked it) +
                     "Proven" in ink (+ the date in lists of words, ink-2)
   Kitchen original  small seal glyph + "Kitchen original", ink
   In testing        the tally (pen boxes) + "1 of 3 cooks", ink
   Needs work        the correction glyph + red-pencil words with a wavy
                     red-pencil underline (a correction: the wave keeps it
                     apart from an error, which has a bar and a square)
   Members only      padlock glyph + "Members only", ink-2
   Hidden            eye-off glyph + "Hidden", red pencil (authors, admins)
   A mark wraps rather than run past its column (a Japanese date in a
   pick at 320px, an admin header under wider text spacing): the glyph
   and the words stay on one flex line, so the glyph never leaves its
   word, and the words wrap beside it (the testing mark's note shares
   their span after a no-break space, so no line starts with its dot); a
   date (time) stays whole. Where there is room nothing wraps. In a table
   a mark stays on one line, so its column is sized to hold it (table
   layout reads the cell's narrowest width): a wrapping mark let the
   admin's recipe table squeeze "In testing" onto two lines at 1280px. */
.badge {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 0;
    border: 0;
    border-radius: 0;
    background: none;
    color: var(--ink);
    font-family: var(--sans);
    font-size: var(--t-s);
    font-weight: 600;
    line-height: 1.3;
    white-space: normal;
    vertical-align: middle;
}

.badge time,
.data-table .badge {
    white-space: nowrap;
}

.badge .icon {
    width: 18px;
    height: 18px;
}

.badge-proven .icon {
    color: var(--pen);
}

.badge-premium,
.badge-premium .icon {
    color: var(--ink-2);
}

.badge-needs-work {
    color: var(--red);
    text-decoration: underline wavy var(--red);
    text-decoration-thickness: 1.5px;
    text-underline-offset: 4px;
}

.badge-hidden {
    color: var(--red);
}

/* The lighter part of a mark: the date of "Proven 9 Aug 2026", the count
   of "In the Test Kitchen · 1 of 3 cooks". */
.badge time,
.badge .badge-note {
    font-weight: 400;
    color: var(--ink-2);
}

/* A dated mark ("Proven 9 Aug 2026") is one flex item, so its words and
   the date stay together; only the word is bold, not a particle or a
   preposition around the date ("{date}に検証済み", "Éprouvée le …"). */
.badge-dated {
    font-weight: 400;
    color: var(--ink-2);
}

.badge-dated b {
    font-weight: 600;
    color: var(--ink);
}

/* Tally: one box per as-written cook needed, drawn in the testers' pen; a
   cook ticks a box (a card-stock tick knocked out of a pen square), with
   a gap after the pass count when there are more cooks than needed. An
   empty box shows the ground it sits on. Always paired with words ("2 of
   3 cooks"). */
.tally {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    vertical-align: middle;
}

.tally i {
    display: block;
    width: 14px;
    height: 14px;
    border: 1.5px solid var(--pen);
    border-radius: 1px;
    background: var(--ground);
}

.tally i.is-done {
    background: var(--pen) var(--tick-mark) center / 11px 11px no-repeat;
}

.tally i.is-pass:not(:last-child) {
    margin-inline-end: 7px;
}

.tally-line {
    display: inline-flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 4px 8px;
    font-size: var(--t-xs);
    color: var(--ink);
}

/* Small inline tags (As written / With changes / You cooked this): plain
   words, no fill. "With changes" is a valid way to cook, so it stays in
   ink-2 (never red pencil). */
.tag {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: var(--t-xs);
    font-weight: 600;
    line-height: 1.4;
    color: var(--ink-2);
    white-space: nowrap;
}

.tag .icon {
    width: 16px;
    height: 16px;
}

/* The testers' figures (the recipe_figures partial): a glyph and a figure
   each, in the pen, Besley 600, tabular: the as-written cooks (users),
   came out as promised (check-circle), easy to follow (list). The glyphs
   are pen-2 (6.8 paper, 6.1 or more on a tint). Each figure carries its
   words for screen readers; the legend (.figures-legend) shows sighted
   readers what the glyphs mean, once per grid or section. A recipe with
   no as-written cooks says "No scores yet" (.figures-none) in ink-3. */
.figures {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 2px 14px;
    color: var(--pen);
    font-family: var(--serif);
    font-size: 1rem;
    font-weight: 600;
    line-height: 1.25;
    font-variant-numeric: lining-nums proportional-nums;
}

.figure {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    white-space: nowrap;
}

.figure .icon {
    width: 17px;
    height: 17px;
    color: var(--pen-2);
}

.figures-none {
    font-family: var(--sans);
    font-size: var(--t-xs);
    font-weight: 400;
    color: var(--ink-3);
}

/* The legend: the three glyphs and their words on one line (wrapping on
   a phone), 14px ink-2. aria-hidden: the figures carry their own words. */
.figures-legend {
    display: flex;
    flex-wrap: wrap;
    gap: 4px 16px;
    font-size: var(--t-xs);
    line-height: 1.4;
    color: var(--ink-2);
}

.figures-legend > span {
    display: inline-flex;
    align-items: center;
    gap: 6px;
}

.figures-legend .icon {
    width: 17px;
    height: 17px;
    color: var(--pen-2);
}

/* Score readout: a Besley figure in the testers' pen over "/5", with its
   label (its glyph first, the one the cards' figures use) and the
   question it answers, where there is room. The figure is a <bdi>. */
.readout {
    display: grid;
    gap: 2px;
}

.readout-label {
    font-size: var(--t-s);
    font-weight: 600;
    color: var(--ink);
}

.readout-label > .icon {
    width: 19px;
    height: 19px;
    margin-inline-end: 6px;
    color: var(--pen-2);
}

.readout-q {
    font-weight: 400;
    color: var(--ink-3);
}

.readout-figure {
    font-family: var(--serif);
    font-size: 2rem;
    font-weight: 700;
    line-height: 1;
    letter-spacing: -0.01em;
    font-variant-numeric: lining-nums proportional-nums;
    color: var(--pen);
}

.readout-of {
    margin-inline-start: 2px;
    font-family: var(--sans);
    font-size: var(--t-xs);
    font-weight: 600;
    letter-spacing: normal;
    color: var(--ink-3);
}

/* Score scale: a little ruler printed on the card. A 2px ruled blue line,
   the pass zone (the pass mark to 5) on the highlighter, and the testers'
   average as a 4px pen stroke standing across both (pen on the
   highlighter 8.5:1), with the numbers 1 to 5 under it. Inline SVG on a
   0-100 axis with x and width set server-side (scoreScale), so no
   style="". Mirrors in right-to-left pages, like the numbers under it.
   The inline padding holds the end numbers (centred on 1 and 5) and the
   pin inside the scale's box. */
.scale {
    display: grid;
    gap: 4px;
    width: 100%;
    max-width: 22rem;
    padding-inline: 6px;
}

.scale-svg {
    display: block;
    width: 100%;
    height: 28px;
    overflow: visible;
}

[dir="rtl"] .scale-svg {
    transform: scaleX(-1);
}

.scale-zone {
    fill: var(--mark);
}

.scale-axis {
    stroke: var(--rule-2);
    stroke-width: 2px;
    vector-effect: non-scaling-stroke;
}

.scale-pin {
    stroke: var(--pen);
    stroke-width: 4px;
    vector-effect: non-scaling-stroke;
}

.scale-nums {
    display: flex;
    justify-content: space-between;
    font-size: var(--t-xs);
    line-height: 1;
    color: var(--ink-3);
    font-variant-numeric: tabular-nums lining-nums;
}

/* Each number is centred on its place on the line: a zero-width box that its digit
   overflows equally on both sides (flex centring, unlike a grid item's,
   isn't pushed back to the start). */
.scale-nums > span {
    display: flex;
    justify-content: center;
    width: 0;
}

/* Score rows: a readout beside its scale (the "scores" partial), each on a
   ruled blue line under an ink top edge. */
.score-rows {
    display: grid;
    border-top: 1px solid var(--ink);
}

.score-row {
    display: grid;
    grid-template-columns: minmax(0, 14rem) minmax(0, 1fr);
    gap: var(--s-3) var(--s-5);
    align-items: center;
    padding-block: var(--s-4);
    border-bottom: 1px solid var(--rule);
}

/* One layout for a score wherever it shows (the recipe's record, the home
   page's specimen, /join, the admin's recipe record): the label and its
   question, the scale under them, the figure at the end. A readout with
   no scale ("Would make again") keeps its label at the start and its
   figure at the end. */
.score-pair {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas:
        "label figure"
        "scale figure";
    align-items: center;
    gap: var(--s-2) var(--s-5);
}

.score-pair > .readout {
    display: contents;
}

.score-pair .readout-label {
    grid-area: label;
}

.score-pair .readout-q {
    display: block;
    font-size: var(--t-xs);
}

/* The figure's column keeps one width whatever the digits ("2.3" is
   narrower than "3.7" in Besley's proportional figures), so the scales
   of a record's rows line up. */
.score-pair .readout-figure {
    grid-area: figure;
    min-inline-size: 4.75rem;
    font-size: 2.25rem;
    text-align: end;
    white-space: nowrap;
}

.score-pair > .scale {
    grid-area: scale;
    max-width: none;
}

/* 09  Recipe cards: the photo-card grid ------------------------------------
   Every list of recipes is a grid of photo cards (partials recipe_index,
   recipe_index_with, recipe_card_with; recipe_row is the card's old
   name). A card is a white index card filed behind its category: the
   category's tab (.cat-tab) stands on its top edge and a 6px stripe of
   the same fill runs along it; then the photo (4:3), the title in Besley
   (in the recipe's own language and direction; its link covers the
   card), and on a ruled blue line the one status mark and the testers'
   figures. The clip toggle is a 44px square of card stock on the photo's
   corner. Hover underlines the title; focus rings the whole card. A card
   lifts by its 1px edge alone, never a shadow.
   The grid counts its columns from its own width (a container query on
   .recipe-index): one under 19rem, two (a phone), three from 44rem, four
   from 66rem; and each card tightens on its own width under 15rem. The
   rows are 48px apart, room for the next row's tabs.
   Markup:
   .recipe-index > .recipe-index-head (the legend) + ol.recipe-list > li >
     article.recipe-card.cat-{slug}
       .recipe-card-photo > img | .no-photo
       .recipe-card-body > h2|h3.recipe-card-title > a.recipe-card-link,
         p.recipe-card-tab.cat-tab, .recipe-card-foot (p.recipe-card-status,
         p.figures, p.recipe-card-note), p.recipe-card-action
       .recipe-card-clip (the clip toggle) */
.recipe-index {
    container-type: inline-size;
    min-width: 0;
}

/* The legend, at the grid's head, at its end. */
.recipe-index-head {
    display: flex;
    justify-content: flex-end;
}

.recipe-list {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 48px 14px;
    margin: 0;
    padding: 34px 0 0;         /* room for the first row's tabs */
    list-style: none;
}

/* Each cell is a column: the card (or a page's wrapper round it, as the
   cookbook's) takes the cell's full width, and a card its full height,
   so the cards in a row line up top and bottom. */
.recipe-list > li {
    display: flex;
    flex-direction: column;
    min-width: 0;
    container-type: inline-size;
}

.recipe-list > li > .recipe-card {
    flex: 1 0 auto;
}

@container (width < 19rem) {
    .recipe-list {
        grid-template-columns: minmax(0, 1fr);
    }
}

@container (width >= 44rem) {
    .recipe-list {
        grid-template-columns: repeat(3, minmax(0, 1fr));
        gap: 52px 24px;
    }
}

@container (width >= 66rem) {
    .recipe-list {
        grid-template-columns: repeat(4, minmax(0, 1fr));
    }
}

/* The card. The tab stands at its top-start corner, so that corner is
   square. Inside it --ground is card stock (an empty tally box). */
.recipe-card {
    --ground: var(--card);
    position: relative;
    display: flex;
    flex-direction: column;
    border: 1px solid var(--card-edge);
    border-top: var(--stripe) solid var(--c, var(--card-edge));
    border-radius: var(--corner);
    border-start-start-radius: 0;
    background: var(--card);
}

.recipe-card:has(.recipe-card-link:focus-visible) {
    outline: var(--focus-width) solid var(--focus);
    outline-offset: 2px;
}

.recipe-card-photo {
    aspect-ratio: 4 / 3;
    overflow: hidden;
    background: var(--paper-3);
}

.recipe-card-photo img,
.recipe-card-photo .no-photo {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.recipe-card-body {
    display: flex;
    flex: 1;
    flex-direction: column;
    gap: 10px;
    padding: 14px 16px 16px;
}

.recipe-card-title {
    font-size: 1.1875rem;
    font-weight: 600;
    line-height: 1.22;
}

/* A title in another script keeps its own direction but lines up with
   the page, like every other title in the grid. */
[dir="ltr"] .recipe-card-title { text-align: left; }
[dir="rtl"] .recipe-card-title { text-align: right; }

.recipe-card-link {
    color: inherit;
    text-decoration: none;
}

/* The title link covers the whole card. */
.recipe-card-link::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 1;
}

.recipe-card-link:focus-visible {
    outline: none;
}

.recipe-card:hover .recipe-card-link {
    text-decoration: underline;
    text-decoration-thickness: 1px;
    text-decoration-color: currentColor;
    text-underline-offset: 3px;
}

/* The tab, standing on the card's top edge at its start, in the same
   fill as the stripe (its edge too, so tab and stripe read as one
   piece). A long word ("Accompagnements et salades") wraps inside the
   card's width rather than run over the next card's. */
.recipe-card-tab {
    position: absolute;
    bottom: 100%;
    inset-inline-start: -1px;
    max-width: calc(100% + 2px);
    margin-bottom: var(--stripe);
    padding-block: 4px;
    border-color: var(--c, var(--card-edge));
    background: var(--c, var(--card));
    line-height: 1.15;
    white-space: normal;
}

/* The record, on a ruled blue line: the status mark on its own line,
   the figures under it, so every card reads the same way whatever its
   status word; then any note. The figures' line is always there ("No
   scores yet" stands as tall as the figures, and a testing recipe
   nobody has cooked keeps it blank: its tally is its only mark), so the
   ruled lines of a row of cards line up. */
.recipe-card-foot {
    --figures-line: 1.25rem;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 6px;
    margin-top: auto;
    padding-top: 10px;
    border-top: 1px solid var(--rule);
}

.recipe-card-foot > .figures {
    min-height: var(--figures-line);
}

.recipe-card-foot:not(:has(> .figures, > .recipe-card-note))::after {
    content: "";
    height: var(--figures-line);
}

.recipe-card-note {
    display: flex;
    align-items: flex-start;
    gap: 6px;
    font-size: var(--t-xs);
    line-height: 1.4;
    color: var(--ink-2);
}

.recipe-card-note .icon {
    width: 16px;
    height: 16px;
    margin-top: 0.1em;
    color: var(--pen);
}

/* A link in a note ("Revise it") and the card's own action ("Cook it")
   sit above the title link that covers the card. */
.recipe-card-note a {
    position: relative;
    z-index: 2;
    font-weight: 600;
    color: var(--ink);
}

.recipe-card-action {
    position: relative;
    z-index: 2;
}

/* A hidden recipe (its author's view): the photo greys, the mark says
   why in words. */
.recipe-card.is-hidden .recipe-card-photo img {
    filter: grayscale(1);
    opacity: 0.7;
}

/* The clip toggle on the photo's corner: a 44px square of card stock
   with its --control edge (3:1 or more on any photo is not promised, so
   the card stock behind the glyph is what carries it), its word read,
   not shown. */
.recipe-card-clip {
    position: absolute;
    top: 8px;
    inset-inline-end: 8px;
    z-index: 2;
}

.recipe-card-clip .clip {
    width: 44px;
    padding: 0;
}

.recipe-card-clip .clip > span:not(.visually-hidden) {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

/* No photo: a dashed box that says so, never a blank tile. Its size is
   set where it stands (88px squares by default; the whole photo area on
   a card). */
.no-photo {
    display: grid;
    place-items: center;
    width: 88px;
    height: 88px;
    padding: var(--s-2);
    border: 1px dashed var(--rule-2);
    background: transparent;
    font-size: var(--t-xs);
    line-height: 1.25;
    color: var(--ink-3);
    text-align: center;
}

/* A narrow card (a phone's two columns, about 170px): tighter, a size
   down, the tab smaller. */
@container (width < 15rem) {
    .recipe-card-body {
        gap: 8px;
        padding: 10px 10px 12px;
    }

    .recipe-card-title {
        font-size: 1rem;
    }

    .recipe-card-foot {
        --figures-line: 1.171875rem;   /* the figures' 15px at 1.25 */
        padding-top: 8px;
    }

    .recipe-card .figures {
        gap: 2px 10px;
        font-size: 0.9375rem;
    }

    .recipe-card .badge {
        font-size: var(--t-xs);
    }

    .recipe-card-tab {
        min-height: 26px;
        padding-inline: 8px;
        font-size: var(--t-xs);
    }

    .recipe-card-clip {
        top: 4px;
        inset-inline-end: 4px;
    }
}

/* 10  Category dividers: the tin -----------------------------------------------
   The categories box (partials category_box, category_divider): the
   eight dividers standing in the teal enamel tin, each a tall card in
   its category's fill with its 1px edge, whatever its count (an empty
   one keeps its colour and its "0" tab), its count on the tab at its top
   (Besley 700), its glyph (catIcon) and its word in ink. The tin's front
   is drawn across the foot of each shelf, in front of the dividers; its
   card-stock label ("Recipes 17", aria-hidden) sits on it. Hover or
   focus lifts a divider 14px out of the tin; the chosen one (aria-current)
   stands lifted with an ink edge. The count tabs step across the box out
   and back (a V), so no two counts sit side by side and read as one
   number.

   A name wraps at its spaces only (and after a hyphen or a ・): never
   hyphenated, never broken inside a word. Its size follows the room a
   divider has (--room, from the tin's width), up to the layout's size;
   a name whose widest word runs long (nameFit: .is-long, .is-longer)
   steps down so it fits whole, never under 14px. Should a word still
   not fit, its column widens for it (minmax(min-content, 1fr)) and the
   others share what is left.

   The box counts its own width (a container query on .tin): two shelves
   of four on phones (three shelves, of three, three and two, on the
   narrowest phones when the box holds a long word: .has-long), roomier
   from 44rem, one row of eight from 66rem.
   Markup: .tin > ul.dividers > li > a.divider.cat-{slug} >
     span.divider-count + svg.divider-glyph + span.divider-name
   and p.tin-label after the list. */
.tin {
    position: relative;
    container-type: inline-size;
}

/* The shelves, as variables each layout sets: --tab of count tab over a
   --tall divider, its foot behind a --front; --tin-pad inside the tin's
   sides, --divider-gap either side of a divider, --divider-pad inside
   it. Phones: two shelves of four, 40px apart, so the first shelf's
   front starts 148px down. */
.dividers {
    --cols: 4;
    --tab: 30px;
    --tall: 158px;
    --front: 40px;
    --tin-pad: 4px;
    --divider-gap: 1px;
    --divider-pad: 5px;
    --name-max: var(--t-xs);
    /* A divider's content width, for its name's size. */
    --room: calc((100cqi - 2 * var(--tin-pad)) / var(--cols) - 2 * var(--divider-gap) - 2px - 2 * var(--divider-pad));
    position: relative;
    display: grid;
    grid-template-columns: repeat(var(--cols), minmax(min-content, 1fr));
    row-gap: 40px;
    margin: 0;
    padding: 0 var(--tin-pad);
    list-style: none;
}

.dividers > li {
    position: relative;
    padding-top: var(--tab);
}

/* The tin's front: a panel across the foot of each shelf, in front of the
   dividers, its rolled rim along the top. */
.dividers::before,
.dividers::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    bottom: -1px;              /* a hair past the dividers, so no sub-pixel row shows */
    z-index: 2;
    height: calc(var(--front) + 1px);
    border-top: 3px solid var(--tin-edge);
    border-radius: var(--corner) var(--corner) 0 0;
    background: var(--tin);
    pointer-events: none;
}

.dividers::before {
    top: calc(var(--tab) + var(--tall) - var(--front));
    bottom: auto;
}

.divider {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    height: var(--tall);
    margin-inline: var(--divider-gap);
    padding: 10px var(--divider-pad) 0;
    border: 1px solid var(--c-edge);
    background: var(--c);
    color: var(--ink);
    text-decoration: none;
    transition: transform 180ms var(--ease);
}

.divider:hover,
.divider:focus-visible,
.divider[aria-current="page"] {
    transform: translateY(-14px);
}

.divider[aria-current="page"] {
    border: 1.5px solid var(--ink);
}

.divider-count {
    position: absolute;
    bottom: 100%;
    inset-inline-start: -1px;
    display: flex;
    align-items: center;
    justify-content: center;
    height: var(--tab);
    min-width: 36px;
    padding: 0 8px;
    border: 1px solid var(--c-edge);
    border-bottom: 0;
    border-radius: var(--tab-corner) var(--tab-corner) 0 0;
    background: var(--c);
    font-family: var(--latin-serif);
    font-size: 1.0625rem;
    font-weight: 700;
    line-height: 1;
    font-variant-numeric: lining-nums proportional-nums;
}

/* The tabs step across the box out and back, like a cut set of dividers. */
.dividers > li:is(:nth-child(2), :nth-child(7)) .divider-count { inset-inline-start: 18%; }
.dividers > li:is(:nth-child(3), :nth-child(6)) .divider-count { inset-inline-start: auto; inset-inline-end: 18%; }
.dividers > li:is(:nth-child(4), :nth-child(5)) .divider-count { inset-inline-start: auto; inset-inline-end: -1px; }

.divider-glyph {
    width: 30px;
    height: 30px;
    color: var(--ink);
}

/* The word: as large as the divider's room allows for a word about
   5.8em wide ("Desayunos" in Besley), up to the layout's size; a long
   word sizes for 7em ("Guarniciones"), a longer one for 8.5em. On
   phones it is 14px bold whatever its length (nothing is smaller), and
   the columns make the room. Japanese keeps its words whole (keep-all) and breaks after
   its ・. Breaking inside a word (break-word) is only a last resort, for
   a word wider than the whole shelf. */
.divider-name {
    --fit: 5.8;
    margin-top: 8px;
    font-size: clamp(var(--t-xs), var(--room) / var(--fit), var(--name-max));
    font-weight: 700;
    line-height: 1.15;
    hyphens: manual;
    overflow-wrap: break-word;
}

.divider-name.is-long { --fit: 7; }
.divider-name.is-longer { --fit: 8.5; }

.divider-name:lang(ja) {
    word-break: keep-all;
}

/* The tin's label: a slip of card stock on its front, on a red line. */
.tin-label {
    position: absolute;
    bottom: 18px;
    left: 50%;
    z-index: 3;
    display: none;
    align-items: baseline;
    gap: 10px;
    padding: 6px 18px 5px;
    border-bottom: var(--head-line) solid var(--card-red);
    border-radius: 2px;
    background: var(--card);
    font-family: var(--serif);
    font-size: 1.1875rem;
    font-weight: 600;
    line-height: 1.2;
    white-space: nowrap;
    transform: translateX(-50%);
}

.tin-label span {
    font-family: var(--latin-sans);
    font-size: var(--t-s);
    font-weight: 600;
    color: var(--ink-3);
}

/* The narrowest phones, with a long word in the box: four to a shelf
   can't hold "Mittagessen" or "Guarniciones" at 14px, so three shelves,
   of three, three and two. The second shelf's front is drawn by its
   first divider's slot, across the whole tin. */
@container (width < 22rem) {
    .tin.has-long .dividers {
        --cols: 3;
    }

    .tin.has-long .dividers > li:nth-child(4)::after {
        content: "";
        position: absolute;
        bottom: -1px;
        inset-inline-start: calc(-1 * var(--tin-pad));
        z-index: 2;
        width: 100cqi;
        height: calc(var(--front) + 1px);
        border-top: 3px solid var(--tin-edge);
        border-radius: var(--corner) var(--corner) 0 0;
        background: var(--tin);
        pointer-events: none;
    }
}

/* A tablet's width: the same two shelves, roomier, a little taller (a
   three-line name, as Arabic's "Sides & Salads", stays clear of the
   front), the word in Besley. */
@container (width >= 44rem) {
    .dividers {
        --tall: 184px;
        --tin-pad: 14px;
        --divider-gap: 6px;
        --divider-pad: 14px;
        --name-max: 1.1875rem;
    }

    .divider {
        padding-top: 14px;
    }

    .divider-count {
        min-width: 48px;
        font-size: 1.1875rem;
    }

    .divider-glyph {
        width: 36px;
        height: 36px;
    }

    .divider-name {
        font-family: var(--serif);
        font-weight: 600;
    }
}

/* The full desk: one row of eight, the front 78px deep, the counts
   stepping out and back across the whole box, the label on the front. */
@container (width >= 66rem) {
    .dividers {
        --cols: 8;
        --tab: 34px;
        --tall: 246px;
        --front: 78px;
        --tin-pad: clamp(10px, 1.6vw, 22px);
        --divider-gap: 4px;
        --divider-pad: 12px;
        --name-max: 1.25rem;
    }

    .dividers::before {
        display: none;
    }

    .divider {
        padding-top: 16px;
    }

    .divider-count {
        min-width: 54px;
        padding: 0 12px;
        font-size: 1.25rem;
    }

    .dividers > li:is(:nth-child(1), :nth-child(8)) .divider-count { inset-inline: -1px auto; }
    .dividers > li:is(:nth-child(2), :nth-child(7)) .divider-count { inset-inline: 26% auto; }
    .dividers > li:is(:nth-child(3), :nth-child(6)) .divider-count { inset-inline: 50% auto; }
    .dividers > li:is(:nth-child(4), :nth-child(5)) .divider-count { inset-inline: auto -1px; }

    .divider-glyph {
        width: 40px;
        height: 40px;
    }

    .divider-name {
        margin-top: 14px;
    }

    .tin-label {
        display: flex;
    }
}

/* 11  Tables, pager, empty states, notices ------------------------------------
   Tables: 14-15px, 1px ink under the head row, each row on a ruled blue
   line, numbers right-aligned and tabular, no zebra, no boxed container.
   The admin's tables are these, on the same ruled blue lines (admin.css
   sets their cells closer and folds a narrow one into a list).
   A scroller clips at its padding edge, and the first column's cells have
   no start padding, so the focus ring (3px, 2px out) of a link or button
   there lost its whole inline-start side. The wrap pads the ring's width
   on both sides and takes it back with a negative margin: the table and
   its container queries (admin.css) keep their place and width. */
.table-wrap {
    position: relative;    /* keeps absolutely positioned helpers inside the scroller */
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    padding-inline: calc(var(--focus-width) + 2px);
    margin-inline: calc(-1 * (var(--focus-width) + 2px));
}

.data-table {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--t-s);
}

.data-table caption {
    padding-bottom: var(--s-2);
    font-weight: 700;
    text-align: start;
}

.data-table th {
    padding: var(--s-2) var(--s-3);
    border-bottom: 1px solid var(--ink);
    background: none;
    font-size: var(--t-xs);
    font-weight: 600;
    color: var(--ink-2);
    text-align: start;
    vertical-align: bottom;
    white-space: nowrap;
}

.data-table th:first-child,
.data-table td:first-child {
    padding-inline-start: 0;
}

/* A column head that is a link (a sort) reads as the head: no underline,
   room for an arrow beside the word. Only in <thead>: a row heading that
   is a link (a filter, a label) is underlined like any link. */
.data-table thead th a {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    color: inherit;
    text-decoration: none;
}

.data-table th[aria-sort] {
    color: var(--ink);
}

.data-table td {
    padding: var(--s-3);
    border-bottom: 1px solid var(--rule);
    vertical-align: middle;
}

.data-table tbody tr:hover td {
    background: var(--paper-2);
}

.data-table tbody tr:has(input:checked) td {
    background: var(--paper-2);
}

.data-table .num,
.data-table td.num,
.data-table th.num {
    text-align: end;
    font-variant-numeric: tabular-nums lining-nums;
}

.data-table-compact td,
.data-table-compact th {
    padding-block: 6px;
}

/* Pager: "Showing 1–24 of 46" in ink-3, then Previous / Next as small ink
   outline buttons with their arrows. Page numbers, where a pager lists
   them, are words; the current one underlined. A missing Previous or Next
   is grey words with no outline, not a button that reads as live
   (:where() keeps the first rule weaker than [aria-disabled]). */
.pager {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--s-2) var(--s-5);
    font-size: var(--t-s);
}

.pager-status {
    color: var(--ink-3);
    font-variant-numeric: tabular-nums lining-nums;
}

.pager-links {
    display: flex;
    gap: var(--s-2);
    margin-inline-start: auto;
}

.pager a,
.pager span:where(:not(.pager-status, .pager-links)) {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    min-height: 44px;
    padding: 0 14px;
    border: 1.5px solid transparent;
    border-radius: var(--corner);
    color: var(--ink);
    font-weight: 600;
    text-decoration: none;
}

.pager a[rel] {
    border-color: var(--ink);
}

.pager a[rel]:hover {
    background: var(--hover);
}

.pager .icon {
    width: 18px;
    height: 18px;
}

.pager [aria-current="page"] {
    text-decoration: underline;
    text-decoration-thickness: 2px;
    text-underline-offset: 0.35em;
}

.pager [aria-disabled="true"] {
    color: var(--ink-3);
    font-weight: 400;
}

/* Result summaries: one plain sentence (role="status"). */
.results-summary {
    margin-bottom: var(--s-5);
    font-size: var(--t-s);
    color: var(--ink-2);
}

/* Empty states: a blank index card (white stock, a dashed ruled-blue
   edge: a space still to fill) with one heading-sized sentence (h2 or
   h3), at most one short hint, and one action. No illustration, no icon
   in a disc. */
.empty-state {
    grid-column: 1 / -1;
    display: grid;
    justify-items: start;
    gap: var(--s-3);
    padding: var(--s-6) var(--card-pad);
    border: 1px dashed var(--rule-2);
    border-radius: var(--corner);
    background: var(--card);
    color: var(--ink-2);
    text-align: start;
}

.empty-state h2,
.empty-state h3 {
    font-family: var(--serif);
    font-size: var(--t-l);
    font-weight: 600;
    line-height: var(--leading-tight);
    letter-spacing: normal;
    color: var(--ink);
}

.empty-state p {
    max-width: 52ch;
}

.empty-state .btn {
    margin-top: var(--s-1);
}

/* Notices (.alert): a 2px bar at the start, a glyph and a sentence. Ink
   for news and success, red pencil for an error. (The one ochre bar is
   the unconfirmed-email banner's, section 05.) */
.alert {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    margin-bottom: var(--s-5);
    padding: var(--s-1) 0;
    padding-inline: var(--s-3) 0;
    border: 0;
    border-inline-start: 2px solid var(--ink);
    border-radius: 0;
    background: none;
    color: var(--ink);
    font-size: var(--t-s);
    font-weight: 600;
    line-height: 1.45;
}

.alert > .icon {
    flex: none;
    width: 18px;
    height: 18px;
    margin-top: 1px;
}

.alert-error {
    border-inline-start-color: var(--red);
    color: var(--red);
}

.alert-success,
.alert-info {
    border-inline-start-color: var(--ink);
    color: var(--ink);
}

/* 12  Footer: the bottom of the box ------------------------------------------
   A deeper ground (--paper-3); the name written on the red header line
   (Besley 700, the line under the name alone); one short row of the
   site's pages (Recipes, Our story, How it works, and what this visitor
   can use) with the languages, each written in itself, at its end; the
   copyright, small, under a ruled line. 15px ink-2 words, underlined on
   hover; the current language bold. */
.site-footer {
    margin-top: var(--section);
    padding-block: var(--s-7) var(--s-6);
    background: var(--paper-3);
    color: var(--ink-2);
    font-size: var(--t-s);
}

/* No gap above the footer after a section on a wash: the wash meets the
   bottom of the box. */
main:has(> :last-child:is([class*="ground-"])) + .site-footer,
main:has(> :last-child > :last-child:is([class*="ground-"])) + .site-footer {
    margin-top: 0;
}

.footer-name {
    display: inline-block;
    padding-bottom: var(--s-2);
    border-bottom: var(--head-line) solid var(--card-red);
    font-family: var(--latin-serif);
    font-size: 1.375rem;
    font-weight: 700;
    line-height: 1.2;
    letter-spacing: -0.006em;
    color: var(--ink);
}

.footer-index {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    gap: var(--s-3) var(--s-7);
    margin-top: var(--s-5);
    padding-bottom: var(--s-4);
    border-bottom: 1px solid var(--rule);
}

.inline-links {
    display: flex;
    flex-wrap: wrap;
    gap: 0 var(--s-5);
    list-style: none;
}

.inline-links a,
.inline-links .link-button {
    display: inline-flex;
    align-items: center;
    min-height: 36px;
    font-weight: 400;
}

.site-footer .inline-links a {
    color: var(--ink-2);
    text-decoration: none;
}

.site-footer .inline-links a:hover {
    color: var(--ink);
    text-decoration: underline;
    text-decoration-color: currentColor;
}

.inline-links [aria-current="true"] {
    font-weight: 700;
    text-decoration: none;
}

.site-footer .inline-links [aria-current="true"] {
    color: var(--ink);
}

/* The © line is Latin in every language (in a Japanese face the © is
   a speck). */
.footer-base {
    padding-top: var(--s-4);
    font-family: var(--latin-sans);
    color: var(--ink-3);
    font-size: var(--t-xs);
}

/* 13  Status words ----------------------------------------------------------------
   A small shape and a word (the admin's email states: Sent, Pending,
   Failed). The word carries the meaning; the shape repeats it without
   colour: a dot, a ring, a square. Never a tab colour. */
.status {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: var(--t-xs);
    font-weight: 600;
    line-height: 1.3;
    color: var(--ink);
    white-space: nowrap;
}

.status::before {
    content: "";
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: currentColor;
}

.status-neutral { color: var(--ink-2); }
.status-neutral::before { border: 1.5px solid currentColor; background: transparent; }

.status-bad { color: var(--red); }
.status-bad::before { border-radius: 0; }

.stat-value {
    font-family: var(--serif);
    font-size: clamp(1.75rem, 1.5rem + 0.8vw, 2.25rem);
    font-weight: 700;
    line-height: 1.05;
    letter-spacing: -0.01em;
    font-variant-numeric: lining-nums proportional-nums;
    color: var(--ink);
}

/* 14  Utilities ------------------------------------------------------------- */
.push { margin-inline-start: auto; }
.nowrap { white-space: nowrap; }
.print-only { display: none; }

/* 15  Responsive ------------------------------------------------------------- */
/* Wide screens: the masthead stays in view (plain paper, no blur), and
   anchored headings clear it. */
@media (min-width: 981px) {
    html { scroll-padding-top: calc(var(--masthead-h) + var(--s-4)); }
    .masthead { position: sticky; top: 0; }
}

/* Up to 1200px the account menu shows a person glyph instead of the
   member's name, which keeps a long name and French or German nav words on
   one line down to 981px. The name stays in the summary for screen readers
   and heads the open panel. */
@media (max-width: 1200px) {
    .user-summary { padding-inline: 8px 6px; }
    .user-summary .icon-user { display: inline-block; width: 20px; height: 20px; }
    .user-summary .user-name {
        position: absolute;
        width: 1px;
        height: 1px;
        overflow: hidden;
        clip-path: inset(50%);
        white-space: nowrap;
    }
}

/* Down to 981px a member's nav and menus stay on one line in French
   ("Partager une recette", "Mon carnet de recettes") with the words a
   little closer and the language code without its globe. */
@media (max-width: 1100px) {
    .masthead-inner { gap: var(--s-4); }
    .nav-link { padding-inline: 8px; }
    .lang-globe { display: none; }
}

/* Below 980px the nav folds behind a "Menu" button and opens as a
   full-width ruled list under the masthead, closed by its own red line.
   A guest's nav (Log in, Join) stays in the bar from 390px up, with no
   Menu (.masthead-guest, below): under 390px "Se connecter", "Adhérer"
   and the language code no longer fit beside the word mark. */
@media (max-width: 980px) {
    .masthead-tools { margin-inline-start: auto; }
    .nav-burger { display: inline-flex; }

    /* The drawer checkbox: focusable, visually hidden, ring on its label.
       Fixed, so focusing it (tapping the label) never scrolls the page. */
    .nav-toggle {
        display: block;
        position: fixed;
        top: 0;
        inset-inline-start: 0;
        width: 1px;
        height: 1px;
        margin: 0;
        opacity: 0;
        pointer-events: none;
    }

    .nav {
        position: absolute;
        top: calc(100% + var(--head-line));
        left: 0;
        right: 0;
        display: none;
        flex-direction: column;
        align-items: stretch;
        gap: 0;
        margin: 0;
        padding: 0 var(--gutter) var(--s-5);
        background: var(--paper);
        border-bottom: var(--head-line) solid var(--card-red);
    }

    .nav-toggle:checked ~ .nav { display: flex; }
    /* One menu at a time: the phone menu hides an open account or language
       menu (app.js also closes it, and closes the phone menu when that opens). */
    .nav-toggle:checked ~ .masthead-tools .menu > .menu-panel { display: none; }

    .nav-link {
        min-height: 52px;
        border: 0;
        border-bottom: 1px solid var(--rule);
        font-size: var(--t-m);
    }

    .nav-link:hover {
        border-bottom-color: var(--rule);
        background: var(--paper-2);
    }

    .nav-link[aria-current="page"] {
        border-bottom-color: var(--rule);
        font-weight: 700;
        text-decoration: underline;
        text-decoration-thickness: 2px;
        text-decoration-color: var(--ink);
        text-underline-offset: 0.3em;
    }

    .nav-cta { align-self: flex-start; margin: var(--s-4) 0 0; }
}

@media (min-width: 390px) and (max-width: 980px) {
    .masthead-guest .nav-toggle,
    .masthead-guest .nav-burger { display: none; }

    .masthead-guest .nav {
        position: static;
        display: flex;
        flex-direction: row;
        align-items: center;
        gap: 0;
        margin-inline-start: auto;
        padding: 0;
        border: 0;
        background: none;
    }

    .masthead-guest .nav-link {
        min-height: 44px;
        padding-inline: 8px;
        border-block: 2px solid transparent;
        font-size: var(--t-s);
    }

    .masthead-guest .nav-link:hover {
        border-bottom-color: var(--rule-2);
        background: none;
    }

    .masthead-guest .nav-link[aria-current="page"] {
        border-bottom-color: var(--ink);
        font-weight: 600;
        text-decoration: none;
    }

    .masthead-guest .nav-cta { align-self: center; margin: 0 4px; }
    .masthead-guest .masthead-tools { margin-inline-start: 0; }
}

/* A guest's phone masthead: Recipes is the seal's link, so its word goes,
   and the language menu keeps its code and caret without the globe; from
   480px down the code stands alone (a 44px target, as on the smallest
   phones), so Arabic's "تسجيل الدخول" and the code fit at 390px. */
@media (min-width: 390px) and (max-width: 600px) {
    .masthead-guest .nav-recipes,
    .masthead-guest .lang-globe { display: none; }
    .masthead-guest .nav-link { font-size: 0.9375rem; }
}

@media (min-width: 390px) and (max-width: 480px) {
    .masthead-guest .lang-summary { min-width: 44px; justify-content: center; padding-inline: 6px; }
    .masthead-guest .lang-summary .menu-caret { display: none; }
}

@media (max-width: 720px) {
    :root { --t-l: 1.1875rem; --masthead-h: 64px; }
    /* "The Cooking" stays at 14px (nothing is smaller); the name and the
       seal shrink instead. */
    .logo { gap: 8px; }
    .logo-mark { width: 36px; height: 36px; }
    .logo-word { gap: 3px; }
    .logo-the { font-size: var(--t-xs); }
    .logo-name { font-size: 1.25rem; }
    .masthead-inner { gap: var(--s-2); }
    .lang-summary { padding-inline: 8px 6px; }

    .search { min-height: 52px; padding-inline-start: 12px; }
    .search input { font-size: var(--t-m); }
    .search .btn { padding: 0 14px; }

    .form-row { grid-template-columns: minmax(0, 1fr); }
    .form-actions > .btn { flex: 1 1 auto; }

    .score-row:not(.score-pair) { grid-template-columns: minmax(0, 1fr); }
}

/* Phones: the account menu's person glyph drops its caret, so a member's
   masthead (seal, language, account, Menu) fits from 480px down in every
   language, right to left included (Japanese "メニュー" and an Arabic name
   are the widest). The language code keeps its caret, so it still reads
   as a menu. */
@media (max-width: 480px) {
    .masthead-inner { gap: 6px; }
    .lang-summary { gap: 4px; padding-inline: 6px 4px; }
    .user-summary { min-width: 44px; justify-content: center; padding-inline: 6px; }
    .user-summary .menu-caret { display: none; }

    /* The language and account panels hang from the masthead itself, not
       from their summaries, and line up with the gutter: anchored to a
       summary that "メニュー" had pushed left, they started off-screen at
       320px. Still on the red line, never wider than the screen. */
    .masthead-tools .menu { position: static; }
    .masthead-tools .menu-panel {
        top: 100%;
        inset-inline: auto var(--gutter);
        max-width: calc(100vw - 2 * var(--gutter));
    }
}

/* Small phones: a smaller seal and name, and a guest's words a little
   closer (French, "Se connecter" and "Adhérer", ran 2px past a 390px
   screen). */
@media (max-width: 400px) {
    .logo { gap: 6px; }
    .logo-mark { width: 30px; height: 30px; }
    .logo-name { font-size: 1.125rem; }
    .nav-burger { padding: 0 10px; }
    .masthead-guest .nav-link { padding-inline: 6px; }
    .masthead-guest .nav-cta { margin-inline: 2px; }
}

/* The smallest phones (320px): a narrower gutter, the smallest seal, the
   name a step down, no caret on the language code, and the Menu button
   as its glyph, a 44px square like the summaries beside it. With the
   word, a member's masthead ran 8px into the gutter in Japanese
   ("メニュー"), and under wider text spacing (WCAG 1.4.12) the word grew
   and pushed the button 3px off the screen in English. Measured with
   room to spare in en, de, fr, es, ar and ja, spaced and not. */
@media (max-width: 359px) {
    :root { --gutter: 12px; }
    .logo-mark { width: 28px; height: 28px; }
    .logo-name { font-size: 1rem; }
    .masthead-inner,
    .masthead-tools { gap: 4px; }
    .lang-summary { min-width: 44px; justify-content: center; padding-inline: 6px; }
    .lang-summary .menu-caret,
    .lang-globe { display: none; }
    .nav-burger { padding: 0; }
    .nav-burger .icon { display: inline-block; vertical-align: top; }
    .nav-burger-word { display: none; }
}

/* Small buttons, divider tabs, filter words and inline links stay 44px
   tall under a finger; a switch's track and a section's or an arrow link
   take a 44px tap however short they look. */
@media (pointer: coarse) {
    .btn-sm { min-height: 44px; }
    .pill { min-width: 44px; min-height: 44px; }
    .pill:has(input:checked),
    .pill[aria-current="page"],
    .pill[aria-pressed="true"] { min-height: 50px; }
    .segmented > label,
    .segmented > a,
    .inline-links a,
    .inline-links .link-button { min-height: 44px; }
    .switch input::before {
        content: "";
        position: absolute;
        inset: -10px -1px;
    }
    a.section-link,
    .link-arrow { position: relative; }
    a.section-link::after,
    .link-arrow::after {
        content: "";
        position: absolute;
        inset: -6px -4px;
    }
}

/* 16  Motion, forced colours, print ----------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
    html { scroll-behavior: smooth; }
}

@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }
}

/* Forced colours: fills, washes and the ruled lines drop out, so system
   colours for the highlighter, the tally and the scale; outlines where
   shape carries meaning. The chosen divider tab keeps its outline (in
   Highlight) and its bold; the strip's edge becomes a border; each
   category swatch, card tab, stripe and divider gets a CanvasText edge so
   it still reads as a tab or a card; the tin's fronts and the masthead
   button's current-page line are drawn in CanvasText; checkboxes and
   radios go back to the system's own. State that was only a fill gets a
   system colour: the chosen score is Highlight, and the switch's knob is
   drawn in CanvasText (Highlight track and HighlightText knob when on),
   so on and off still differ. */
@media (forced-colors: active) {
    .btn, .clip, .nav-burger { border: 1px solid ButtonText; }
    .nav-cta[aria-current="page"]::after { forced-color-adjust: none; background: CanvasText; }
    .pills { border-bottom: 1px solid CanvasText; background: none; }
    .pill:has(input:checked), .pill[aria-current="page"], .pill[aria-pressed="true"], .divider-tab { border-color: Highlight; }
    .segmented > label:has(input:checked), .segmented > a[aria-current="page"] { text-decoration-color: Highlight; }
    .tab[aria-current="page"], .tab[aria-selected="true"] { border-bottom-color: Highlight; }
    .cat::before { forced-color-adjust: none; border: 1px solid CanvasText; background: Canvas; }
    .cat-tab, .divider, .divider-count, .recipe-card { border-color: CanvasText; }
    .divider[aria-current="page"] { border-color: Highlight; }
    .dividers::before, .dividers::after, .dividers > li::after { forced-color-adjust: none; background: CanvasText; border-color: CanvasText; }
    mark { forced-color-adjust: none; background: Highlight; color: HighlightText; }
    .tally i { border-color: CanvasText; }
    .tally i.is-done { forced-color-adjust: none; background: CanvasText; }
    :where(input[type="checkbox"], input[type="radio"]) { appearance: auto; }
    .rating-option input:checked + span { forced-color-adjust: none; background: Highlight; color: HighlightText; border-color: Highlight; }
    .switch input::after { forced-color-adjust: none; background: CanvasText; }
    .switch input:checked { forced-color-adjust: none; background: Highlight; border-color: Highlight; }
    .switch input:checked::after { background: HighlightText; }
    .scale-zone { fill: Highlight; }
    .scale-axis, .scale-pin { stroke: CanvasText; }
    .rating-option.is-pass::after { forced-color-adjust: none; background: Highlight; }
    :is(.select, .field select) { appearance: auto; background-image: none; }
    .ruled { background-image: none; }
    .status::before { forced-color-adjust: none; background: CanvasText; }
    .status-neutral::before { border-color: CanvasText; background: Canvas; }
    .field-error::before { forced-color-adjust: none; background: CanvasText; }
}

/* Print: ink on white. The chrome drops out; washes go white; highlights
   become underlines; the pen prints as a dark ink, the red line as a thin
   grey one, card edges in grey, and the ruled lines go (they are
   backgrounds). Ticked boxes and chosen radios keep their fill. A
   category's swatch and a card's tab and stripe print as what they are
   on screen: small marks in the category's colour (exact: they spend
   next to no ink), beside the word that carries the meaning. Page-specific print
   rules live in each surface's stylesheet; print.css (the cookbook) keeps
   its own copies of these. */
@media print {
    @page { margin: 14mm 14mm 16mm; }

    :root,
    [lang] {
        --paper: #fff;
        --paper-2: #fff;
        --paper-3: #fff;
        --card: #fff;
        --card-edge: #999;
        --ink: #111;
        --ink-2: #333;
        --ink-3: #555;
        --pen: #222;
        --pen-2: #444;
        --red: #111;
        --ochre: #111;
        --card-red: #888;
        --mark: transparent;
        --on-mark: #111;
        --rule: #ccc;
        --rule-2: #999;
        --ground: #fff;
        --hover: transparent;
    }

    [class*="ground-"] { background: var(--paper); }

    body { background: var(--paper); color: var(--ink); font-size: 10.5pt; line-height: 1.45; }

    .masthead,
    .site-footer,
    .site-banner,
    .skip-link,
    .recipe-card-clip,
    .flash {
        display: none !important;
    }

    .print-only { display: block; }
    .container { max-width: none; padding: 0; }
    a { color: inherit; text-decoration: none; }
    mark { text-decoration: underline; text-decoration-thickness: 1.5pt; }
    .scale-zone { fill: none; stroke: var(--ink); stroke-width: 1px; vector-effect: non-scaling-stroke; }
    .recipe-card { break-inside: avoid; }
    .index-card { break-inside: avoid; }
    .pills { animation: none; mask-image: none; }
    .cat::before,
    .cat-tab,
    .recipe-card,
    .divider,
    .tally i.is-done,
    :where(input[type="checkbox"], input[type="radio"]):checked {
        -webkit-print-color-adjust: exact;
        print-color-adjust: exact;
    }
}

/* 17  Languages and scripts -------------------------------------------------
   The layout sets <html lang dir>; recipe content and each language-menu
   item carry their own lang, and :lang() follows the language of the text
   rather than of the page. Besley and Source Sans 3 are latin only, so
   Arabic and Japanese swap in script fonts, first in the stack (their latin
   letters then set any latin words in the same text); the latin stacks
   follow as a fallback. The word mark and language codes stay latin in
   every language (--latin-*). */

/* Icons that mean back, next or forward point the other way in
   right-to-left pages (templates add .flip-rtl to them). */
[dir="rtl"] .flip-rtl {
    transform: scaleX(-1);
}

/* Words that keep their writer's direction (dir="auto": a comment, a cook
   report, a tester's note, a reported text) line up with their own start.
   Safari gives list items text-align: -webkit-match-parent, which resolves
   to the page's side and is inherited as such, so an English comment in a
   list on an Arabic page sat flush right with a ragged left edge (Chrome
   computes start). Zero specificity: it only replaces the inherited value,
   so a surface that lines such words up with the page on purpose (the
   reported words on /flags/new, account.css) still wins. */
:where(li :is(p, blockquote)[dir="auto"]) {
    text-align: start;
}

/* Arabic: a naskh for display, a sans for text; a step larger and more
   open, as Arabic letters are small for their size and need room above and
   below for dots and marks. */
:lang(ar) {
    --serif: "Noto Naskh Arabic", "Geeza Pro", "Arabic Typesetting", "Traditional Arabic", var(--latin-serif);
    --sans: "Noto Sans Arabic", "Geeza Pro", "Segoe UI", Tahoma, var(--latin-sans);
    --t-xs: 0.9375rem;   /* 15px */
    --t-s: 1rem;         /* 16px */
    --t-m: 1.125rem;     /* 18px */
    --leading: 1.8;
    --leading-tight: 1.4;
}

/* Japanese: mincho for display, gothic for text; strict line breaking
   (no small kana or closing brackets at the start of a line) and a looser
   leading. Nothing hangs: a full line's closing 。 or 、 is a whole
   full-width glyph (17-19px), wider than the 16px phone gutter, so
   section 03's hanging stop pushed paragraphs that fill their line past
   the viewport (1-3px of sideways scroll at 390). The browser fits the
   stop inside the measure instead. */
:lang(ja) {
    --serif: "Hiragino Mincho ProN", "Yu Mincho", YuMincho, "Noto Serif JP", "Noto Serif CJK JP", var(--latin-serif);
    --sans: "Hiragino Sans", "Hiragino Kaku Gothic ProN", "Yu Gothic", YuGothic, Meiryo, "Noto Sans JP", "Noto Sans CJK JP", var(--latin-sans);
    --leading: 1.8;
    --leading-tight: 1.35;
    line-break: strict;
    word-break: normal;
    hanging-punctuation: none;
}

:is(p, li, dd, blockquote, figcaption):lang(ja) {
    overflow-wrap: anywhere;
}

/* Text in a latin-script language inside an Arabic or Japanese page (an
   English recipe title, its times, a language name in the menu) goes back
   to the latin stacks and sizes: custom properties inherit, so without
   this it would take the script font's latin letters. Zero specificity, so
   a component that sets its own family still wins (a heading asks for
   --serif and gets the latin one here). */
:where([lang]:not(:lang(ar)):not(:lang(ja))) {
    --serif: var(--latin-serif);
    --sans: var(--latin-sans);
    --t-xs: 0.875rem;
    --t-s: 0.9375rem;
    --t-m: 1.0625rem;
    --leading: 1.55;
    --leading-tight: 1.15;
}

/* Where such an element would inherit its family from text in the other
   script (and sets none itself), it takes the text face of its own
   language: English times in an Arabic row, an Arabic recipe's ingredients
   on the English site. */
:where(:is(:lang(ar), :lang(ja)) > [lang]:not(:lang(ar)):not(:lang(ja)),
       :not(:lang(ar)) > [lang]:lang(ar),
       :not(:lang(ja)) > [lang]:lang(ja)) {
    font-family: var(--sans);
}

/* Neither script has italics or letter case: browsers would slant the
   letters, and letter-spacing breaks the joins between Arabic letters and
   crowds or scatters kana and kanji. A guard: the headings' slight
   tightening and the testers' italic notes are undone here, and a later
   rule must not track, slant or uppercase text either. */
:is(:lang(ar), :lang(ja)) {
    letter-spacing: normal !important;
    font-style: normal !important;
    text-transform: none !important;
    font-synthesis: weight;
}

/* Ruled text stays unruled in both scripts: their 1.8 leading doesn't
   match a ruled pitch, and Arabic marks and descenders would cross the
   lines. The text keeps its own language's leading. */
.ruled:is(:lang(ar), :lang(ja)) {
    line-height: var(--leading);
    background-image: none;
}

/* Headings and titles set tight would clip Arabic marks and crowd kanji. */
:is(h1, h2, h3, h4, legend, .hero-title, .recipe-title, .recipe-card-title, .cookbook-item-title, .readout-figure, .divider-name):is(:lang(ar), :lang(ja)) {
    line-height: var(--leading-tight);
}

/* Their recipe titles on a card are a weight heavier: the mincho and the
   naskh are lighter than Besley at the same weight. */
.recipe-card-title:is(:lang(ar), :lang(ja)) {
    font-weight: 700;
}

:is(.btn, .clip, .pill, .divider-tab, .cat-tab):lang(ar) {
    line-height: 1.4;
}

/* Japanese headings break between phrases where the browser can
   (word-break: auto-phrase; ignored elsewhere). */
:is(h1, h2, h3, h4, .hero-title, .recipe-title, .recipe-card-title):lang(ja) {
    word-break: auto-phrase;
}
