/* ==========================================================================
   La Famiglia di Sicilia — shared design system
   Single source of truth for design tokens + shared components.
   Linked in every page's <head> BEFORE its inline <style>, so a page can
   still override locally, but new work should reach for these tokens/classes
   instead of hand-rolling magic numbers.
   ========================================================================== */

/* ── The site's type, self-hosted ────────────────────────────────────────────
   Sixteen woff2 files in static/fonts/, the latin subset of what Google serves.
   Nothing on a page fetches a font from a third party any more.

   This is what stops the flash on a hard reload. The faces used to come from
   fonts.googleapis.com, which is a stylesheet on one host that then points at
   font files on a SECOND host: two DNS lookups, two TLS handshakes and two
   round trips before a glyph exists, all of it after the CSS has already
   arrived. display=swap paints the fallback meanwhile, so the text visibly
   changed once the real faces landed. Same origin, preloaded from the <head>
   (see templates/_fonts.html), the fetch starts with the page rather than after
   it, and on any warm connection the real face is there for the first paint.

   The metric-matched stand-ins below still matter: they cover the window before
   the woff2 lands on a cold connection, which is smaller now but not zero.

   font-display: NEVER block, which hides text outright for up to three seconds.
   Everything with a metric-matched stand-in is `optional`: Cormorant, Hanken,
   Cardo and IM Fell English SC. That rule is the whole policy, and it is
   enforced rather than remembered
   (test_a_face_may_only_be_optional_if_it_has_a_metric_matched_standin).

   OPTIONAL MAKES PRELOADING LOAD BEARING, which is the trap it set on the day
   it shipped. Under swap a face that misses its window merely arrives late.
   Under optional it is held out for the WHOLE page view, so any face something
   paints with and nothing preloads renders in the stand-in and stays there.
   cormorant-600i sets the certificate's NAME and was not preloaded; it rendered
   in a fallback and never recovered, reported with screenshots within the hour.
   If you put a face on optional, preload it wherever it is painted.

   Why optional for those two (changed 2026-aug-08, reported as "the text jumps
   in size on a hard reload"). Under swap the browser paints the stand-in and
   then swaps, and the stand-ins are tuned to match WIDTH so the line breaks and
   page height hold. One size-adjust scalar cannot match width AND height when
   Georgia and Cormorant are proportioned differently, so the whole residual
   lands on apparent size. Measured on production at 100px: width matches to
   97%, but the glyph height is 18% short on both italics and 20% short at
   weight 600. Nothing moves, the letters just grow, which is exactly what was
   reported. Retuning cannot fix it: matching the italic's height would take the
   width to 119%.

   optional removes the swap instead of trying to hide it. The browser gives the
   face a short window; it renders in the real face or it keeps the stand-in for
   that page view and never jumps. The old argument here was that optional lets
   a first-time visitor read the whole page in Georgia. That cost is real but
   smaller than it sounds: it applies only to the first page view on a cold
   cache, every later navigation has the file and makes the window, and the
   stand-in is a 97% width match rather than raw Georgia. A visible jump on
   every hard reload is the worse trade.

   The certificate faces were left on swap at first, on the grounds that they
   had no stand-in. That was the wrong call: it left the sheet's type still
   changing on a hard reload, which is what was reported next. The answer was to
   BUILD the stand-ins rather than accept the swap, so Cardo and IM Fell English
   SC now have them and are optional too. IM Fell English (non-SC) and IM Fell
   Seal stay on swap, since nothing has measured a stand-in for them.

   Declaring all sixteen costs nothing, since a browser fetches a face only when
   something on the page actually draws with it.

   Regenerate with tools/fetch_fonts.py if a weight is added; do not hand-edit a
   URL. The certificate PDF templates are NOT on this stylesheet and never may be
   (see the WeasyPrint note in CLAUDE.md) — they self-host their own TTFs. */
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: normal;
  font-weight: 400;
  font-display: optional;
  src: url('/static/fonts/cormorant-400.woff2') format('woff2');
}
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: normal;
  font-weight: 500;
  font-display: optional;
  src: url('/static/fonts/cormorant-500.woff2') format('woff2');
}
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: normal;
  font-weight: 600;
  font-display: optional;
  src: url('/static/fonts/cormorant-600.woff2') format('woff2');
}
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: normal;
  font-weight: 700;
  font-display: optional;
  src: url('/static/fonts/cormorant-700.woff2') format('woff2');
}
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: italic;
  font-weight: 400;
  font-display: optional;
  src: url('/static/fonts/cormorant-400i.woff2') format('woff2');
}
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: italic;
  font-weight: 500;
  font-display: optional;
  src: url('/static/fonts/cormorant-500i.woff2') format('woff2');
}
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: italic;
  font-weight: 600;
  font-display: optional;
  src: url('/static/fonts/cormorant-600i.woff2') format('woff2');
}
@font-face {
  font-family: 'Hanken Grotesk';
  font-style: normal;
  font-weight: 400;
  font-display: optional;
  src: url('/static/fonts/hanken-400.woff2') format('woff2');
}
@font-face {
  font-family: 'Hanken Grotesk';
  font-style: normal;
  font-weight: 500;
  font-display: optional;
  src: url('/static/fonts/hanken-500.woff2') format('woff2');
}
@font-face {
  font-family: 'Hanken Grotesk';
  font-style: normal;
  font-weight: 600;
  font-display: optional;
  src: url('/static/fonts/hanken-600.woff2') format('woff2');
}
@font-face {
  font-family: 'Hanken Grotesk';
  font-style: normal;
  font-weight: 700;
  font-display: optional;
  src: url('/static/fonts/hanken-700.woff2') format('woff2');
}
@font-face {
  font-family: 'IM Fell English';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/static/fonts/imfell-400.woff2') format('woff2');
}
@font-face {
  font-family: 'IM Fell English';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url('/static/fonts/imfell-400i.woff2') format('woff2');
}
@font-face {
  font-family: 'IM Fell English SC';
  font-style: normal;
  font-weight: 400;
  font-display: optional;
  src: url('/static/fonts/imfellsc-400.woff2') format('woff2');
}
@font-face {
  font-family: 'Cardo';
  font-style: normal;
  font-weight: 400;
  font-display: optional;
  src: url('/static/fonts/cardo-400.woff2') format('woff2');
}
@font-face {
  font-family: 'Cardo';
  font-style: italic;
  font-weight: 400;
  font-display: optional;
  src: url('/static/fonts/cardo-400i.woff2') format('woff2');
}

/* The brand seal's lettering, self-hosted. The mark is in the header of every page
   and while IM Fell English SC is now self-hosted above, a browser fetches a face
   only when something draws with it, and the seal is that something on every page.
   Preloading the full 55 KB face site-wide to set two words would be the wrong
   trade; without this the seal falls to Georgia, a heavier, wider letter that does
   not fit the ring.
   This is the same 5.3 KB subset seal_brand.svg embeds, 14 glyphs, which is the
   whole of "La Famiglia di Sicilia" and nothing else.

   The SUBSET is preloaded, in _fonts.html, and that is not a contradiction of the
   paragraph above: what is a bad trade is queue-jumping 55 KB, not 5.3 KB. Left to
   CSS discovery it landed at 404ms on production while the preloads were in at 87,
   so the seal's motto redistributed around its ring in front of the visitor.

   The family is deliberately NOT called 'IM Fell English SC'. A page that also
   loads the full face from Google would then have two faces under one family name
   with no unicode-range between them, the later declaration would win outright,
   and every other use of the face on that page (the certificate's eyebrow, its
   language labels, its number line) would fall back to the 14 glyphs this file
   has. Its own name keeps the subset to the one element that asks for it. */
@font-face {
  font-family: 'IM Fell Seal';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/static/fonts/IMFellEnglishSC-seal.woff2') format('woff2');
}

/* ── Metric-matched fallbacks: why the text stops jumping on a cold load ──────
   The faces are self-hosted and preloaded now, so on a warm connection there is
   nothing to swap. On a cold one there still is: display=swap paints a local face
   first (block would hide the text outright for up to three seconds), and the page
   re-lays-out when the real one lands. These stand-ins are what makes that
   re-layout invisible rather than a jump.

   That re-layout was severe, and it was measured rather than guessed. At 100px,
   over the same string:

                      width     x-height   ascent  descent
     Cormorant Garamond  3167.68     38.6      92      29
     Georgia             3619.43     48.14     92      22

   So the stand-in was setting 14.3% wider with a 25% larger x-height: every
   heading visibly shrank and re-wrapped the moment the webfont arrived. Hanken
   against Helvetica Neue is within 0.2% and was never the problem, but it is
   declared here too so the rule is one rule.

   size-adjust is taken from the WIDTH ratio, not the x-height ratio, because a
   single scalar cannot match both on a face with a small x-height, and width is
   what moves the layout. Georgia at 87.52% lands within 9% on x-height, which
   reads as a faint change of weight instead of a jump.

   The overrides are divided by size-adjust on purpose: the used value is scaled
   by it, so ascent-override: 105.12% at 87.52% renders Cormorant's own 0.92 em
   ascent, and the line boxes keep their height across the swap.

   Georgia and Helvetica lead the src because they exist on both macOS and
   Windows; Times New Roman and Arial are there for anything that lacks them.
   Re-measure both tables if a typeface is ever swapped.

   ONE FACE PER WEIGHT AND STYLE, and that is not padding. A single scalar
   measured at weight 400 left the hero h1 (73px, weight 500) still shifting
   74px tall and 53px wide, because the stand-in changes face underneath: at 600
   and above the browser reaches for Georgia BOLD, which needs 76.18% where the
   regular needs 87.52%. Every number below was measured in a browser over the
   same string, per weight, per style. Re-measure with a canvas rather than
   editing one by hand. */
@font-face {                    /* upright 400 */
  font-family: 'Cormorant Fallback';
  font-style: normal;
  font-weight: 400;
  src: local('Georgia'), local('Times New Roman'), local('Times');
  size-adjust: 87.52%;
  ascent-override: 105.12%;
  descent-override: 33.14%;
  line-gap-override: 0%;
}
@font-face {                    /* upright 500, the hero h1. Averaging this with 400
                                   left it 0.18% narrow, which is 0.75px on the hero's
                                   measure and enough to pull "Sicily" up a line: a
                                   74px jump from a rounding decision. Exact values. */
  font-family: 'Cormorant Fallback';
  font-style: normal;
  font-weight: 500;
  src: local('Georgia'), local('Times New Roman'), local('Times');
  size-adjust: 87.86%;
  ascent-override: 104.71%;
  descent-override: 33.01%;
  line-gap-override: 0%;
}
@font-face {                    /* upright 600: Georgia Bold stands in, and is much wider */
  font-family: 'Cormorant Fallback';
  font-style: normal;
  font-weight: 600;
  src: local('Georgia'), local('Times New Roman'), local('Times');
  size-adjust: 76.18%;
  ascent-override: 120.77%;
  descent-override: 38.07%;
  line-gap-override: 0%;
}
@font-face {                    /* upright 700 */
  font-family: 'Cormorant Fallback';
  font-style: normal;
  font-weight: 700;
  src: local('Georgia'), local('Times New Roman'), local('Times');
  size-adjust: 82.63%;
  ascent-override: 108.92%;
  descent-override: 30.26%;
  line-gap-override: 0%;
}
@font-face {                    /* italic, 400 and 500: the leads and pull quotes */
  font-family: 'Cormorant Fallback';
  font-style: italic;
  font-weight: 400 500;
  src: local('Georgia Italic'), local('Georgia'), local('Times New Roman Italic');
  size-adjust: 78.79%;
  ascent-override: 116.77%;
  descent-override: 36.81%;
  line-gap-override: 0%;
}
@font-face {                    /* italic, 600 and 700 */
  font-family: 'Cormorant Fallback';
  font-style: italic;
  font-weight: 600 700;
  src: local('Georgia Italic'), local('Georgia'), local('Times New Roman Italic');
  size-adjust: 80.46%;
  ascent-override: 111.86%;
  descent-override: 31.07%;
  line-gap-override: 0%;
}
@font-face {                    /* Hanken 400: within 0.2%, and never was the problem */
  font-family: 'Hanken Fallback';
  font-weight: 400;
  src: local('Helvetica Neue'), local('Helvetica'), local('Arial');
  size-adjust: 99.79%;
  ascent-override: 100.21%;
  descent-override: 30.06%;
  line-gap-override: 0%;
}
@font-face {                    /* Hanken 500 picks up Helvetica Neue Medium, which is wider */
  font-family: 'Hanken Fallback';
  font-weight: 500;
  src: local('Helvetica Neue'), local('Helvetica'), local('Arial');
  size-adjust: 88.92%;
  ascent-override: 101.22%;
  descent-override: 28.12%;
  line-gap-override: 0%;
}
@font-face {                    /* Hanken 600 and 700: the nav links, buttons and CTAs */
  font-family: 'Hanken Fallback';
  font-weight: 600 700;
  src: local('Helvetica Neue'), local('Helvetica'), local('Arial');
  size-adjust: 95.64%;
  ascent-override: 104.56%;
  descent-override: 31.37%;
  line-gap-override: 0%;
}

/* Stand-ins for the CERTIFICATE's two faces, added 2026-aug-08.

   Reported as the certificate's type still changing on a hard reload after the
   faux-bold fix, and correctly: that fix stopped app.css bolding the top line,
   it did not stop the SWAP. Cardo and IM Fell were left on font-display: swap
   because the rule is that a face may only be optional where a metric-matched
   stand-in covers it, and neither had one. So the answer was to build them
   rather than to leave the swap.

   Measured in the browser against Georgia, on a mixed string at 100px:
     Cardo 400    real 3316.0 / Georgia 3424.6  -> 96.83%
     Cardo 400i   real 3097.4 / Georgia 3467.0  -> 89.34%
     IM Fell SC   real 3346.6 / Georgia 3424.6  -> 97.72%
   ascent/descent are the real face's own metrics as a share of the em.

   These are much closer than the Cormorant pair (87.52%), because both faces
   are old-style serifs with Georgia-ish proportions, so the residual apparent
   size difference is far smaller than the one that made the body text pop.

   They matter for the PAGE only. certificate_pdf.html and certificate_print.html
   do not link this stylesheet, so under WeasyPrint these names resolve to
   nothing and the stack falls through to the self-hosted TTFs exactly as before.
   Guarded by test_a_face_may_only_be_optional_if_it_has_a_metric_matched_standin. */
@font-face {
  font-family: 'Cardo Fallback';
  font-style: normal;
  font-weight: 400;
  src: local('Georgia'), local('Times New Roman'), local('Times');
  size-adjust: 96.83%;
  ascent-override: 99%;
  descent-override: 36%;
  line-gap-override: 0%;
}
@font-face {
  font-family: 'Cardo Fallback';
  font-style: italic;
  font-weight: 400;
  src: local('Georgia Italic'), local('Georgia'), local('Times New Roman Italic');
  size-adjust: 89.34%;
  ascent-override: 99%;
  descent-override: 36%;
  line-gap-override: 0%;
}
@font-face {
  font-family: 'IM Fell Fallback';
  font-style: normal;
  font-weight: 400;
  src: local('Georgia'), local('Times New Roman'), local('Times');
  size-adjust: 97.72%;
  ascent-override: 91%;
  descent-override: 36%;
  line-gap-override: 0%;
}

:root {
  /* Declare the design's colour scheme so the browser does NOT force-darken it.
     Without this, Chrome's Auto Dark Mode (on by default for many Android users
     when the system theme or battery saver is dark) re-renders the whole site
     inverted: the cream palette goes near-black and the transparent hero nav
     loses its contrast, so the header reads as missing while scrolling. The
     site chooses its own phase from the clock (see templates/_theme_init.html)
     and this rule states what the DAY phase is; the browser has no way to know
     that unless we say so here, and a forced inversion on top of our own dark
     phases is exactly the double-darkening we do not want.
     The `only` keyword is load-bearing: plain `light` still lets Chrome's Auto
     Dark Theme darken the page, whereas `only light` is the explicit opt-out.
     The [data-theme="dark"] override below keeps the real toggle working: when
     the flag is on and dark is chosen, the scheme flips to dark for real. */
  color-scheme: only light;

  /* ── palette ─────────────────────────────────────────────── */
  --c-bg:            #f3ead6;   /* page background (cream) */
  --c-surface:       #fff;      /* input / raised surface */
  --c-card:          #ede3cc;   /* card / note background */
  --c-text:          #1a1e16;   /* primary text */
  --c-text-body:     #4a443c;   /* body copy */
  --c-text-muted:    #8a8073;   /* secondary / meta */
  --c-label:         #5a5a52;   /* uppercase field labels */
  --c-accent:        #9c3a26;   /* terracotta: button and badge grounds */
  --c-accent-dark:   #7e2e1e;   /* terracotta hover */
  /* Terracotta as TEXT (eyebrows, inline links). Identical to --c-accent in
     daylight, but it has to lift in the dark phases: one colour cannot both
     carry cream button text and read as text on a near-black ground, and
     #9c3a26 on the late-night ground is 2.1:1. Use this for accent-coloured
     type, --c-accent for anything type sits on top of. */
  --c-accent-text:   #9c3a26;
  --c-gold:          #cba24e;   /* gold accent */
  /* The brand mark's ink ON A DARK GROUND, rings and lettering. Declared once
     here and deliberately NOT overridden in either dark phase, which is the one
     place that rule is right: the footer band is dark in daylight too, so a
     value that moved with the clock would drift the footer seal against a footer
     that did not move. Both carriers read these (the header only under
     [data-theme="dark"], the footer always), so the mark cannot come to be two
     colours on one screen again, which is what it was until 2026-aug-05: the
     header followed --c-text to cream at night while the footer stayed gold. */
  --c-seal-dark:      #c9a24e;  /* the mark's rings, on dark */
  --c-seal-dark-type: #d8c48a;  /* the mark's lettering, on dark */
  --c-cream:         #f6efdc;   /* light text on dark */
  --c-ink:           #1a1e16;   /* dark button / footer ink */
  --c-border:        #d4c9b4;   /* input / pill border */
  --c-border-dot:    #c4b9a8;   /* radio dot ring */
  --c-hairline:      rgba(26,30,22,.12); /* dividers */
  --c-placeholder:   #b0a898;
  --c-err:           #9c3a26;

  /* shop-kit's partials (vendor/shopkit, shopkit/templates) read these; the
     full list is shopkit.templates.TOKENS and the suite asserts every one is
     declared here. Fixed inks, the values this shop's own copies carried, and
     not restated in the dark phases: the zoom overlay and the buy bar look the
     same at every hour (the bar's dark-phase colours come from the kit's own
     [data-theme="dark"] rule, through tokens). */
  --c-buy-ground:    #1a1e16;   /* the sticky buy bar's button */
  --c-buy-ink:       #f6efdc;
  --c-zoom-backdrop: rgba(12,13,10,.93);
  --c-zoom-chip:     rgba(12,13,10,.88);
  --c-zoom-chip-ink: #f3ead6;
  --c-zoom-ring:     rgba(243,234,214,.4);
  --c-zoom-paper:    #fff;
  --c-zoom-btn-bg:   rgba(250,248,244,.94);
  --c-zoom-btn-hover:#fff;
  --c-zoom-btn-ink:  #1a1e16;
  --c-zoom-btn-line: rgba(26,30,22,.16);

  /* ── typography ──────────────────────────────────────────── */
  /* The fallback families carry size-adjust and the metric overrides declared
     above, so they must sit between the webfont and the plain system name.
     Every font-family in the site goes through these two tokens for that
     reason; a hand-written 'Cormorant Garamond', serif stack skips the
     stand-in and brings the jump back on that element alone, which is the
     hardest kind to spot. Guarded by test_every_font_stack_goes_through_the_tokens. */
  --font-serif: 'Cormorant Garamond', 'Cormorant Fallback', Georgia, serif;
  --font-sans:  'Hanken Grotesk', 'Hanken Fallback', system-ui, -apple-system, sans-serif;
  /* Each face's own `line-height: normal`, (hhea ascent + descent + gap) / UPM,
     read off the woff2 files. Every rule that names a font token names its line
     height beside it, so a line box is the same height in the real face and in
     its stand-in (WebKit ignores the stand-ins' ascent/descent overrides).
     Kit RULES §21c/§23, guarded by test_every_font_token_carries_its_line_height. */
  --lh-serif: 1.211;
  --lh-sans: 1.303;

  --fs-eyebrow: 11px;   /* uppercase eyebrow / field label */
  --fs-meta:    12px;
  --fs-pill:    13.5px; /* radio pill text */
  --fs-body:    15px;   /* input / body */
  --fs-lead:    17px;

  --ls-eyebrow: .16em;  /* field labels */
  --ls-wide:    .22em;  /* section eyebrows */
  --ls-btn:     .1em;

  /* ── spacing scale (vertical rhythm) ─────────────────────── */
  --sp-1: 4px;
  --sp-2: 8px;
  --sp-label: 9px;      /* label → control */
  --sp-3: 12px;
  --sp-4: 16px;
  --sp-field: 22px;     /* control → next label (form rhythm) */
  --sp-5: 28px;
  --sp-6: 36px;
  --sp-7: 44px;
  --sp-8: 56px;
  --sp-9: 80px;

  /* ── radii ───────────────────────────────────────────────── */
  --radius-sm:   2px;   /* inputs, buttons */
  --radius-img:  3px;
  --radius-card: 4px;
  --radius-pill: 30px;

  /* ── layout breakpoint ───────────────────────────────────── */
  --bp-stack: 1040px;
}

/* ── dark-mode token overrides ─────────────────────────────── */
[data-theme="dark"] {
  /* Real dark mode (ENABLE_THEME_TOGGLE on + dark chosen). Flipping the scheme
     here, not on :root, is what lets the light default opt out of Chrome's
     forced auto-darkening without disabling genuine dark mode. */
  color-scheme: dark;

  --c-bg:          #0f1209;
  --c-surface:     #181f13;
  --c-card:        #1c2117;
  --c-text:        #e4dac4;
  --c-text-body:   #bab29a;
  --c-text-muted:  #85837a;   /* 5.0:1 on the evening ground */
  --c-label:       #85837a;
  --c-border:      #2e3829;
  --c-border-dot:  #3a4035;
  --c-hairline:    rgba(228,218,196,.15);
  --c-placeholder: #5a6052;
  --c-err:         #e07060;
  --c-accent-text: #cf7a4a;   /* 5.9:1 on the evening ground */
  --c-pill-text:   #c4b99a;   /* option pills; a hex here kept the late tier from reaching them */
}

/* ── late-night tier ───────────────────────────────────────
   Set from 23:00 to 06:00 alongside data-theme="dark" (see
   templates/_theme_init.html). Evening dark is still a lit room; this is the
   one read in bed, so the ground drops further, the cream text comes down off
   full brightness, and the terracotta and gold lose their glare. Only tokens
   are re-stated, so every page that themes through them follows automatically.
   Contrast stays above 4.5:1 on body copy (#a49c85 on #070903 is ~9.4:1). */
[data-theme="dark"][data-night="late"] {
  --c-bg:          #070903;
  --c-surface:     #101509;
  --c-card:        #12170d;
  --c-text:        #cfc4a8;
  --c-text-body:   #a49c85;
  --c-text-muted:  #7d7a6e;   /* 4.8:1 on the late ground */
  --c-label:       #7d7a6e;
  --c-accent:      #8a3421;
  --c-accent-dark: #6e2a19;
  --c-accent-text: #c06f43;   /* 5.5:1 on the late ground */
  --c-gold:        #b08f45;
  --c-cream:       #e6dcc4;
  --c-border:      #232b1f;
  --c-border-dot:  #2c3128;
  --c-hairline:    rgba(207,196,168,.12);
  --c-placeholder: #4b5045;
  --c-err:         #cf6555;
  --c-pill-text:   #ada285;   /* the evening pill ink, stepped down as --c-text-body is */
}

/* Photography carries its own brightness, so tokens can't dim it. Ease it down
   in the dark phases or a full-bleed hero lights the whole screen back up. */
[data-theme="dark"] .hero-bg,
[data-theme="dark"] .story-fig img,
[data-theme="dark"] .img-stack img,
[data-theme="dark"] .pack-card img,
[data-theme="dark"] .product-card img { filter: brightness(.82) saturate(.94); }
[data-theme="dark"][data-night="late"] .hero-bg,
[data-theme="dark"][data-night="late"] .story-fig img,
[data-theme="dark"][data-night="late"] .img-stack img,
[data-theme="dark"][data-night="late"] .pack-card img,
[data-theme="dark"][data-night="late"] .product-card img { filter: brightness(.62) saturate(.86); }

/* The shared footer is a fixed dark band in every phase, so it has to keep
   sinking below the page ground or it glows at night. Scoped to a direct child
   of body: collections.html has a second, legitimate <footer> (the citation
   inside the Lampedusa quote) that must not pick this up. */
[data-theme="dark"] body > footer { background: #0b100c !important; }
[data-theme="dark"][data-night="late"] body > footer { background: #050803 !important; }
/* Its bottom bar is styled for the lighter #161c18 band, where #5a6052 reads at
   4.6:1; on the sunken ground it falls to 3.1:1, so lift it with the ground. */
[data-theme="dark"] body > footer .footer-bottom { color: #808777; }

/* The clock can flip the phase under a page that is already open, so fade the
   change rather than snapping it. Load is unaffected: the attribute is set
   before first paint. */
html, body { transition: background-color .8s ease, color .8s ease; }
@media (prefers-reduced-motion: reduce) {
  html, body { transition: none; }
}

/* ==========================================================================
   Shared components
   ========================================================================== */

/* ── divider ─────────────────────────────────────────────── */
.divider {
  border: none;
  border-top: 1.5px solid var(--c-hairline);
  margin: 18px 0;
}

/* ── uppercase eyebrow label (section kicker) ────────────── */
.eyebrow {
  display: block;
  font-size: var(--fs-eyebrow);
  font-weight: 700;
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  color: var(--c-accent-text);
  margin-bottom: var(--sp-3);
}

/* ── form field label ────────────────────────────────────── */
.field-label {
  display: block;
  font-size: var(--fs-eyebrow);
  font-weight: 700;
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--c-label);
  margin-bottom: var(--sp-label);
}

/* ── text input ──────────────────────────────────────────── */
.field {
  display: block;
  width: 100%;
  padding: 12px 14px;
  border: 1.5px solid var(--c-border);
  border-radius: var(--radius-sm);
  background: var(--c-surface);
  font-size: var(--fs-body);
  font-family: inherit;
  color: var(--c-text);
  margin-bottom: var(--sp-field);
  transition: border-color .15s;
}
.field:focus       { outline: none; border-color: var(--c-accent); }
.field::placeholder { color: var(--c-placeholder); }

/* ── radio pills (circle-dot + label) ────────────────────── */
.radio-group {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: var(--sp-field);
}
.radio-opt { cursor: pointer; }
.radio-opt input { display: none; }
.radio-opt span {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 18px 9px 12px;
  border: 1.5px solid var(--c-border);
  border-radius: var(--radius-pill);
  font-size: var(--fs-pill);
  font-weight: 500;
  color: #3a3830;
  transition: all .15s;
  white-space: nowrap;
}
[data-theme="dark"] .radio-opt span { color: var(--c-pill-text); }
.radio-opt span::before {
  content: '';
  display: block;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  border: 1.5px solid var(--c-border-dot);
  flex-shrink: 0;
  transition: all .15s;
}
.radio-opt input:checked ~ span {
  background: var(--c-accent);
  border-color: var(--c-accent);
  color: var(--c-cream);
}
.radio-opt input:checked ~ span em { color: var(--c-cream) !important; }
.radio-opt input:checked ~ span::before {
  background: #fff;
  border-color: #fff;
  box-shadow: inset 0 0 0 3px var(--c-accent);
}

/* ── button (full-width dark CTA; .btn--accent for terracotta) ─ */
.btn {
  display: block;
  width: 100%;
  padding: 17px;
  background: var(--c-ink);
  color: var(--c-cream);
  border: 2px solid var(--c-ink);
  border-radius: var(--radius-sm);
  font-family: inherit;
  font-weight: 700;
  font-size: 14px;
  letter-spacing: var(--ls-btn);
  text-transform: uppercase;
  cursor: pointer;
  transition: background .15s, color .15s;
}
.btn:hover    { background: var(--c-accent); border-color: var(--c-accent); }
.btn:disabled { opacity: .55; cursor: not-allowed; }
[data-theme="dark"] .btn        { background: var(--c-accent); border-color: var(--c-accent); }
[data-theme="dark"] .btn:hover  { background: var(--c-accent-dark); border-color: var(--c-accent-dark); }

/* ── responsive: long-labelled pickers stack full-width ──── */
@media (max-width: 1040px) {
  .radio-group--stack {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--sp-2);
  }
  .radio-group--stack .radio-opt span {
    width: 100%;
    box-sizing: border-box;
  }
}

/* ── Sicily outline motif ─────────────────────────────────
   Inline via templates/_sicily.html (currentColor = stroke).
   Wrap in .sicily-mark and set width/height + color, or use
   a modifier below. */
.sicily-outline { display: block; width: 100%; height: auto; overflow: visible; }

/* faint decorative watermark behind a section */
.sicily-watermark {
  position: absolute;
  pointer-events: none;
  color: var(--c-gold);
  opacity: .10;
  z-index: 0;
}
.sicily-watermark .sicily-outline { stroke-width: 4; }

/* small inline emblem next to a wordmark or heading */
.sicily-emblem { display: inline-block; color: var(--c-gold); vertical-align: middle; }

/* ── Nothing may scroll sideways ─────────────────────────────────────────────
   The site is single-column: there is no horizontal content and therefore no
   horizontal scroll that is ever correct. A phone that pans left and right feels
   broken, and it hides the right edge of the layout while it does it.

   Most pages already set `overflow-x:hidden` on <body> and it was not enough,
   which is the point of putting the rule here instead. Two reasons it failed:
   several pages never had the declaration at all (a per-page copy, drifted, the
   same shape as the nav and footer before they were unified), and body-level
   clipping is honoured by desktop Chrome but NOT reliably by iOS Safari, which
   still lets the visual viewport pan. So the real fix is upstream — the one
   element that actually overflowed, the homepage island watermark, now sits in a
   clipping section — and this is the backstop for whatever gets added next.

   **`clip`, never `hidden`.** `overflow:hidden` makes the element a scroll
   container, and a scroll container between the viewport and `.std-nav` breaks
   `position:sticky`, so the header would stop sticking on every page. `clip`
   forbids scrolling without creating that container, which is exactly the
   difference wanted here. Guarded by test_no_page_can_be_swiped_sideways, which
   asserts the sticky header survives as well as the clipping. */
/* Form controls do not inherit line-height: the browser's own stylesheet gives
   them `normal`, the face's metric again. Inherit the token like everything else. */
button, input, select, textarea { line-height: inherit; }

html, body {
  overflow-x: clip;
  max-width: 100%;
}
