/* =====================================================================
   ZERO WEB — DO NOT EDIT WITHOUT CEO DISCUSSION.
   Every value in this file is a Zero design decision. A mismatch with
   brand.md does not license fixing either side; report the mismatch.

   CANONICAL FILE: zero-industries/00_exec/parts/brand/web.css — beside
   brand.md. zero.space/assets/web.css is a GENERATED copy made by
   scripts/brand/sync.py. Edit only the canonical; a copy that differs is a defect.
   One session edits the canonical at a time; if unsure that is you, it is not.
   ===================================================================== */
/* =====================================================================
   Zero Web — implementation canon.
   Spec: zero-industries/00_exec/parts/brand/brand.md §7.3
   One family (Geist), two weights (500/300), three declared sizes plus the
   key title, tracking and 0.8 leading on ALL CAPS only, 1.2 everywhere else,
   margins assigned by frame — screen (narrow) / page (wide) / nested (--r-pad),
   one radius (24) with one nested gap (14), no lines, no shadows, no animation. Blur only.
   Colors are per-brand; everything else is identical across all sites.
   ===================================================================== */

:root {
  /* margins: the screen frame / the page frame (30 / 100 at 1920px).
     §2 assigns by which frame a thing answers to, not by what kind of thing
     it is — chrome and the full-screen hero take the screen frame, and the
     page's contents all take the page frame. The third relation, nesting,
     is --r-pad below. */
  --m-narrow: clamp(16px, 1.6vw, 30px);
  --m-wide: clamp(24px, 5.2vw, 100px);
  --m-v: clamp(32px, 7vh, 72px);

  /* two faces of one family: Sans is prose, Mono is record */
  /* Geist draws Latin, Greek and Cyrillic. For the scripts it does not draw,
     Zero Web does not pick a second family — it hands the script to the face
     that platform already treats as standard for it (CEO 2026-09-05, the
     approval §2 was waiting on). Fallback is per character, so Latin never
     reaches past Geist and the rule "one family" still holds where Geist can
     hold it. Naming the faces rather than leaning on system-ui is deliberate:
     system-ui follows the reader's locale, not the page's, so a Japanese page
     on an English machine would otherwise fall to a face chosen for English. */
  --font: "Geist",
    "Hiragino Sans", "Yu Gothic", "Noto Sans JP",
    "PingFang SC", "Microsoft YaHei", "Noto Sans SC",
    "Kohinoor Devanagari", "Nirmala UI", "Noto Sans Devanagari",
    "Kohinoor Bangla", "Noto Sans Bengali",
    "Kohinoor Telugu", "Noto Sans Telugu",
    "Geeza Pro", "Noto Sans Arabic",
    -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Arial, sans-serif;
  --font-mono: "Geist Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  /* type scale */
  --fs-body: clamp(20px, 1.85vw, 35px);
  --fs-caption: clamp(12px, 0.95vw, 16px);
  --fs-nav: 13px;                            /* chrome, not content: recessive by size and weight */
  --fs-input: 16px;                          /* a field's text. 16 is not taste: below it iOS zooms the page on focus */
  --fs-big: clamp(22px, 1.7vw, 30px);        /* the dropdown's list links */
  --fs-head: clamp(28px, 3.2vw, 60px);       /* a section's name in the body: above a subtitle, below a page title */
  --fs-display: clamp(38px, 8.6vw, 190px);   /* a page title — fills the text frame */
  --fs-display-xl: clamp(56px, 12vw, 260px); /* a title that is the whole page (404) */
  --fs-display-long: clamp(22px, 2.6vw, 52px); /* a full statement carrying the key-title role */
  --fs-menu: clamp(24px, 6.4vw, 32px);       /* the phone menu's rows */
  --fs-menu-sub: clamp(18px, 4.6vw, 22px);   /* the rows under an opened one */

  /* the pill: one line of caption with this much air round it. A field, an
     arrow and a button all measure from it, so their words stand on one
     inset and their shapes on one height. */
  --pill-y: 11px;
  --pill-x: 22px;

  /* tracking: ALL CAPS key titles only. Capitals are drawn to sit beside
     capitals, so they need closing up; lower-case text does not. */
  --track: -0.04em;                          /* key titles only: optical correction for display sizes */

  /* ---- colour, in three layers ----
     1. absolute — raw values with no meaning of their own. Never swap.
     2. role     — what a thing is for. These swap with the mode.
     3. media    — the plate faces. They follow the brightness of the photo
                   underneath, not the mode, so they are built from the
                   absolutes and never swap. A dark photo needs a dark plate
                   in light mode and in dark mode alike. */

  /* 1. absolute */
  --white: #ffffff;
  --black: #000000;
  --gray: #808080;    /* one value serves both modes: 4.0:1 on white, 5.3:1 on black */

  /* 2. role — light mode */
  --bg: var(--white);
  --fg: var(--black);
  --link: #0000ee;                           /* external links only (leaving this site) */
  --chrome: rgba(255, 255, 255, 0.72);       /* nav, dropdown, cookie: glass over the page */
  --chrome-open: rgba(255, 255, 255, 0.94);  /* the same glass, open: bar and dropdown together, nearly opaque */
  --veil: rgba(255, 255, 255, 0.22);         /* page dim behind a dropdown */
  --glass-blur: blur(12px) saturate(1.4);    /* frosted: the recipe before liquid, and the reader's alternative */
  /* ---- glass: everything that is glass is liquid glass (CEO 2026-09-11) ----
     Two strengths by role, as the platform draws them. CLEAR for the floating
     chrome and the controls — thin enough to show the page moving under it,
     the chrome's ink following the backdrop (web.js). REGULAR for the plates,
     which carry reading and keep their own tint. Both bend the backdrop at
     the rim (web.js, Chromium) and are lit along the edge. A reader can choose
     frosted glass instead (Appearance → data-glass="frosted"): the block after
     this one puts back the single recipe Zero Web had before. */
  --liquid: 16%;
  --glass-liquid: blur(2px) saturate(1.8);          /* clear */
  --glass-liquid-regular: blur(10px) saturate(1.8); /* regular: whatever carries words — the bar, the cookie note, the plates */
  --glass-rim: inset 0 1px 0 color-mix(in srgb, var(--white) 80%, transparent),
               inset 0 -1px 0 color-mix(in srgb, var(--white) 30%, transparent),
               inset 0 0 0 0.5px color-mix(in srgb, var(--white) 40%, transparent);
  --glass-lift: 0 10px 30px color-mix(in srgb, var(--black) 16%, transparent);    /* the chrome, floating */
  --glass-lift-sm: 0 2px 10px color-mix(in srgb, var(--black) 10%, transparent);  /* a control, just off the page */
  /* what each role draws with — the only names components use, so the
     frosted block swaps the whole material by swapping these */
  --chrome-surface: color-mix(in srgb, var(--bg) var(--liquid), transparent);
  /* Regular, not clear: the bar and the cookie note carry words, and at 2px
     the words behind them stayed legible and collided with their own (the
     cookie note over a card title, 2026-09-11). Clear is for the controls,
     which carry a word or two and seldom sit over text. */
  --chrome-filter: var(--glass-liquid-regular);
  --chrome-shadow: var(--glass-rim), var(--glass-lift);
  /* Glass has no colour of its own (CEO 2026-09-11, "リキッドグラスに色つけたら
     あかん"): a control is the page's own colour, thinly — white on white,
     black on black — so what shows is the rim, the shadow and what is behind.
     It was grey until then, the one tinted thing in the glass. */
  --control: color-mix(in srgb, var(--bg) var(--liquid), transparent);
  --control-filter: var(--glass-liquid);
  --control-shadow: var(--glass-rim),
                    0 0 0 0.5px color-mix(in srgb, var(--black) 8%, transparent),   /* the glass's own edge, so it holds on white */
                    var(--glass-lift-sm);
  /* pressed: a colourless thing cannot darken, so it sinks — the lift gives
     way to a shadow inside the rim. Held, it is the one in force (§3.0). */
  --control-pressed: inset 0 1px 3px color-mix(in srgb, var(--black) 22%, transparent),
                     inset 0 0 0 0.5px color-mix(in srgb, var(--black) 10%, transparent);
  --plate-filter: var(--glass-liquid-regular);
  --plate-shadow: var(--glass-rim);

  /* motion: three durations and one curve. The curve never overshoots —
     a bounce is decoration, and decoration is not a reason to move. */
  --bar: 48px;                               /* the bar's height; the page and the hero both measure from it */
  --t-fast: 90ms;                            /* interaction feedback */
  --t-page: 220ms;                           /* anything that opens, closes, or crosses */
  --t-media: 700ms;                          /* one image giving way to the next */
  --ease: cubic-bezier(0.4, 0, 0.2, 1);      /* feedback */
  --ease-out: cubic-bezier(0.32, 0.72, 0, 1); /* opening: leaves and settles without a snap */
  /* the bar's stretch: a spring that runs a few percent past its mark and
     settles back (quicker and softer after CEO review, same day) — the soft overshoot reads as a thing with give, not a
     panel on rails (CEO 2026-09-10, "like slime"). linear() is the curve
     sampled, so it needs no script and no second library. */
  --ease-goo: linear(0, 0.2 7%, 0.58 16%, 0.88 26%, 1.02 36%, 1.04 44%, 1.03 54%, 1.01 66%, 1 80%, 1);
  --t-goo: 420ms;                            /* a spring needs longer than a slide to settle */

  /* Everything is round except a corner that meets the screen. A thing placed
     on its own takes --r; a thing inside a thing takes the concentric
     --r-inner, because a frame has to hold an even thickness the whole way
     round. The photograph is not the exception it used to be — it is round
     too, and the plate on it is the nested case. Anything meeting the screen
     edge stays square there: a curve against the screen's own corner reads as
     a chip. A button goes further and takes the full pill, which is its own
     shape rather than a second radius. */
  --r: 24px;
  --r-pill: 999px;
  --r-pad: clamp(8px, 1.1vw, 14px);          /* one gap, wherever a thing sits inside a thing — proportional to the frame (2026-09-10) */
  --r-inner: calc(var(--r) - var(--r-pad));  /* nested: concentric with the curve around it */
  --tint: color-mix(in srgb, var(--gray) 16%, var(--bg));       /* a control at rest */
  --tint-deep: color-mix(in srgb, var(--gray) 34%, var(--bg));  /* a card under the pointer */
  /* the awaited photograph: the same 18 points of grey it has always been,
     laid over the slot's own ground instead of the page's — over the bare
     page nothing moves, and inside a card's tint the held space finally
     reads, in both modes. The frame is made the way every frame here is
     made — a surface with a concentric corner — never a line (§3). */
  --ph: color-mix(in srgb, var(--gray) 18%, transparent);

  /* 3. media — fixed in both modes */
  --plate-light: rgba(255, 255, 255, 0.72);
  --plate-dark: rgba(0, 0, 0, 0.30);
  --ink-on-light: var(--black);
  --ink-on-dark: var(--white);
  --tint-on-dark: color-mix(in srgb, var(--ink-on-dark) 18%, transparent);       /* a control at rest on a dark plate */
}

/* Frosted glass, by the reader's choice (Appearance) or by their system's
   request for less transparency: one thick recipe, no rim, no bend, and the
   chrome's ink stays the page's (web.js leaves it alone). */
:root[data-glass="frosted"] {
  --chrome-surface: var(--chrome); --chrome-filter: var(--glass-blur); --chrome-shadow: none;
  --control: var(--chrome); --control-filter: var(--glass-blur); --control-shadow: var(--glass-lift-sm);
  --plate-filter: var(--glass-blur); --plate-shadow: none;
}
@media (prefers-reduced-transparency: reduce) {
  :root {
    --chrome-surface: var(--chrome); --chrome-filter: var(--glass-blur); --chrome-shadow: none;
    --control: var(--chrome); --control-filter: var(--glass-blur); --control-shadow: var(--glass-lift-sm);
    --plate-filter: var(--glass-blur); --plate-shadow: none;
  }
}

/* The mode has three states: an explicit choice stamps data-theme on the root,
   and the default stamps nothing — so the media query has to carry the
   un-stamped case while still losing to an explicit light choice. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: var(--black);
    --fg: var(--white);
    --link: #8f9cff;                         /* the light-mode blue is 2.2:1 on black */
    --chrome: rgba(0, 0, 0, 0.72);
    --chrome-open: rgba(0, 0, 0, 0.94);
    --veil: rgba(0, 0, 0, 0.22);
  }
}
:root[data-theme="dark"] {
  --bg: var(--black);
  --fg: var(--white);
  --link: #8f9cff;
  --chrome: rgba(0, 0, 0, 0.72);
  --chrome-open: rgba(0, 0, 0, 0.94);
  --veil: rgba(0, 0, 0, 0.22);
}

/* selected text is ink on page, the same two colours everything else uses.
   Written with the role tokens, so it flips with the mode without a second
   declaration, and it holds on a dark plate where the text is already white. */
/* A brand may paint selection in its own colour (Zero: BEAM with black ink,
   CEO 2026-09-10 — the one place its accent fills an area, because a 14%
   tint of it could not be seen as a selection). Declared in the site's
   shell, never here: colours are per brand. */
::selection { background: var(--selection, var(--fg)); color: var(--selection-ink, var(--bg)); }
::-moz-selection { background: var(--selection, var(--fg)); color: var(--selection-ink, var(--bg)); }

:root { interpolate-size: allow-keywords; }
*, *::before, *::after { box-sizing: border-box; }

html { scroll-behavior: smooth; }  /* anchor jumps; the wheel is smoothed in JS */

/* a short page still closes with its signature at the bottom edge. This was
   written into page-level <style> blocks and nowhere on
   zero.space, so contact/ left 323px of blank under the footer on a tall
   screen. Layout rhythm is unified (§1), so it belongs here. */
body {
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--font);
  font-weight: 300;
  font-size: var(--fs-body);
  letter-spacing: normal;
  line-height: 1.2;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* ---- roles (spec §2.0) ---- */

.display {
  /* key title: CAPS + Medium + 0.8; the text frame spans the full
     width inside the page frame (--m-wide) and the size fills the frame */
  font-weight: 500;
  line-height: 0.8;
  letter-spacing: var(--track);
  text-transform: uppercase;
  font-size: var(--fs-display);                  /* page title — fills the text frame */
  margin: 0;
  /* §3 leaves wrapping to the frame — but a frame can only wrap between words,
     and some languages have words longer than the frame. At 375px the German
     "Datenschutzerklärung" measured 464px against a 327px frame and simply ran
     off the page; Russian did the same (found 2026-09-05, in 6 of 225 checks).
     `hyphens: auto` lets the browser break the word by that language's own
     rules — the same principle, extended to words the frame cannot hold. The
     <html lang> the build already writes is what selects those rules.
     `overflow-wrap` is the floor for languages the browser cannot hyphenate. */
  overflow-wrap: break-word;
  hyphens: auto;
}
.display--xl { font-size: var(--fs-display-xl); }    /* a title that is the whole page (404) */

h1, h2, h3, h4, h5, h6 { font-size: inherit; font-weight: inherit; margin: 0; }
.label { font-weight: 500; font-size: var(--fs-body); }

.body { font-weight: 300; }

.caption { font-size: var(--fs-caption); font-weight: 300; }

/* ---- Mono: the register of record ----
   Not a style but a register. Prose is set in Sans; anything that is a
   recorded fact rather than a sentence is set in Mono — quantities, units,
   dates, identifiers, and the registered company name. Same family, so the
   two faces share proportions and the page still reads as one system.
   Mono is never used for a sentence, a heading, or navigation. */
/* Mono has exactly one weight. It carries no hierarchy — a record is neither
   emphasised nor faint — so it needs none. Regular, not the Light the prose
   uses: at caption size the Light mono goes fragile, and 500 makes a date
   shout. Only 400 is loaded, so no other weight can be reached by accident. */
.num, time, .stamp {
  font-family: var(--font-mono);
  font-weight: 400;
  font-variant-numeric: tabular-nums;
  text-transform: uppercase;
}
/* A unit symbol carries its case as meaning, so it opts out of the transform:
   kW is a kilowatt and KW is not an SI symbol at all, and mW and MW differ by
   a factor of a billion but render identically once uppercased. Wrap every
   unit — the record stays exact where being exact is the whole point. */
.unit { text-transform: none; }

/* ---- interaction: two tiers ----
   PRIMARY carries a surface. At rest a grey plane holding ink; on contact the
   plane deepens and the words hold still. One motion for everything that can
   be pressed — a button and a card behaving differently reads as two systems.
   SECONDARY carries none. Ink at rest, grey on contact. Used for getting
   around — navigation, the footer, links inside prose. A bar of filled pills
   shouts louder than the thing it is a route to, so movement stays quiet. */
.btn {
  display: inline-flex;
  align-items: center;
  border: none;
  /* a pill is a piece of clear glass (CEO 2026-09-11): no colour of its
     own, lit along its rim and lifted just off the page */
  background: var(--control);
  -webkit-backdrop-filter: var(--control-filter);
  backdrop-filter: var(--control-filter);
  box-shadow: var(--control-shadow);
  color: var(--fg);
  cursor: pointer;
  transition: box-shadow var(--t-fast) linear;
  /* its shape: the full pill, one size for every pill (2026-09-10 — there
     were two, and the larger read as too big for its words). Stated here with
     the tier; it used to be a second `.btn` rule 500 lines further down. */
  padding: var(--pill-y) var(--pill-x);
  border-radius: var(--r-pill);
  font-family: inherit;
  font-size: var(--fs-caption);
  font-weight: 500;
}
.btn:hover, .btn:focus-visible, .btn:active,
.slide-arrow:hover, .slide-arrow:focus-visible, .slide-arrow:active,
.btn[aria-pressed="true"], .btn[aria-current]:not([aria-current="false"]) { box-shadow: var(--control-pressed); }

/* No underline, anywhere. The decision to abolish decorative lines (§3,
   2026-08-16) was applied component by component — `.article a`, `.footer a`,
   and `.card` each turned it off for itself — which left the browser
   default standing wherever a link appeared outside a named component. The
   sitemap's rows were underlined for that reason alone. Stated once, at the
   element, so the next new component inherits the decision instead of having
   to remember it. */
a, .mode { color: var(--fg); text-decoration: none; transition: color var(--t-fast) linear; }
/* The tier is decided by whether the thing carries a surface, so a link that
   HAS one must be held out of here. `.btn` and `.card` are both <a>, and
   without the exclusion this rule lands on them at the same time as their own
   surface rule — the plane deepens and the words fade at once, which is the
   two tiers playing on one element. Its worst form is a card: the headline
   greys against an already-darker plane and the card reads as disabled. */
a:not(.btn, .card):hover, a:not(.btn, .card):focus-visible, a:not(.btn, .card):active,
.mode:hover, .mode:focus-visible, .mode:active { color: var(--gray); }
.nav li.is-open > a, .nav li.is-open > .drop, .node.is-open > .big { color: var(--gray); }

/* ---- what is in force ----
   The two tiers above describe only the moment of touching. Neither can say
   the other thing a control has to say: that this one is the one in force,
   after the pointer has gone. Left unstated it gets invented once per place
   and never twice the same way — a word beside the item, an ink-against-grey
   of its own, a second colour that belongs to nothing else. All three were
   already in this site before the rule was written (2026-09-02).

   SELECTION IS THE PRESS, SUSTAINED. What is chosen is simply left in the
   state that touching it produces: the primary tier holds the deepened plane,
   the secondary tier holds the grey. Nothing new is drawn, so there is no
   second language to keep in agreement with the first, and the answer to
   "what does chosen look like" is already known by anyone who has pressed
   anything here once.

   It also lands the right way round in both tiers. The pressed plane is the
   heavier one, and the kind in force should be the heavier of its row. In the
   secondary tier it inverts — what is in force recedes to grey — and that is
   correct there for the same reason: a nav item whose panel is open has done
   its work, and the panel it opened is now the loud thing on screen. The rule
   is one sentence either way, which is the test that it is the right one.

   Not to be confused with grey used for UNAVAILABLE (`.is-off`: a language
   with no translation shipped). Where both facts appear in one list, grey
   cannot carry them both — say the unavailable one in grey and mark the one
   in force some other way, as the language panel does with a word. */
[aria-pressed="true"], [aria-current]:not([aria-current="false"]) { cursor: default; }

/* ---- layout ---- */

main { padding-top: calc(var(--bar) + var(--r-pad)); flex: 1 0 auto; }
main.bleed { padding-top: 0; }                 /* a hero starts at the top edge, under the nav */
main.bleed > .hero { min-height: 100svh; }

.hero {
  position: relative;
  min-height: calc(100svh - var(--bar) - var(--r-pad));
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding: var(--m-v) var(--m-narrow);
}

.hero .hero-bg {
  position: absolute; inset: 0; z-index: -1;
  background: var(--ph) center / cover no-repeat;
  display: flex; align-items: center; justify-content: center;
  color: var(--gray); font-size: var(--fs-caption); font-weight: 500;
}
.hero .hero-bg img {
  position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: cover; display: block;
  opacity: 0;
  transition: opacity var(--t-media) linear;
}
.hero .hero-bg img.is-on { opacity: 1; }

/* slideshow controls live inside the plate, so no second surface is needed */
.slide-ui { display: flex; align-items: center; justify-content: space-between; gap: 24px; margin-top: 1.4em; }
.gauge { display: flex; gap: 6px; }
.gauge span { width: 26px; height: 2px; background: currentColor; opacity: 0.3; }
.gauge span.is-on { opacity: 1; }
.slide-arrows { display: flex; gap: 8px; }
/* An arrow is pressed, so it takes the pill (CEO 2026-09-10, 丸ゴマ式): the
   pill's tint, the pill's text, and a circle exactly the pill's height —
   11px above and below one line of caption — so an arrow beside a pill
   stands on the same line at the same size. It was a bare glyph at nav size,
   the one pressable thing on the site that carried no surface. */
.slide-arrow {
  display: inline-grid; place-items: center;
  width: calc(var(--pill-y) * 2 + 1lh); height: calc(var(--pill-y) * 2 + 1lh); padding: 0;
  border: none; border-radius: var(--r-pill); cursor: pointer;
  background: var(--control); color: currentColor;
  -webkit-backdrop-filter: var(--control-filter); backdrop-filter: var(--control-filter);
  box-shadow: var(--control-shadow);
  font-family: inherit; font-size: var(--fs-caption); font-weight: 500;
  line-height: inherit;   /* a <button> keeps the browser's own, and 1lh must be the pill's line */
  transition: box-shadow var(--t-fast) linear;
}
/* on a dark plate the page's tint is a pale disc under white ink — the same
   step of the ink colour instead, so the arrow reads on the photograph */
.plate--dark .slide-arrow { background: var(--tint-on-dark); }
/* Read right to left, the row is mirrored and so is the way forward: the
   glyph turns with it, so `next` points where the reader is going (CEO
   2026-09-11, Arabic and Urdu). */
[dir="rtl"] .slide-arrow { transform: scaleX(-1); }
/* long-form display: a full statement carrying the key-title role.
   Sentence case, because ALL CAPS cannot be read at sentence length. */
/* sentence case: it drops the tracking with the capitals, and the leading
   with them too — the 0.8 only works because capitals have no descenders. */
.display--long { text-transform: none; letter-spacing: normal; line-height: 1.2; font-size: var(--fs-display-long);
  /* a sentence breaks between its words, never inside one: `hyphens: auto`
     is for a title that is one long word, and on a sentence it split
     "hu-manity" across two lines (CEO 2026-09-11). overflow-wrap still keeps
     a word too long for the frame from running off it. */
  hyphens: manual; }

/* Everything in the document takes the page frame — the title too. The one
   remaining use of the other margin is chrome: the bar, the full-screen hero,
   the footer signature. So the rule is not "which kind of thing is this" but
   "is it in the page at all", which is a question with an obvious answer. */
.page-head { padding: calc(var(--m-v) + 24px) var(--m-wide) var(--m-v); }

.content { padding: 0 var(--m-wide) var(--m-v); }
/* `.content` takes no margin above because the section before it closes
   with one. The hero does not: it closes on the photograph's edge, so the
   first words under it sat on the picture (CEO 2026-09-10). */
.hero + .content { padding-top: var(--m-v); }
/* A section holding only its heading is the head of what follows, so the
   gap belongs above it, not between it and the thing it names. */
.content:has(> .colhead:only-child) { padding-bottom: 0; }

/* ---- navigation (spec §3: attached, --bar tall, blur only, immediate) ---- */

.nav {
  /* The bar floats one nested gap in from the top and both sides (CEO
     2026-09-10). Off the edge, it no longer has an edge to be square against,
     so it takes the single radius — and at 24 on a 48 bar that is exactly a
     capsule. The gap is --r-pad rather than the screen frame: the bar stays
     close to the rim, as chrome should, and only lifts enough to read as an
     object. */
  position: fixed;
  top: var(--r-pad); left: var(--r-pad); right: var(--r-pad);
  border-radius: var(--r);
  z-index: 10;
  height: var(--bar);
  padding: 0 var(--m-narrow);
  /* mark left, the links centred on the page's axis, language right (CEO
     2026-09-10). A grid rather than space-between: with 1fr either side the
     links sit on the true centre however wide the mark and the globe are,
     where space-between would drift them by the difference. */
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  column-gap: clamp(22px, 2.4vw, 44px);
  /* The bar holds contents only; its glass is the sheet behind it (below).
     One surface, so when a dropdown opens the bar itself grows downward with
     no seam — two glass objects side by side always show where they meet.
     transform keeps this element the containing block for its fixed
     children (the panels, the phone list), which the backdrop-filter used to. */
  transform: translateZ(0);
}

/* The bar never paints a surface of its own, open or closed (2026-09-10).
   Until then it turned solid while a dropdown was open, which put an opaque
   48px strip on top of a glass sheet — one object, two materials. The sheet
   is the only surface: the bar and what hangs from it are one pane of glass. */

.nav ul {
  display: flex;
  align-items: stretch;
  height: 100%;
  gap: clamp(22px, 2.4vw, 44px);
  margin: 0;
  padding: 0;
  list-style: none;
}

.nav li {
  /* full-height items keep :hover unbroken from link to panel */
  display: flex;
  align-items: center;
  height: 100%;
}

/* a name that opens a sheet is not a link: the sheet holds the link. It is
   styled as one so the bar reads evenly, and carries a button's semantics. */
.nav .drop { cursor: pointer; }
.nav a, .nav .drop, .nav .lang {
  font-size: var(--fs-nav);
  font-weight: 500;
  display: flex;
  align-items: center;
  height: 100%;
}

.mark-link { display: flex; align-items: center; height: 100%; }
.nav .mark { height: 13px; width: auto; display: block; color: var(--fg); }
.nav > .mark-link { grid-column: 1; justify-self: start; }
.nav > #nav-list { grid-column: 2; }
.nav > .nav-lang { grid-column: 3; justify-self: end; }

/* language switch: an ordinary nav item, last in the row */
/* inert until a second language ships: shown, but not a link that goes nowhere */
.nav .lang { display: flex; align-items: center; height: 100%; color: var(--fg); cursor: pointer; }
.nav .lang svg { width: 16px; height: 16px; display: block; }
.lang-label { display: none; }   /* the globe carries it on a wide screen */

/* dropdown panel: full-width sheet attached under the bar */
.panel {
  /* The sheet below carries the surface; the panel carries only its contents,
     so moving from one item to the next resizes the open sheet by the
     difference instead of closing it and opening another. */
  position: fixed;
  top: var(--bar); left: 0; right: 0;
  display: grid;
  visibility: hidden;
  opacity: 0;
  pointer-events: none;
  /* contents wait a beat so the surface is under them before they read */
  transition: opacity var(--t-fast) linear var(--t-fast), visibility 0s linear var(--t-page);
  top: var(--bar);   /* the bar's child, inside the same stretched shape — no gap under it */
  padding: 44px calc(var(--m-wide) - var(--r-pad)) 56px;   /* back onto the page's line, not the bar's */
}

/* the surface itself: one sheet, always present, its height the animated value */
.sheet {
  /* floats with the bar: the same side inset, one gap below it, the same
     radius. A bar that lifts off the edge above a sheet still pinned to it
     would put a seam exactly where the two meet. */
  /* the bar's own glass. Closed it is exactly the bar — a 48px capsule;
     open it is the bar plus the panel, the same object stretched. */
  position: fixed;
  top: var(--r-pad); left: var(--r-pad); right: var(--r-pad);
  border-radius: var(--r);
  z-index: 9;
  height: var(--bar);
  overflow: hidden;
  pointer-events: none;
  /* liquid glass (CEO 2026-09-11): a thin tint of the page colour, a light
     blur, a lit rim. web.js adds the refraction where the browser draws it,
     and swaps tint and ink to whichever of black or white reads over what is
     behind (is-ink-light / is-ink-dark, below). */
  background: var(--chrome-surface);
  -webkit-backdrop-filter: var(--chrome-filter);
  backdrop-filter: var(--chrome-filter);
  box-shadow: var(--chrome-shadow);
  /* Closing takes the plain ease, not the spring. A spring that overshoots
     while shrinking dips below the bar's own height, and for that instant the
     glass is shorter than what it holds — the mark sticks out of it. The
     rule for whichever state is being entered is the one that runs, so the
     spring is written on the open states only. */
  transition: height var(--t-page) var(--ease-out),
              background-color var(--t-page) var(--ease-out);
}
/* Open, the whole object — the bar and what hangs from it, together — turns
   nearly opaque (CEO 2026-09-10): once a dropdown has the screen the menu is
   what is being read, not the page behind it. It is the one sheet that
   thickens, never the bar alone; the bar turning solid on its own was the
   seam between two materials. */
.nav:has(.has-panel.is-open) ~ .sheet,
.nav.is-menu-open ~ .sheet {
  background: var(--chrome-open);
  transition: height var(--t-goo) var(--ease-goo),
              background-color var(--t-page) var(--ease-out);
}

/* ---- ink that follows the backdrop (web.js) ----
   Clear glass shows what is under it, so black ink goes unread over a dark
   photograph. web.js measures the backdrop under the bar and under the cookie
   note and marks each is-ink-light (white ink, dark tint) or is-ink-dark
   (black ink, light tint), independent of the site's own mode. Open, the
   glass is nearly opaque and the page's ink returns, so the mark is ignored
   there — the open rules above outrank these. */
.sheet.is-ink-light, .cookie.is-ink-light { background: color-mix(in srgb, var(--black) var(--liquid), transparent); }
.sheet.is-ink-dark, .cookie.is-ink-dark { background: color-mix(in srgb, var(--white) var(--liquid), transparent); }
.nav.is-ink-light:not(.is-menu-open):not(:has(.has-panel.is-open)) :is(a:not(:hover), .drop:not(:hover), .lang, .mark, .nav-toggle) { color: var(--ink-on-dark); }
.nav.is-ink-dark:not(.is-menu-open):not(:has(.has-panel.is-open)) :is(a:not(:hover), .drop:not(:hover), .lang, .mark, .nav-toggle) { color: var(--ink-on-light); }
.nav .mark, .nav .lang, .nav-toggle { transition: color var(--t-page) linear; }


.nav li:hover > .panel,
.nav li:focus-within > .panel,
.nav li.is-open > .panel {
  visibility: visible;
  opacity: 1;
  pointer-events: auto;
  transition: opacity var(--t-fast) linear var(--t-fast), visibility 0s;
}

.panel {
  grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
  column-gap: clamp(32px, 4vw, 80px);
  row-gap: clamp(32px, 4vh, 56px);
}

.panel .col { display: flex; flex-direction: column; align-items: flex-start; gap: 14px; }

/* the lead column says what the section is, in one line, in the section's own
   words. One track: a wider lead pushes everything after it to the right. */
.panel .col--lead { gap: 16px; }
.panel .col--lead p { margin: 0; font-size: var(--fs-caption); }
.panel .col--lead .lead-name { font-weight: 500; }

/* tree disclosure (hover): a node with children carries a plus; hovering it
   opens that node's own column to the right. Repeatable at any depth.
   The child column's hit area reaches back across the gutter so the pointer
   never falls into a dead zone on its way over. Focus opens it for keyboards. */
.panel .node { display: flex; align-items: center; gap: 12px; }
.node-plus { position: relative; flex: none; width: 13px; height: 13px; }
.node-plus::before, .node-plus::after { content: ""; position: absolute; background: var(--fg); }
.node-plus::before { left: 0; top: 6px; width: 13px; height: 1px; }
.node-plus::after { top: 0; left: 6px; width: 1px; height: 13px; }
.node:hover .node-plus::after,
.node.is-open .node-plus::after,
.node:focus-within .node-plus::after { opacity: 0; }

.panel .col.node-panel {
  display: none;
  opacity: 0;
  transition: opacity var(--t-page) var(--ease-out), display var(--t-page) allow-discrete;
  margin-left: calc(-1 * clamp(32px, 4vw, 80px));
  padding-left: clamp(32px, 4vw, 80px);
}
.panel:has(.node:hover) .col.node-panel,
.panel:has(.node.is-open) .col.node-panel,
.panel:has(.node:focus-within) .col.node-panel,
.panel .col.node-panel:hover,
.panel .col.node-panel:focus-within { display: flex; opacity: 1; }
@starting-style {
  .panel:has(.node:hover) .col.node-panel,
  .panel:has(.node.is-open) .col.node-panel { opacity: 0; }
}

.colhead {
  font-size: var(--fs-caption);
  font-weight: 300;
  color: var(--gray);
  margin: 0 0 4px;
}
/* In the body a column head names a part of the page, and at caption size
   it read as a footnote — `Products` under the hero, `Leadership` over the
   team (CEO 2026-09-10). It is a heading there: larger than a subtitle,
   heavy, in ink. In the dropdown and the footer it heads a short list of
   links and stays a caption. */
main .colhead { font-size: var(--fs-head); font-weight: 500; color: var(--fg); margin-bottom: 0.5em; }

.panel a.big { font-size: var(--fs-big); font-weight: 500; height: auto; }

/* language panel: names in their own language, inert until a translation ships */
.panel--lang { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); row-gap: 18px; }
/* A language reads the same whether or not it can be pressed. The rule named
   `span` only, so when shipped languages became links (2026-09-05) they fell
   out of it and sat smaller and lighter than the greyed ones beside them —
   one list in two sizes. The state a row is in is said by its colour, never
   by its size. */
.panel--lang span, .panel--lang a { font-size: var(--fs-body); font-weight: 500; color: var(--fg); }
.panel--lang .is-off { color: var(--gray); }
.panel--lang .now { font-size: var(--fs-caption); font-weight: 300; color: var(--gray); }
.panel a { height: auto; }

/* ---- forms ----
   §3.0 is the canon and nothing here repeats it: a field carries the primary
   tier's surface — the grey plane at rest, deepening while engaged — and the
   single radius. No border. The provisional 1px frame and the offset outline
   were withdrawn in full (2026-08-31); the deepened plane IS the focus
   indicator, exactly as it is the press.

   This component was deleted on 2026-09-04 as unused vocabulary and is back
   on 2026-09-05 because a form now exists. That is the intended cycle (§4):
   what is not used is removed, and what is needed is discussed and added —
   Zero Web does not keep furniture for a room nobody has built yet.

   The label sits above its input rather than inside it: a placeholder that
   doubles as a label disappears exactly when the reader needs it most. */
.field { display: block; margin: 0 0 22px; }
.field > span {
  display: block;
  font-size: var(--fs-caption); font-weight: 500;
  margin: 0 0 8px;
}
.field input, .field textarea {
  width: 100%;
  box-sizing: border-box;
  /* the pill's inset, so a field's words start where a button's do — at 14px
     they sat inside the curve of the 24px corner (2026-09-11) */
  padding: 13px var(--pill-x);
  border: none;
  border-radius: var(--r);
  background: var(--tint);   /* a well in the page, not a piece of glass: it keeps the page's tint */
  color: var(--fg);
  font-family: inherit;
  font-size: var(--fs-input);
  font-weight: 300;
  transition: background-color var(--t-fast) linear;
}
/* the same three-part press every other pressable thing takes (§3.0) */
.field input:hover, .field textarea:hover,
.field input:focus, .field textarea:focus { background: var(--tint-deep); }
.field input:focus, .field textarea:focus { outline: none; }
.field textarea { min-height: 180px; resize: vertical; }
.field input::placeholder, .field textarea::placeholder { color: var(--gray); }

/* the honeypot: a field only a robot fills in. Hidden from sight and from
   the accessibility tree, and never focusable by tab. */
.field--trap {
  /* Clipped in place, not thrown off-screen. left: -9999px is off-screen only
     in a left-to-right page: in Arabic and Urdu the scroll origin is on the
     right, so the far left is scrollable and the page grew 9,995px wide
     (found 2026-09-10). A clip hides it the same way in either direction. */
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap;
}

/* what the form says back. Prose, not a coloured banner — §3.1.1 already
   states that an absence is said in words, and so is an outcome. */
.form-note { margin: 0; font-size: var(--fs-caption); color: var(--gray); }
.form-note.is-bad { color: var(--fg); }

/* ---- cookie consent ----
   The third thing that floats over the page, so it takes the same recipe as
   the other two: `--chrome` at 0.72 with the one blur, black ink on white
   glass whatever is behind it (§3.2). It floats one nested gap in from the
   bottom and sides, like the bar (CEO 2026-09-10), so it takes the single
   radius — off the edge there is no edge to be square against. Nothing new is drawn: the surface is the chrome, the buttons are
   the pill every other working control takes.

   It asks before the tag loads, not after. A banner that appears while
   analytics is already running is a notice wearing a question's clothes. */
.cookie {
  position: fixed; left: var(--r-pad); right: var(--r-pad); bottom: var(--r-pad);
  z-index: 60;
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 14px clamp(20px, 2.4vw, 40px);
  padding: 18px calc(var(--m-wide) - var(--r-pad));   /* the words stay on the page's line */
  background: var(--chrome-surface);   /* the bar's glass */
  -webkit-backdrop-filter: var(--chrome-filter);
  backdrop-filter: var(--chrome-filter);
  box-shadow: var(--chrome-shadow);
  color: var(--fg);
  border-radius: var(--r);
  /* it arrives with the page rather than on top of it: the same page-scale
     interval the sheet uses to open */
  transition: transform var(--t-page) var(--ease), opacity var(--t-page) linear;
}
.cookie[hidden] { display: none; }
.cookie.is-away { transform: translateY(calc(100% + var(--r-pad))); opacity: 0; }   /* clear of the gap it floats in */
/* the sentence carries the fact; it is chrome, so it takes the chrome scale */
.cookie p { margin: 0; flex: 1 1 22ch; font-size: var(--fs-caption); }
/* its ink and its answers follow the backdrop as the bar's do */
.cookie.is-ink-light, .cookie.is-ink-light a { color: var(--ink-on-dark); }
.cookie.is-ink-light .btn { color: var(--ink-on-dark); }
.cookie.is-ink-dark, .cookie.is-ink-dark a { color: var(--ink-on-light); }
.cookie.is-ink-dark .btn { color: var(--ink-on-light); }

/* On a phone the sentence takes the whole row, so the two answers stay
   together on the next one. Left to wrap on its own, `Accept` rode up beside
   the text and `Reject` fell below it — one question with its two answers on
   different lines reads as two separate controls. */
@media (max-width: 760px) {
  .cookie { padding: 16px calc(var(--m-wide) - var(--r-pad)); }
  .cookie p { flex: 1 1 100%; }
}

@media (prefers-reduced-motion: reduce) { .cookie, .sheet { transition: none; } }

/* page veil: blurs the page only while a dropdown-bearing item is hovered */
.veil {
  position: fixed;
  inset: 0;
  z-index: 8;
  display: none;
  opacity: 0;
  transition: opacity var(--t-fast) var(--ease), display var(--t-fast) allow-discrete;
  background: var(--veil);
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
}

.nav:has(.has-panel:hover) ~ .veil,
.nav:has(.has-panel.is-open) ~ .veil { display: block; opacity: 1; }
@starting-style {
  .nav:has(.has-panel:hover) ~ .veil,
  .nav:has(.has-panel.is-open) ~ .veil { opacity: 0; }
}

/* ---- buttons ----
   §3.0 is the canon. The press is stated once, with the tier itself; this
   block gives only the shape. The provisional set (1px outline, R0, invert,
   underline-on-hover) was withdrawn in full — spec §3, 2026-08-31. */

/* A button takes the full pill — its own shape, not a second radius. The
   shape is stated with the tier in §3.0's rule above (one `.btn` since
   2026-09-11). */
/* no second :hover here. The press belongs to the tier, not to the shape:
   a button and a card deepen the same plane by the same amount, or the two
   read as two systems. Stated once, above, with the tier itself. */

/* `.btn-text` — a word with an arrow after it — was withdrawn in full
   (CEO 2026-09-04). The tiers are two, and it was a third: no surface, so
   not the primary tier, yet marked with a glyph no plain link carries. The
   arrow is an affordance drawn on, which is the decoration §3 abolished
   everywhere else. It also split one thing in two — About offered Team and
   Careers as arrows while the top page offered All news as a pill, and the
   two are the same act.

   ONE QUESTION SETTLES IT: does the word stand on its own, or does it sit
   inside something? Standing on its own it is a button and takes the pill,
   whether it acts or travels — a button that goes is still a button. Inside
   prose, the nav, or the footer it is a plain link and takes nothing. */

/* ---- long-form article: no width cap. Text runs to the frame;
   the only division allowed is the centered duo gutter. ---- */

/* One step, and it is 1em. Heading to prose, prose to prose, caption to prose
   — the same gap every time, because a page that spaces its parts by five
   different amounts is claiming five kinds of relatedness it cannot name.

   A section supplies its own edges, so the first thing inside one takes no
   space above it and the last takes none below. Until 2026-09-02 this was
   done by hand: every heading on the site carried `style="margin: 0 0 1em"`
   because `.article h2` opened with 2.2em that landed on top of the section's
   padding. Ninety-three inline margins in eight different values were the
   accumulated cost of that one wrong opening value, and the rule underneath
   them had never once applied. */
.article h2 { margin: 2.2em 0 1em; }
.article p { margin: 0 0 1em; }
.content > :first-child, .page-head > :first-child { margin-top: 0; }
.content > :last-child { margin-bottom: 0; }

/* ---- news ----
   A story is a picture with a date and a headline under it. The newest runs
   wide across the frame; the rest fall into a grid that reflows by width.
   The card carries its surface from the start, which binds the picture to the
   words under it as one object and says it can be pressed before anyone has
   to hover to find out. On contact the surface deepens rather than inverting:
   a card holds a photograph, and a black slab beside one shouts over it. The
   step is about twice the smallest one that reads. */

/* ---- kinds: narrowing a list to one of its sorts ----
   A kind is pressed, not travelled to. Nothing moves and nowhere is left —
   the list under it is rewritten in place — so this is the primary tier and
   takes the button's own shape, the full pill. The bar of pills withdrawn
   from the nav (2026-08-25) was navigation wearing a control's clothes; this
   is the opposite case, and one question sorts both: does it act, or does it
   go? A row that acts may carry surfaces. A row that goes may not.

   The one in force holds the press, stated once above with the tier. There is
   no rule for it here, and there must not be one. */
.kinds { display: flex; flex-wrap: wrap; gap: 8px; margin: 1.6em 0 0; }

/* What a list says when the narrowing leaves nothing in it. Prose, not a
   panel: a held space would promise a picture that is not coming. */
.empty { color: var(--gray); font-size: var(--fs-caption); margin: calc(1.6 * var(--fs-body)) 0 0; }
/* measured in the body's em, not its own: it stands where a row of cards
   would, so it sits the same distance under the heading (1.6em of caption
   put an empty section's heading half as far from what it names) */

/* A card carries `hidden` while a kind is in force. `.card` sets its own
   display, which outranks the attribute's rule in the browser's sheet, so the
   attribute has to be said again here — otherwise the card stays on screen
   while claiming to be hidden, to a screen reader as much as to the eye. */
.card[hidden] { display: none; }

/* People. A person is photographed upright, so the frame that holds one is the
   only portrait frame Zero Web declares: 4:5. The three landscape ratios frame
   things and places (§3); a person gets the fourth, and it is declared here
   rather than taken from whatever the photographer handed over — the rule is
   still that the frame decides, not the material.

   Standalone, so the corner is `--r` (§3.0). Until 2026-09-02 this whole grid
   lived in inline styles on one page, which is why its pictures were square
   while every other picture on the site was round: markup that never meets a
   rule cannot drift back on its own. */
.people {
  display: grid;
  /* one width, not two. Leadership had 240px and the advisers 300px because
     each grid was written where it was needed; a list does not get to choose
     its own measure any more than it gets to choose its own layout (§3.1.2).
     300 gave three columns and a portrait 494px tall — half a screen per
     person, so seventeen people read as a wall of faces rather than a team.
     220 gives four, and a face at 355; measured against 185, which gives five
     and quiets the pictures further but narrows the prose under them to the
     point where a two-line role wraps to three. A portrait is sized by how
     many belong on the page, not by how large one of them can be. */
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: clamp(32px, 3.6vw, 56px);
  align-items: start;
  margin: 1.6em 0 0;   /* the same step a grid takes below its column head */
}
/* The founders and the advisers each stand in a frame of their own
   (CEO 2026-09-11). The founders: exactly three across, a wide 3:2. The
   advisers: four across, 5:4. Each picture is cut so the face is about the
   size the rest of the team's faces are — the frame gains room around the
   person, not a larger face; the room is the result, not the aim. */
.people--founders { grid-template-columns: repeat(3, 1fr); }
/* .people.people--* : the frame rule for every portrait (.people img, 4:5)
   comes later in this file with the same weight, and would win */
.people.people--founders img, .people.people--founders .ph { aspect-ratio: 3 / 2; }
.people--advisers { grid-template-columns: repeat(4, 1fr); }
.people.people--advisers img, .people.people--advisers .ph { aspect-ratio: 5 / 4; }
/* Everyone gets the frame, whether or not the photograph has been taken yet.
   A list where only some people have pictures ranks them by who was
   photographed, which is not a fact about the company; and the ones who do
   have pictures then read as the important ones purely because the others
   are text. The held space is the same one a card uses (§3.0) — a surface
   with the same corner, never an outline — so an empty frame says "awaited"
   rather than "missing". */
.people img, .people .ph {
  display: block; width: 100%;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  border-radius: var(--r);
  /* the gap under a picture belongs to the picture, not to whatever follows
     it — the same rule the card learned in §3.0 */
  margin: 0 0 14px;
}
.people .ph { background: var(--ph); }
/* the block principle (§2.0): the first line names, the lines under it
   describe. Spacing comes from the block, so no person carries their own */
.people p { margin: 0; }
.people .label { margin: 0 0 2px; }
.people .label + .caption { margin: 0 0 0.9em; }

/* A phone gets two. The desktop minimum falls to one column at 375px, and a
   portrait then takes half the screen — seventeen people become thirteen
   screens of scrolling. Two columns is not a smaller version of the desktop
   grid; it is the same rule (a portrait is sized by how many belong on the
   page) applied to a frame that holds less. */
@media (max-width: 760px) {
  .people { grid-template-columns: repeat(2, 1fr); gap: clamp(20px, 5vw, 32px); }
  /* the founders stack one to a row in their wide frame — three wide
     frames two across would leave each face a third the size of the rest */
  .people--founders { grid-template-columns: 1fr; }
}

/* One shape for every list of cards, whatever it lists: the first item takes
   the whole frame, the rest reflow in a grid under it. News, products, the
   parts of a product, projects, the library — a reader who has learned one
   of these has learned all of them, and a page that lists things does not
   get to invent its own rhythm for doing so.

   Named for what it is, not for where it was first needed. It was `.news-*`
   while five of the pages using it were not news, which is how a name stops
   being read and a second layout gets written next to it (renamed
   2026-09-02). */
/* An index is a list that cannot hold a photograph — a sitemap, a list of open
   positions. `.lead`/`.cards` below is the shape for lists that can, so the two
   never compete: the question is whether there is a picture, not which layout
   is wanted. The rows are secondary (§3.0) and take their hover from the global
   rule; nothing is added here.

   No marker and no indent. A bullet is a mark the palette never declared, and
   the browser's 40px indent would put a second line inside the page frame,
   which §3.3 forbids. The frame is the head of the row. Added 2026-09-02;
   until then these lists carried no rule at all and rendered as browser
   default discs. */
.index {
  display: flex; flex-direction: column;
  /* in em, so the rhythm follows the text rather than a second scale */
  gap: 0.6em;
  margin: 1.6em 0 0;
  padding: 0;
  list-style: none;
}

.lead { display: grid; margin: 1.6em 0 clamp(40px, 4vw, 72px); }
.cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
  gap: clamp(36px, 3.4vw, 64px) clamp(24px, 2.4vw, 40px);
  margin: 1.6em 0 0;
}

.card {
  display: block;
  padding: var(--r-pad);
  /* no negative margin. The frame is the page's structural line, and what sits
     on it is the object — the surface. Bleeding the surface outward to put the
     card's picture on the line instead puts two surfaces of the same tier on
     two different lines, since a button's surface starts at the frame. The
     card's contents indent by one gap, exactly as a button's word does. */
  border-radius: var(--r);
  background: var(--tint);
  color: var(--fg);
  /* only the surface moves: the words stay put, which keeps the picture and
     its caption reading as one unchanged object under the pointer */
  transition: background-color var(--t-fast) linear;
}
.card:hover, .card:focus-visible, .card:active,
.card[aria-current]:not([aria-current="false"]) { background: var(--tint-deep); }

/* a card still waiting for its photograph holds the placeholder ground;
   ratio and corner already come from `.card img, .card .ph` below */
.card .ph { background: var(--ph); }

.card img, .card .ph {
  display: block; width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  /* nested, so concentric: the frame keeps an even thickness all the way round */
  border-radius: var(--r-inner);
}
/* where the subject sits in the frame — a fact about the picture, not a
   style, so it travels with the image rather than with the component */
.focus-low { object-position: center 78%; }
.focus-high { object-position: center 12%; }
.lead .card img, .lead .card .ph { aspect-ratio: 21 / 9; }

.card time {   /* mono, 400, uppercase already come from `.num, time, .stamp` */
  display: block;
  font-size: var(--fs-caption);
  color: var(--gray);
  margin: 0 0 10px;
}
/* What sort of thing this is. Above the date because it is the coarser fact,
   and in the sans rather than the mono the date wears — a kind is a word, not
   a record (§2.1). Grey, so the headline is still read first. */
.card .kind { display: block; font-size: var(--fs-caption); color: var(--gray); margin-bottom: 4px; }
/* margin-bottom, not the `margin` shorthand: the shorthand's 0 top outranked
   `.card img + *` (0,2,0 over 0,1,1) and put the label flush on the photo */
.card .head {
  display: block;
  font-weight: 500;
  font-size: var(--fs-body);
  line-height: 1.2;
  max-width: 34ch;
}
.card .stand {
  display: block;
  font-weight: 300;
  font-size: var(--fs-caption);
  color: var(--gray);
  margin: 10px 0 0;
  max-width: 54ch;
}

/* A card resets what it holds, exactly as a plate does — otherwise a <p>
   arrives with the browser's own margin and the frame stops being one value.
   The gap under the picture belongs to the PICTURE, not to the date: carried
   on `.card time`, a card without a <time> had its headline flush against the
   photograph — which is every card on the products page. */
.card > * { margin: 0; }
.card img + *, .card .ph + * { margin-top: 18px; }

/* ---- plate: the base that carries text over media ----
   Same glass as every other floating surface. It is the nested case: one
   nesting gap in from the picture's edge, with the concentric corner. The
   hero is the single exception, written as one below. Its own text sits one
   screen margin in from the plate edge, which is a typographic indent rather
   than a frame and so keeps its own value.
   Works over any image because the blur normalises whatever is behind it. */

.media { position: relative; }  /* the plate positions against the media box */
.plate {
  /* nested inside a framed picture: the nesting gap, concentric corner */
  border-radius: var(--r-inner);
  position: absolute;
  left: var(--r-pad);
  bottom: var(--r-pad);
  width: fit-content;
  max-width: calc(100% - var(--r-pad) * 2);   /* containment, not a measure */
  padding: var(--m-narrow);
  -webkit-backdrop-filter: var(--plate-filter);
  backdrop-filter: var(--plate-filter);
  box-shadow: var(--plate-shadow);
}
/* Two faces, identical geometry. The glass matches the media it sits on, and the
   text opposes the glass — glass is translucent, so it pulls the backdrop toward its
   own tint rather than replacing it. Inverting lands the composite in mid-grey where
   neither text holds. Measured crossover: media luma 90 of 255.
     media luma <  90  ->  .plate--dark  (white text)
     media luma >= 90  ->  .plate--light (black text; never below 6.9:1, so the safe default) */
.plate, .plate--light { background: var(--plate-light); color: var(--ink-on-light); }
.plate--dark { background: var(--plate-dark); color: var(--ink-on-dark); }
.plate > * { margin: 0; }
.plate > * + * { margin-top: 0.6em; }

/* alignment grid — five lines across any media: both edges, the centre,
   and the midpoint on each side. A plate may end or begin on any line. */
.plate--from-2, .plate--from-3, .plate--from-4 { width: auto; max-width: none; }
/* A plate that runs `to` a line is as wide as its words and stops at the
   line at most — the glass grows and shrinks with the sentence (CEO
   2026-09-11) rather than holding a fixed width the words have to fill. */
.plate--to-2, .plate--to-3, .plate--to-4 { right: auto; width: fit-content; }
/* A plate keeps the same gap from every line it meets that it keeps from the
   media edge — the nesting gap here, the screen margin in the hero override —
   so a plate that stops at the centre and one that starts there leave twice
   that gap between them (28px inside a band).
   from-* is declared first so a from/to pair spans line to line; on its own it
   runs to the far frame edge, mirroring to-* which starts at the near one. */
.plate--from-2 { left: calc(25% + var(--r-pad)); right: var(--r-pad); }
.plate--from-3 { left: calc(50% + var(--r-pad)); right: var(--r-pad); }
.plate--from-4 { left: calc(75% + var(--r-pad)); right: var(--r-pad); }
.plate--to-2 { max-width: calc(25% - var(--r-pad) * 2); }   /* stops short of line 2 (25%) */
.plate--to-3 { max-width: calc(50% - var(--r-pad) * 2); }   /* stops short of line 3 (centre) */
.plate--to-4 { max-width: calc(75% - var(--r-pad) * 2); }   /* stops short of line 4 (75%) */

/* THE ONE EXCEPTION. The hero is not a framed picture — it runs to all four
   screen edges, so there is no frame for its plate to be nested in, only the
   screen. It takes the screen's own margin and keeps the full radius, since
   an inset panel never reaches the screen's corner to chip it. Written here
   as an override rather than as a token both places read, because it is one
   case and an exception should look like one. */
.hero .plate { border-radius: var(--r); left: var(--m-narrow); bottom: var(--m-narrow); }
.hero .plate--to-2 { max-width: calc(25% - var(--m-narrow) * 2); }
.hero .plate--to-3 { max-width: calc(50% - var(--m-narrow) * 2); }
.hero .plate--to-4 { max-width: calc(75% - var(--m-narrow) * 2); }
.hero .plate--from-2 { left: calc(25% + var(--m-narrow)); right: var(--m-narrow); }
.hero .plate--from-3 { left: calc(50% + var(--m-narrow)); right: var(--m-narrow); }
.hero .plate--from-4 { left: calc(75% + var(--m-narrow)); right: var(--m-narrow); }

/* ---- imagery (two tiers, proposal 2026-08-15) ----
   Full bleed is reserved for the hero background only.
   All other images sit on the text margin system:
   The frame is no longer a choice. A picture sits on the same line as the
   text around it — one line down the page, not two — so the frame lives on
   .media itself. What is left to choose is the ratio. */

/* auto width, never width:100% — with margin-inline that would overflow the frame */
.media { display: block; margin: 0 0 var(--m-v); margin-inline: var(--m-wide); }
.media img { display: block; width: 100%; height: auto; }
/* ratio only — the frame is common */
.media .ph {
  display: flex; align-items: center; justify-content: center;
  background: var(--ph); color: var(--gray);
  font-size: var(--fs-caption); font-weight: 500;
}
/* the placeholder and the real image share one set of ratios, so dropping an
   image in never changes the layout around it */
.media .ph, .media img { aspect-ratio: 21 / 9; object-fit: cover; border-radius: var(--r); }
/* nested twice over: a framed picture inside a card is still the card's
   nesting, and these selectors sit later than `.card img` at equal weight */
.card .media .ph, .card .media img { border-radius: var(--r-inner); }
/* scoped to beat `.media img`, which is one specificity point heavier */
.media .ph--flat, .media img.ph--flat { aspect-ratio: 3 / 1; }   /* flat band */
/* the figure already carries its tier as margin-inline, so a horizontal
   padding here would indent the caption past its own picture's left edge */
figcaption { font-size: var(--fs-caption); font-weight: 300; padding: var(--r-pad) 0 0; }

/* ---- motion (spec §3.1) ----
   Movement is allowed when it explains position or continuity, and at no
   other time. Crossing between documents, scrolling, revealing what a page
   sits on: all of these answer "where did that go". Elements arriving as
   they scroll into view answer nothing, and stay forbidden. */
@view-transition { navigation: auto; }

::view-transition-old(root), ::view-transition-new(root) {
  animation-duration: var(--t-page);
  animation-timing-function: var(--ease);
}

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

/* ---- footer (stacked columns + signature in the narrow frame) ----
   Links stand in vertical columns; the ZERO wordmark closes the page
   inside the narrow frame (full bleed stays reserved for the hero). */

.footer { padding: 0; }
.footer .foot-body {
  display: flex;
  flex-wrap: wrap;
  gap: clamp(48px, 8vw, 140px);
  align-items: flex-start;
  padding: calc(var(--m-v) * 1.5) var(--m-wide) var(--m-v);
}
.footer .foot-col { display: flex; flex-direction: column; gap: 12px; }
.footer a { font-size: var(--fs-caption); font-weight: 500; }
.footer .mode {
  border: none; background: none; padding: 0; margin: 0; cursor: pointer;
  font-family: inherit; font-size: var(--fs-caption); font-weight: 500;
  text-align: left;
}
.footer .stamp { font-size: var(--fs-caption); margin: 0 0 0 auto; align-self: flex-end; }
.footer .sig-wrap { padding: 0 var(--m-narrow) var(--m-narrow); }
.footer .sig { display: block; width: 100%; height: auto; aspect-ratio: 79.5 / 14.02;   /* the ink, not the box: the artwork stops short of the viewBox */ color: var(--fg); margin: 0; }

/* ---- layout patterns (provisional set, 2026-08-14) ----
   Reusable section compositions for web and future apps. */

/* P0 duo: two texts side by side; the gutter is centered, 50px each side
   of the center line (100px total = the wide margin value at 1920). */
.duo {
  display: grid;
  grid-template-columns: 1fr 1fr;
  column-gap: var(--m-wide);
  padding: var(--m-v) var(--m-wide);
}
@media (max-width: 800px) { .duo { grid-template-columns: 1fr; row-gap: var(--m-v); } }

/* P4 card grid: uniform tiles (news, shop, team) */
.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 48px;
  padding: 0 var(--m-wide) var(--m-v);
}

/* Leaving the site is blue; internal navigation stays ink. Every scheme that
   hands the reader to something else counts, not only another web page: mail
   and telephone leave the browser altogether, which is a larger departure than
   a link to another site. They were missing until 2026-09-04, so an address in
   the middle of a paragraph was black on black with no underline (§3 abolished
   those) — nothing at all said it could be pressed. In prose the colour is the
   only cue a link has left, and prose here holds no internal links: every one
   is mail or external, so half of them were invisible.

   `tel:` is here before a telephone number is. The rule must not depend on
   someone remembering it on the day one appears — §4, on the cache-buster: a
   step that gets forgotten is not a step, it is a defect.

   An address standing on its own does not need the blue and does not get it:
   it is a word standing alone, so it takes the pill (CEO 2026-09-04), and the
   surface says what the colour would have said. The guard below is what makes
   that work — the same lesson §3.0 states for the hover rule: a second-tier
   colour must not land on a primary-tier surface. */
a:not(.btn, .card)[href^="http://"], a:not(.btn, .card)[href^="https://"],
a:not(.btn, .card)[href^="mailto:"], a:not(.btn, .card)[href^="tel:"] { color: var(--link); }

/* ---- the bar on a phone ----
   Seven items and a globe do not fit in 375px — measured at 438px, so the
   mark fell off one end and the language control off the other. Below the
   breakpoint the bar holds the mark and one control; everything else moves
   into a full-height list. Rows with children stay closed until tapped, and
   they open with the same plus the desktop dropdown uses: one device, both
   platforms, nothing new to learn. */
.nav-toggle {
  display: none;
  position: absolute; right: var(--m-narrow); top: 0;
  width: 22px; height: var(--bar); padding: 0;
  border: none; background: none; cursor: pointer;
  color: var(--fg);
}
.nav-toggle i, .nav-toggle i::before, .nav-toggle i::after {
  position: absolute; left: 0; width: 22px; height: 1px;
  background: currentColor; content: "";
  transition: transform var(--t-page) var(--ease-out), opacity var(--t-fast) linear;
}
.nav-toggle i { top: 50%; }
.nav-toggle i::before { top: -6px; }
.nav-toggle i::after { top: 6px; }
.nav.is-menu-open .nav-toggle i { transform: rotate(45deg); }
.nav.is-menu-open .nav-toggle i::before { transform: translateY(6px) rotate(-90deg); }
.nav.is-menu-open .nav-toggle i::after { opacity: 0; }

@media (max-width: 760px) {
  .nav-toggle { display: block; }
  /* The phone bar is three things in a row — mark, globe, toggle — placed by
     the grid, not by offsets from the edge (2026-09-10). Offsets are
     physical: on RTL pages they did not mirror, so the mark and the globe
     sat side by side on the left, and two of them summed into a 58px gap
     between the globe and the toggle. As columns they mirror with the page,
     and the gap between the two controls is one number. */
  .nav { grid-template-columns: 1fr auto auto; column-gap: 20px; }
  .nav > .mark-link  { grid-column: 1; justify-self: start; }
  .nav > .nav-lang   { grid-column: 2; }
  .nav > .nav-toggle { grid-column: 3; position: relative; right: auto; }
  /* The list is fixed, and a fixed child of a grid that names a column takes
     that column as its containing block — it was squeezed into the middle
     track. Unplaced, it measures from the whole bar. */
  .nav > #nav-list { grid-column: auto; }

  /* Scoped to the main list: the language switch is a second list that stays
     in the bar, so it must not be turned into a full-screen sheet with it. */
  .nav > #nav-list {
    /* the bar carries a backdrop-filter, which makes it the containing block
       for its fixed children — so bottom:0 would resolve inside --bar. The
       height is stated instead. */
    position: fixed; top: var(--bar); left: 0; right: 0;
    display: flex; flex-direction: column; align-items: stretch;
    height: calc(100dvh - var(--bar) - var(--r-pad) * 2);
    gap: 0; padding: 12px calc(var(--m-wide) - var(--r-pad)) 48px;
    /* the surface is the sheet's, not the list's (2026-09-10). This list is
       a child of the bar, and the bar carries a backdrop-filter, which makes
       it a backdrop root: a second glass inside it would blur only what is
       inside the bar, not the page. The desktop dropdown already splits
       surface from contents this way (§3); the phone does the same. */
    background: transparent;
    overflow-y: auto;
    overscroll-behavior: contain;   /* reaching its end does not scroll the page under it */
    visibility: hidden;
    clip-path: inset(0 0 100% 0);
    transition: clip-path var(--t-page) var(--ease-out),
                visibility 0s linear var(--t-page);
  }
  .nav.is-menu-open > #nav-list {
    visibility: visible;
    clip-path: inset(0 0 0 0);
    transition: clip-path var(--t-page) var(--ease-out), visibility 0s;
  }

  .nav li { display: block; height: auto; }
  .nav > #nav-list > li > a, .nav > #nav-list > li > .drop {
    height: auto; padding: 16px 0;
    font-size: var(--fs-menu); font-weight: 500;
  }
  .nav > #nav-list > li.has-panel > .drop { display: flex; justify-content: space-between; align-items: center; }

  /* the same plus the desktop uses, at the end of a row that has children */
  .nav > #nav-list > li.has-panel > .drop::after {
    content: ""; flex: none; width: 14px; height: 14px; margin-left: 16px;
    background:
      linear-gradient(currentColor, currentColor) center/14px 1px no-repeat,
      linear-gradient(currentColor, currentColor) center/1px 14px no-repeat;
    transition: transform var(--t-page) var(--ease-out);
  }
  .nav > #nav-list > li.has-panel.is-open > .drop::after { transform: rotate(45deg); }

  /* ---- language: in the bar, beside the toggle (Apple's placement) ----
     A phone bar holds two controls, not seven. The globe is the second, so it
     sits where a second control is looked for: next to the button that opens
     the first. It is NOT a row in the menu — a switch that changes the whole
     site is not one of the site's destinations. */
  .nav-lang { height: var(--bar); z-index: 1; }   /* its column is set with the bar's, above */
  .nav .lang svg { width: 20px; height: 20px; }
  .lang-label { display: none; }   /* the globe carries it, as it does on a wide screen */

  /* the open menu's glass is the sheet, grown to the height of the list */
  .nav.is-menu-open ~ .sheet {
    height: calc(100dvh - var(--r-pad) * 2);   /* the bar grown to the whole screen, inset one gap */
  }

  /* a 3:1 band is a wide-screen shape — on a phone it is a 114px strip with
     no room for the plate that sits in it */
  .media .ph--flat, .media img.ph--flat { aspect-ratio: 4 / 3; }

  /* a panel is no longer a sheet: it is the part of the list under its name */
  .panel {
    position: static;
    display: grid !important;
    grid-template-columns: 1fr;
    visibility: visible;
    background: none;
    -webkit-backdrop-filter: none; backdrop-filter: none;
    padding: 0 0 0 18px;
    row-gap: 0;
    height: 0; overflow: hidden;
    clip-path: none;
    transition: height var(--t-page) var(--ease-out);
  }
  .nav > #nav-list > li.has-panel.is-open > .panel { height: auto; padding-bottom: 10px; }
  .panel .col { gap: 0; }
  .panel .col.node-panel { display: flex; opacity: 1; margin: 0; padding: 0 0 0 18px; }
  /* 500, as the same links are in the desktop dropdown: a large row reads as
     a thing to press, and a light one read as a caption (CEO 2026-09-11) */
  .panel a.big { font-size: var(--fs-menu-sub); font-weight: 500; padding: 11px 0; }
  .panel .colhead { display: none; }
  .panel .col--lead { display: none; }
  .node-plus { display: none; }
  .panel--lang .col { padding: 6px 0; }

  /* the language list is not part of the menu, so it opens on its own — the
     same clip the menu uses, from the same edge, so there is one motion */
  .nav-lang .panel--lang {
    position: fixed; top: var(--bar); left: 0; right: 0;
    height: calc(100dvh - var(--bar) - var(--r-pad) * 2);
    align-content: start;
    padding: 12px calc(var(--m-wide) - var(--r-pad)) var(--bar);
    background: transparent;   /* the glass is the sheet's, as it is for the menu */
    overflow-y: auto;
    overscroll-behavior: contain;
    visibility: hidden;
    clip-path: inset(0 0 100% 0);
    transition: clip-path var(--t-page) var(--ease-out),
                visibility 0s linear var(--t-page);
  }
  .nav-lang li.is-open > .panel--lang {
    height: calc(100dvh - var(--bar) - var(--r-pad) * 2);
    visibility: visible;
    clip-path: inset(0 0 0 0);
    transition: clip-path var(--t-page) var(--ease-out), visibility 0s;
  }
  /* while it is open the sheet grows under it exactly as it does for the
     menu — one glass, not an opaque page laid over the bar's glass */
  .nav:has(.nav-lang li.is-open) ~ .sheet {
    height: calc(100dvh - var(--r-pad) * 2);
  }
  /* The cookie note floats over the page, not over the menu. Left in place it
     laid a second pane of glass on the open sheet — a whiter patch at the
     foot of the menu. It steps away while the menu or the language list has
     the screen, and comes back when they close. */
  .nav.is-menu-open ~ .cookie,
  .nav:has(.nav-lang li.is-open) ~ .cookie {
    transform: translateY(calc(100% + var(--r-pad)));
    opacity: 0;
    pointer-events: none;
  }

  /* the alignment lines are a wide-screen device: a quarter of a phone is
     not a column, so every plate takes the whole frame. What it does NOT do is
     change the gap: a plate in a framed picture keeps the nesting gap on a
     phone exactly as it has it on a desktop, or the frame would thicken at the
     one width where there is least room for it. */
  .plate,
  .plate--to-2, .plate--to-3, .plate--to-4,
  .plate--from-2, .plate--from-3, .plate--from-4 {
    left: var(--r-pad); right: var(--r-pad);
    width: auto; max-width: none;
  }
  .hero .plate { left: var(--m-narrow); right: var(--m-narrow); }

  /* the sheet stays on a phone: it is the bar's glass and the open menu's
     surface. Hiding it (as this rule did until 2026-09-10) left the bar and
     the menu with no glass at all once the bar became transparent. */
  .veil { display: none; }
}
