/* The public SSR pages' stylesheet.
 *
 * This was a 144-line inline <style> block in base.html, which is why the CSP had to allow
 * `style-src 'unsafe-inline'` — and `unsafe-inline` on styles is not cosmetic: it is what makes
 * CSS-based data exfiltration possible if anything ever manages to inject markup. The block
 * interpolated nothing from the template, so it is a file, and the directive is gone.
 *
 * Linked AFTER /__theme.css so the tokens it consumes are already defined. It names no colour of
 * its own: every value here comes from a --t-* custom property the backend generated for this
 * request, so a recolour and a light/dark flip both move these pages too.
 *
 * It lives in ssr/static/, which the Dockerfile copies explicitly — PyArmor rewrites only .py
 * files, so a non-Python asset that is not COPYed is simply absent from the image while every
 * test still passes.
 */
/*
   * Mobile-first, and wearing the SAME theme as the app.
   *
   * Every colour here is a token from /__theme.css, linked above. Hardcoding one would mean the
   * landing page and the signed-in app drifting into two different accents the first time an
   * operator recoloured anything — and the landing page is the half nobody re-checks.
   *
   * There is no prefers-color-scheme rule left in here on purpose: light and dark are both in
   * the linked stylesheet, so this file has no opinion about which one applies and cannot
   * contradict the operator's default.
   */
  * { box-sizing: border-box; margin: 0; }
  /*
   * The page fill lives on <html>, NOT on <body>.
   *
   * The dot layer below is `body::before` at `z-index: -1`, and a negative z-index child paints
   * behind its own parent's background — <body> establishes no stacking context, so a background
   * here covers the dots completely. They render, they are simply never seen, which is a
   * particularly expensive bug because every computed style reads exactly right.
   */
  html { background: var(--t-canvas, #fff); }
  body {
    font-family: var(--t-font-sans, system-ui, sans-serif);
    min-height: 100vh; display: flex; flex-direction: column;
    color: var(--t-ink, #111);
  }
  /*
   * The dot field, on a fixed layer so it does not scroll away, and masked so it fades out at
   * the edges — a pattern that runs flat into the viewport edge reads as wallpaper.
   *
   * Same tokens as the app's `.t-grid`, and the same behaviour: Paper resolves --t-dot to
   * `transparent` and this paints nothing. No script, nothing to hydrate: a crawler and a
   * visitor with JavaScript off see exactly what everyone else does.
   */
  body::before {
    content: ""; position: fixed; inset: 0; z-index: -1; pointer-events: none;
    background-image: radial-gradient(var(--t-dot, #c6cfde) 1px, transparent 1px);
    background-size: 24px 24px;
    -webkit-mask-image: radial-gradient(120% 90% at 50% 38%, #000 0%, #000 52%, transparent 100%);
    mask-image: radial-gradient(120% 90% at 50% 38%, #000 0%, #000 52%, transparent 100%);
  }
  /*
   * The interactive field takes over only once its canvas is actually running, which the script
   * signals with this attribute. Hiding the CSS grid from the script itself would leave a window
   * with no background at all; hiding it unconditionally would leave visitors without JavaScript
   * looking at a flat page. There is no moment with neither.
   */
  html[data-dot-field="canvas"] body::before { display: none; }
  #dot-field {
    position: fixed; inset: 0; z-index: -1; pointer-events: none;
    -webkit-mask-image: radial-gradient(120% 90% at 50% 38%, #000 0%, #000 52%, transparent 100%);
    mask-image: radial-gradient(120% 90% at 50% 38%, #000 0%, #000 52%, transparent 100%);
  }
  /*
   * 48rem was a reading measure applied to a whole application. On a 1920px screen it spent most
   * of the viewport on empty gutters while the content inside was cramped — the page looked
   * narrow because of this number, not because anything wanted to be narrow. Prose that still
   * wants a measure asks for one itself (`.prose`, below); everything else gets the room.
   */
  header, main, footer {
    padding: 1.5rem 1.5rem; width: 100%; max-width: 100rem; margin: 0 auto;
  }
  @media (max-width: 40rem) { header, main, footer { padding: 1rem; } }
  /* Long-form copy is the one thing a wide container hurts: a line that runs the width of a
     desktop is a line the eye loses on the way back. */
  .prose { max-width: 46rem; }
  header { display: flex; justify-content: space-between; align-items: center; gap: 1rem; }
  header nav { display: flex; align-items: center; gap: 1rem; flex-wrap: wrap; }
  /*
   * Content flows from the TOP. `justify-content: center` was fine when the landing page was a
   * headline and a button, and became a bug the moment it had copy: a flex column centring
   * content taller than itself overflows in BOTH directions, so the top of the page ends up
   * above the scroll origin — unreachable, not merely off-screen. Nothing looks broken, the
   * heading is simply gone.
   */
  main { flex: 1; display: flex; flex-direction: column; justify-content: flex-start; gap: 1rem; }
  /*
   * Headings wear the theme's display face and weight. `font-synthesis: none`: a display face may
   * ship one weight (Darkroom's serif does), and a faked bold is each glyph drawn twice.
   */
  h1, h2 {
    font-family: var(--t-font-display, inherit);
    font-weight: var(--t-display-weight, 700);
    font-synthesis: none;
  }
  h1 { font-size: 2.5rem; line-height: 1.05; letter-spacing: var(--t-display-tracking, normal); }
  h2 { font-size: 1.5rem; line-height: 1.2; }
  p { color: var(--t-muted, #555); }
  a { color: inherit; }
  .cta {
    display: inline-block; padding: .75rem 1.5rem; border-radius: var(--t-radius, .5rem);
    background: var(--t-accent, #0E7490); color: var(--t-accent-ink, #fff);
    text-decoration: none; font-weight: 600;
    align-self: flex-start;
    /* 44px minimum touch target, like the rest of the app */
    min-height: 44px; line-height: 1.5;
    position: relative; overflow: hidden;
    transition: background-color var(--t-motion-fast, 120ms) var(--t-ease, ease);
  }
  /*
   * The primary action catches the light every five seconds.
   *
   * `overflow: hidden` clips the sheen to the button's own corner radius, which belongs to the
   * theme — Paper's is 0. One animation rather than an animation plus a timer: the sweep is an
   * 8% slice of the five seconds and the rest of the cycle is the wait, so nothing can drift
   * out of step with anything else.
   */
  .cta::after {
    content: ""; position: absolute; top: 0; bottom: 0; width: 45%;
    background: linear-gradient(100deg, transparent, rgba(255, 255, 255, .45), transparent);
    transform: translateX(-160%);
    animation: cta-shine 5s linear infinite;
  }
  @keyframes cta-shine {
    0% { transform: translateX(-160%); }
    8% { transform: translateX(320%); }
    100% { transform: translateX(320%); }
  }
  /* Decoration that repeats forever is exactly what this query exists for. */
  @media (prefers-reduced-motion: reduce) {
    .cta::after { animation: none; display: none; }
  }
  /*
   * The top bar. Opaque on purpose: it sits over the dot field, and a translucent bar would run
   * the dots under the words on the one element that has to be readable at a glance.
   */
  .topbar { background: var(--t-surface, #fff); border-bottom: 1px solid var(--t-line, #ddd); }
  /* Beta mode's notice, under the bar. Opaque for the same reason as the bar: it sits over the
     dot field, and it is one sentence somebody has to be able to read. */
  .beta-banner {
    background: var(--t-warning-soft, #f6ebe2);
    border-bottom: 1px solid var(--t-warning-line, #d6b19a);
    color: var(--t-ink, #1a1a1a);
  }
  .beta-banner-inner {
    max-width: 68rem; margin: 0 auto; padding: .55rem 1.5rem;
    font-size: .875rem; line-height: 1.45;
  }
  @media (max-width: 40rem) { .beta-banner-inner { padding: .5rem 1rem; } }
  .beta-chip {
    display: inline-block; margin-right: .4rem; padding: .05rem .45rem;
    border-radius: var(--t-radius-pill, 999px);
    background: var(--t-warning, #b45309); color: var(--t-warning-ink, #fff);
    font-family: var(--t-font-mono, ui-monospace, monospace); font-size: .7rem;
    letter-spacing: .08em; text-transform: uppercase; vertical-align: .1em;
  }
  .topbar-inner {
    /* The same container as the page below it, or the bar and the content sit on different
       left edges — which is the kind of misalignment nobody can name but everybody sees. */
    width: 100%; max-width: 100rem; margin: 0 auto; padding: .4rem 1.5rem;
    /* WRAPS. Without this the nav is squeezed into whatever is left over and breaks one
       link per line on a phone — a vertical stack jammed between the brand and the
       language, which is not a menu, it is an accident. Wrapping lets the right-hand
       group drop to its own line instead, which is what `margin-left: auto` already
       wants to do. */
    display: flex; flex-wrap: wrap; align-items: center; gap: .5rem 1rem;
  }
  @media (max-width: 40rem) { .topbar-inner { padding: .4rem 1rem; } }
  /* The mark and the name are one link, so the whole thing is one target rather than a word
     beside a picture that does the same job and has to be aimed at separately. */
  .topbar .brand {
    display: inline-flex; align-items: center; gap: .5rem;
    font-weight: 700; text-decoration: none; color: var(--t-ink, #111); white-space: nowrap;
  }
  /* `flex: none` or the mark is the thing that gives way when the bar is tight, and a squashed
     logo is worse than a wrapped word. */
  .brand-mark { width: 1.5rem; height: 1.5rem; flex: none; }
  /* The mark's fuse turns slowly, as in the design; its colour is the theme's `warning` (ember). */
  .brand-ring {
    transform-box: fill-box; transform-origin: center;
    animation: brand-spin 40s linear infinite;
  }
  @keyframes brand-spin { to { transform: rotate(360deg); } }
  @media (prefers-reduced-motion: reduce) {
    .brand-ring { animation: none; }
  }
  /* Pushes everything after it to the right edge. */
  .topbar-right { margin-left: auto; display: flex; align-items: center; gap: .5rem; }
  .topbar-nav { display: flex; align-items: center; gap: .25rem; flex-wrap: wrap; }
  .topbar-nav a, .topbar-right > a {
    display: inline-flex; align-items: center; min-height: 44px; padding: 0 .7rem;
    border-radius: var(--t-radius-sm, .4rem); text-decoration: none; font-size: .95rem;
    transition: background-color var(--t-motion-fast, 120ms) var(--t-ease, ease);
  }
  /*
   * `display` on the link beats the browser's own `[hidden] { display: none }` — same property,
   * a class selector against a UA rule — so the hidden Account link renders anyway. A signed-out
   * visitor seeing an Account link is the whole bug, and it is invisible in the markup, which
   * reads exactly right.
   */
  .topbar-nav a[hidden], .topbar-right > a[hidden] { display: none; }
  .topbar-nav a:hover, .topbar-right > a:hover { background: var(--t-raised, #f2f4f8); }
  .topbar-nav a[aria-current="page"] { font-weight: 700; }
  /*
   * The language, as an abbreviation beside a globe.
   *
   * TWO shapes for one control, and which one renders depends on how many locale files exist.
   * `.lang` is a plain LABEL, for an app that ships one language: a dropdown offering a single
   * option is a control that promises a choice it cannot deliver. `.lang-picker` is the menu,
   * and it is a <details> — see the markup for why it must open without JavaScript.
   */
  .lang, .lang-summary {
    display: inline-flex; align-items: center; gap: .35rem;
    min-height: 44px; padding: 0 .6rem; font-size: .8rem; font-weight: 700;
    letter-spacing: .04em; color: var(--t-muted, #555);
    border: 1px solid var(--t-line, #ddd); border-radius: var(--t-radius-sm, .4rem);
  }
  .lang svg, .lang-summary svg { width: 1rem; height: 1rem; }
  .lang-picker { position: relative; }
  .lang-summary { cursor: pointer; }
  /* The disclosure triangle, removed in both dialects — `::marker` is the standard and the
     WebKit pseudo-element is what Safari still reads. A triangle beside a globe beside an
     abbreviation is three symbols saying the same thing. */
  .lang-summary { list-style: none; }
  .lang-summary::-webkit-details-marker { display: none; }
  .lang-summary::marker { content: ""; }
  .lang-summary:hover { background: var(--t-raised, #f2f4f8); }
  /* Reuses `.account-menu` for the panel itself: two dropdowns in one bar that do not look
     alike is two dropdowns somebody has to learn separately. */
  .lang-menu { min-width: 9rem; }
  .lang-menu a[aria-current="true"] { font-weight: 700; color: var(--t-accent, #0e7490); }

  /* The theme menu. Its summary carries all three marks and shows the one in force, read from the
     attribute itself — no attribute is "whatever this device uses". */
  .theme-picker[hidden] { display: none; }
  .theme-icon { display: none; }
  html:not([data-theme]) .theme-icon--system,
  html[data-theme="light"] .theme-icon--light,
  html[data-theme="dark"] .theme-icon--dark { display: inline; }
  .theme-menu { min-width: 13rem; }
  .theme-menu button {
    display: flex; align-items: center; gap: .6rem; width: 100%; min-height: 44px;
    padding: .55rem .9rem; font: inherit; font-size: .9rem; text-align: left;
    color: var(--t-ink, #111); background: none; border: 0; cursor: pointer;
  }
  .theme-menu button:hover { background: var(--t-raised, #f2f4f8); }
  .theme-menu button svg { width: 1rem; height: 1rem; display: inline; }
  .theme-menu button[aria-checked="true"] { font-weight: 700; color: var(--t-accent, #0e7490); }

  /* The Dashboard link only a signed-in visitor sees — the one place in the bar worth an accent,
     because it is where somebody who already has an account is trying to get to. */
  .topbar-cta {
    display: inline-flex; align-items: center; min-height: 44px; padding: 0 .9rem;
    border-radius: var(--t-radius-sm, .4rem); text-decoration: none; font-size: .95rem;
    font-weight: 600; background: var(--t-accent, #0e7490); color: var(--t-accent-ink, #fff);
  }
  .topbar-cta[hidden] { display: none; }
  .topbar-cta:hover { background: var(--t-accent-hover, #0c5f77); }

  /* Account: a person, so a round control rather than a page-shaped one. */
  .account { position: relative; }
  .account[hidden] { display: none; }
  .account-button {
    display: inline-flex; align-items: center; justify-content: center;
    width: 44px; height: 44px; padding: 0; cursor: pointer;
    color: var(--t-ink, #111); background: var(--t-raised, #f2f4f8);
    border: 1px solid var(--t-line, #ddd); border-radius: 999px;
  }
  .account-button svg { width: 1.25rem; height: 1.25rem; }
  .account-button:hover { background: var(--t-surface, #fff); }
  /* The storage gauge: the used share as an arc in the plan's colour, warning past 80%, danger when
     full (`services/storageGauge.js`); the faint track is what is left. With it drawn, the ring is
     the button's edge. Theme tokens only, so it follows light, dark and the operator's palette. */
  .account[data-gauge] .account-button { border-color: transparent; }
  .account-gauge {
    position: absolute; top: 0; left: 0; width: 44px; height: 44px;
    transform: rotate(-90deg); pointer-events: none;
  }
  .account-gauge[hidden] { display: none; }
  .account-gauge circle { fill: none; stroke-width: 2.5; }
  .account-gauge-track { stroke: var(--t-line, #ddd); }
  .account-gauge-arc {
    stroke: var(--t-accent, #0e7490); stroke-linecap: round;
    transition: stroke-dashoffset var(--t-motion-slow, .6s) var(--t-ease, ease-out);
  }
  .account-gauge[data-tone="muted"] .account-gauge-arc { stroke: var(--t-muted, #555); }
  .account-gauge[data-tone="ink"] .account-gauge-arc { stroke: var(--t-ink, #111); }
  .account-gauge[data-tone="warning"] .account-gauge-arc { stroke: var(--t-warning, #b45309); }
  .account-gauge[data-tone="danger"] .account-gauge-arc { stroke: var(--t-danger, #b91c1c); }
  .account-menu {
    position: absolute; right: 0; top: calc(100% + .4rem); z-index: 50; min-width: 13rem;
    background: var(--t-surface, #fff); border: 1px solid var(--t-line, #ddd);
    border-radius: var(--t-radius, .8rem); overflow: hidden;
    box-shadow: 0 10px 30px -12px rgba(0, 0, 0, .45);
  }
  .account-menu[hidden] { display: none; }
  .account-menu a {
    display: block; min-height: 44px; padding: .7rem .9rem; font-size: .9rem;
    text-decoration: none; color: var(--t-ink, #111);
  }
  .account-menu a:hover { background: var(--t-raised, #f2f4f8); }
  /* Signing out is the last entry, set apart by a rule: it is the one that does something rather
     than going somewhere. */
  .account-signout {
    display: block; width: 100%; min-height: 44px; padding: .7rem .9rem; font: inherit;
    font-size: .9rem; text-align: left; color: var(--t-ink, #111); background: none; border: 0;
    border-top: 1px solid var(--t-line, #ddd); cursor: pointer;
  }
  .account-signout:hover { background: var(--t-raised, #f2f4f8); }
  .visually-hidden {
    position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
    overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
  }
  .plans { display: grid; gap: 1rem; list-style: none; padding: 0; }
  @media (min-width: 40rem) { .plans { grid-template-columns: repeat(3, 1fr); } }
  .plan {
    border: 1px solid var(--t-line, #ddd); border-radius: var(--t-radius, .5rem);
    background: var(--t-surface, #fff); padding: 1rem;
    display: flex; flex-direction: column; gap: .5rem;
  }
  .plan ul { margin: 0; padding-left: 1.1rem; font-size: .9rem; color: var(--t-muted, #555); }
  footer { font-size: .8rem; color: var(--t-muted, #555); }
.brand { text-decoration: none; }

  /*
   * The landing page's search field, and its rotating border.
   *
   * THE COLOURS ARE THE THEME'S. The gradient is built from `--t-accent`, `--t-success` and
   * `--t-warning` — the operator's own palette — so a recolour on the admin page moves this too
   * and a light/dark flip moves it again. A gradient with colours written into it would be the
   * one element on the page that ignores the theme, and it would look wrong on exactly the
   * installs that customised anything.
   *
   * Built as a rotating conic gradient UNDER the field, with the field itself opaque on top, so
   * the colour only shows in the padding ring. That is one element and one animation rather than
   * a border image, and the field stays a normal input — selectable, focusable, autofillable.
   */
  @property --t-spin {
    syntax: '<angle>';
    initial-value: 0deg;
    inherits: false;
  }

  @keyframes t-spin { to { --t-spin: 360deg; } }

  .search {
    position: relative;
    display: block;
    padding: 2px;                      /* the ring's thickness */
    border-radius: calc(var(--t-radius, .8rem) + 2px);
    background:
      conic-gradient(from var(--t-spin),
        var(--t-accent, #0e7490),
        var(--t-success, #047857),
        var(--t-warning, #b45309),
        var(--t-accent, #0e7490));
    animation: t-spin 6s linear infinite;
  }

  /*
   * Browsers without `@property` cannot animate an angle, so the gradient simply sits still —
   * a coloured ring instead of a moving one. Nothing is broken and nothing is missing, which is
   * the right way for decoration to degrade.
   */
  @supports not (background: conic-gradient(from 0deg, red, blue)) {
    .search { background: var(--t-accent, #0e7490); }
  }

  .search-field {
    display: flex; align-items: center; gap: .6rem;
    padding: .85rem 1rem;
    background: var(--t-surface, #fff);
    border-radius: var(--t-radius, .8rem);
  }
  .search-field svg { width: 1.2rem; height: 1.2rem; color: var(--t-muted, #555); flex: none; }
  .search-field input {
    flex: 1; min-width: 0; min-height: 28px;
    font: inherit; color: var(--t-ink, #111);
    background: transparent; border: 0; outline: none;
  }
  /*
   * A COLUMN at every width, and capped.
   *
   * Full width the field read as a page-wide banner rather than a control: on a 1920px screen the
   * caret sits a metre from its own label, and the eye has to travel the width of the page to find
   * the button. A search box is an input, not a section — it takes the width an input wants.
   *
   * The button sits UNDER it rather than beside it, at the same left edge as everything else on the
   * page, so the column of things you can act on reads top to bottom instead of jumping sideways.
   * That is also the mobile layout, which is the point: one arrangement, and the narrow case is not
   * a special case of the wide one.
   */
  .search-row {
    display: flex; flex-direction: column; gap: .75rem; margin: 1.5rem 0;
    max-width: 42rem;
  }
  .search-submit {
    /* Left-aligned and auto-width, like every other primary action on the page — a button that
       stretches to its container is a button whose size means nothing. */
    align-self: flex-start;
    min-height: 44px; padding: 0 1.4rem; font: inherit; font-weight: 600; cursor: pointer;
    color: var(--t-accent-ink, #fff); background: var(--t-accent, #0e7490);
    border: 0; border-radius: var(--t-radius-sm, .4rem);
  }
  /* Disabled until there is enough to search for. Visibly disabled rather than absent: a button
     that appears when you have typed enough moves the layout under the cursor. */
  .search-submit:disabled { opacity: .45; cursor: not-allowed; }

  /* The ring is decoration, and decoration never costs anybody anything. */
  @media (prefers-reduced-motion: reduce) {
    .search { animation: none; }
  }

  /* The opening line: one step up from body copy, and capped like the rest of the prose. */
  .lede { max-width: 46rem; font-size: 1.1rem; color: var(--t-muted, #555); }

  /* The landing page's supporting copy. `.prose` caps the measure; this adds the rhythm. */
  .prose h2 { margin-top: 2rem; }

  /*
   * The home, as the design's welcome: the promise beside the picture of the product. A phone
   * gets the picture first and the words under it, as the design's 01 draws it; a wide screen puts
   * them side by side. The picture itself is `darkroom.css`, shared with the app's sign-in screen.
   */
  .welcome { display: grid; gap: 1.25rem; align-items: center; padding: .5rem 0 2.5rem; }
  .welcome-picture { order: -1; }
  .welcome-picture .pd-hero { max-width: 28rem; }
  .welcome-title {
    margin: 0; font-size: clamp(2.6rem, 11vw, 5rem); line-height: .98; letter-spacing: -.02em;
  }
  .welcome-title em { font-style: italic; }
  .welcome-copy .lede { margin-top: 1rem; }
  .welcome-actions { display: flex; flex-wrap: wrap; align-items: center; gap: .75rem; margin-top: 1.5rem; }
  .welcome-actions .cta { border-radius: 999px; }
  .cta-line {
    display: inline-flex; align-items: center; min-height: 44px; padding: .6rem 1.25rem;
    border: 1.5px solid var(--t-line, #ddd); border-radius: 999px; text-decoration: none;
    font-weight: 600; color: var(--t-ink, #111);
  }
  .cta-line:hover { border-color: var(--t-ink, #111); }
  .welcome-note {
    margin-top: 1rem; font-family: var(--t-font-mono, ui-monospace, monospace); font-size: .72rem;
    letter-spacing: .08em; text-transform: uppercase;
  }
  @media (min-width: 60rem) {
    .welcome { grid-template-columns: minmax(0, 1.05fr) minmax(0, .95fr); gap: 3rem; min-height: 68vh; }
    .welcome-picture { order: 0; }
  }
  /* What it does, how, and what it costs: three cards under the welcome, side by side when wide. */
  .welcome-more { display: grid; gap: 1rem; padding-bottom: 2rem; }
  @media (min-width: 60rem) { .welcome-more { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
  .welcome-card {
    background: var(--t-surface, #fff); border: 1px solid var(--t-line, #ddd);
    border-radius: var(--t-radius, .75rem); padding: 1.25rem 1.25rem 1rem;
  }
  .welcome-card h2 { margin: 0 0 .5rem; }
  .welcome-card p, .welcome-card li { color: var(--t-muted, #555); }
  .welcome-card .steps { padding-left: 1.2rem; display: grid; gap: .5rem; }
  .prose p, .prose li { color: var(--t-muted, #555); }

  /* The Privacy Policy and the Terms. Long, so they get a table of contents whose entries are
     real anchors (44px rows, like every other touch target), and a little scroll margin so a
     heading reached from it does not sit flush against the top of the window. */
  .legal-meta { font-size: .9rem; color: var(--t-muted, #555); }
  .legal-toc { max-width: 46rem; margin-top: 1.5rem; padding: .5rem 1.25rem;
    border: 1px solid var(--t-line, #ddd); border-radius: var(--t-radius, .5rem); }
  .legal-toc ol { margin: 0; padding-left: 1.25rem; }
  .legal-toc a { display: inline-flex; align-items: center; min-height: 44px; }
  .legal h2 { scroll-margin-top: 1.5rem; }
  .legal li + li { margin-top: .5rem; }
  .legal-contact { font-style: normal; display: flex; flex-direction: column; gap: .25rem;
    color: var(--t-ink, #111); }

  /* The footer's own links: the pages the bar leaves out. */
  .footer-nav { display: flex; flex-wrap: wrap; gap: 0 1.25rem; }
  .footer-nav a { display: inline-flex; align-items: center; min-height: 44px; color: inherit; }
  /* "Cookie settings": a button that reads as one of the footer's links beside it. */
  .footer-button {
    display: inline-flex; align-items: center; min-height: 44px; padding: 0; font: inherit;
    color: inherit; text-decoration: underline; background: none; border: 0; cursor: pointer;
  }
  .footer-button[hidden] { display: none; }

  /* The cookie banner (2026-10-10): the twin of the app's CookieConsent.jsx. At the bottom, never
     over the page's content — a choice asked for, not extracted. Reject and Accept look the same:
     refusing must be as easy, and as visible, as accepting. Theme tokens only. */
  .cookie-banner {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 60; padding: .75rem;
  }
  .cookie-banner[hidden], .cookie-choices[hidden], .cookie-actions [hidden] { display: none; }
  .cookie-card {
    max-width: 28rem; padding: 1rem; color: var(--t-ink, #111);
    background: var(--t-surface, #fff); border: 1px solid var(--t-line, #ddd);
    border-radius: var(--t-radius, .8rem); box-shadow: 0 10px 30px -12px rgba(0, 0, 0, .45);
  }
  .cookie-card h2 { margin: 0; font-size: 1rem; }
  .cookie-card p { margin: .35rem 0 0; font-size: .875rem; color: var(--t-muted, #555); }
  .cookie-card p a { font-weight: 600; color: var(--t-ink, #111); }
  .cookie-choices { margin-top: .75rem; }
  .cookie-row {
    display: flex; align-items: center; gap: .75rem; min-height: 60px; padding: .6rem 0;
    border-bottom: 1px solid var(--t-line, #ddd); cursor: pointer;
  }
  .cookie-row:last-child { border-bottom: 0; }
  .cookie-row > span { flex: 1; min-width: 0; font-size: .95rem; }
  .cookie-row em { font-style: normal; font-size: .7rem; font-weight: 600; color: var(--t-muted, #555); }
  .cookie-row > span > span { display: block; margin-top: .15rem; font-size: .8rem; color: var(--t-muted, #555); }
  .cookie-row input { width: 1.4rem; height: 1.4rem; accent-color: var(--t-accent, #0e7490); }
  .cookie-actions { display: flex; flex-wrap: wrap; gap: .5rem; margin-top: .75rem; }
  .cookie-actions button {
    display: inline-flex; align-items: center; justify-content: center; min-height: 44px;
    padding: 0 1rem; font: inherit; font-size: .875rem; font-weight: 600;
    border-radius: 999px; cursor: pointer;
  }
  .cookie-decide { color: var(--t-surface, #fff); background: var(--t-ink, #111); border: 0; }
  .cookie-secondary { color: var(--t-ink, #111); background: none; border: 1px solid var(--t-line, #ddd); }
  @media (min-width: 40rem) {
    .cookie-banner { left: 1rem; right: auto; bottom: 1rem; padding: 0; width: 28rem; }
  }
  footer p { margin: .25rem 0; }
  .steps { padding-left: 1.2rem; display: grid; gap: .5rem; }
