@layer patterns {
  /* ==========================================================================
     THE TRAVERSE — the survey traverse the whole site is printed over.

     Named `traverse`, not `sheet`: .fh-sheet is already the product-sheet
     card in this system, and a background field inheriting a card's
     position: relative and its pointer edge-light is exactly the kind of
     collision a shared prefix invites.

     This replaces the third-party dot-field library the old shell loads
     site-wide, and it is a port rather than a deletion: same job (a live
     field behind every page), more marks, more structure, and a meaning. A
     drifting dot-and-line field says "network" generically and is named as an
     anti-reference in DESIGN.md; this says the one thing the product does.

     WHAT IT DEPICTS. A hydrographic survey is not a picture of the seabed, it
     is a record of passes: the vessel runs parallel track lines across the
     area and records a fix at each reading along the way, and the water
     between two tracks is not measured. That is exactly FullHunt's mechanic —
     the scanner runs repeated passes across address space, records what it
     finds on each pass, and the gaps are gaps. So the sheet prints:

       s-run    the passes. Six track lines on the same upper-left to
                lower-right set the hero survey's contours run on, so the
                sheet and the hero read as one drawing rather than two.
       s-fix    the readings. A fix on a survey sheet is a CROSS, not a dot —
                the distinction is the whole difference from a particle — and
                its longer lower arm is the leadline drop, the same drop-line
                mark the scan-depth section uses. Every fix is evaluated ON
                its own track curve by scripts, never hand-placed, so a mark
                cannot drift off the pass it belongs to.
       __grad   the sheet's latitude graduations, on the left and right
                margins where a chart carries them. Same device as
                .fh-plate--graduated, promoted from one plate to the whole
                sheet.

     It prints marks and never numerals: a background field cannot source a
     measurement, so it claims none (The No Invented Data Rule).

     `body` already carries the ground and a static 96px graticule; the sheet
     is the survey work on top of it, not a second graticule.

     RULES IT HOLDS. Flat — stroke and mask only, no shadow, no filter, no
     gradient used as colour. No gold and no violet: gold is land and the
     primary action, violet is caution, and neither job is happening in a
     background field, so the sheet stays in the engraved line family.
     Fixed, pointer-events: none, aria-hidden, and one step below the flow, so
     it never takes a click, never traps focus and never overlaps a caption.
     Every animation is one-shot and lives inside no-preference, so under
     reduced motion the sheet is fully drawn and completely still.
     ======================================================================== */
  .fh-traverse {
    /* Channel forms are resolved here, in custom properties, so the call
       sites below stay literal-free. */
    --traverse-run: rgb(var(--l-neatline-rgb) / 0.62);
    --traverse-fix: rgb(var(--l-faint-rgb) / 0.3);
    --traverse-edge: rgb(var(--l-neatline-rgb) / 0.9);

    position: fixed;
    inset: 0;
    z-index: var(--l-z-traverse);
    overflow: hidden;
    pointer-events: none;
  }

  .fh-traverse__svg {
    width: 100%;
    height: 100%;
    display: block;
    /* A mask, not a wash: the passes fade out before the margins so they never
       collide with the graduations. Same device as the ticker's edge mask. */
    -webkit-mask-image: linear-gradient(to right, transparent, black 6%, black 94%, transparent);
    mask-image: linear-gradient(to right, transparent, black 6%, black 94%, transparent);
  }

  .fh-traverse .s-run {
    fill: none;
    stroke: var(--traverse-run);
    stroke-width: 1;
    vector-effect: non-scaling-stroke;
  }

  /* Two of the six passes are the deeper set, so the field has a line-weight
     hierarchy instead of six identical strokes. */
  .fh-traverse .s-run--hi {
    stroke: var(--l-contour);
    stroke-width: 1.5;
  }

  .fh-traverse .s-fix {
    fill: none;
    stroke: var(--traverse-fix);
    stroke-width: 1;
    vector-effect: non-scaling-stroke;
  }

  /* The latitude graduations, drawn AS the sheet's margin rather than added
     beside it — 1px ticks every 32px down both edges. */
  .fh-traverse__grad {
    position: absolute;
    inset-block: 0;
    width: 6px;
    background-image: repeating-linear-gradient(
      to bottom,
      var(--traverse-edge) 0 var(--l-hairline),
      transparent var(--l-hairline) 32px
    );
  }

  .fh-traverse__grad--start { inset-inline-start: 0; }
  .fh-traverse__grad--end { inset-inline-end: 0; }

  /* ---- Signature reveal: the sheet runs its passes once, on load. The
          tracks draw along their own length; the fixes come up behind the
          advancing track, in order. One shot, never rearmed, and the resting
          state below this block is already complete — which is what makes the
          reduced-motion case a no-op rather than a hidden field. ---- */
  @media (prefers-reduced-motion: no-preference) {
    .fh-traverse .s-run {
      /* pathLength="1" normalises every curve, so one dasharray spans each
         pass exactly regardless of its real length. */
      stroke-dasharray: 1;
      stroke-dashoffset: 1;
      animation: fh-traverse-run var(--l-dur-sweep) var(--l-ease) forwards;
      animation-delay: calc(var(--fh-run-i, 0) * 150ms);
    }

    .fh-traverse .s-fix {
      opacity: 0;
      animation: fh-traverse-fix var(--l-dur-scan) var(--l-ease) forwards;
      animation-delay: calc(300ms + var(--fh-fix-i, 0) * 24ms);
    }

    /* The graduations step in down the margin. steps(), not a smooth wipe:
       a graduation is a discrete series of ticks, so it is revealed tick-wise
       — the same "discrete, never interpolated" rule that gives the
       scan-depth section its stepped floor. */
    .fh-traverse__grad {
      clip-path: inset(0 0 100% 0);
      animation: fh-traverse-grad var(--l-dur-sweep) steps(10) forwards;
    }
  }

  @keyframes fh-traverse-run {
    to {
      stroke-dashoffset: 0;
    }
  }

  @keyframes fh-traverse-fix {
    from { opacity: 0; }
    to { opacity: 1; }
  }

  @keyframes fh-traverse-grad {
    from { clip-path: inset(0 0 100% 0); }
    to { clip-path: inset(0 0 0 0); }
  }

  /* ==========================================================================
     THE PLATE — the hero. Not a hero: a chart plate, with the title cartouche
     ruled into the survey field. The chart is an authored asset, not stock
     decoration, and it carries the product's real vocabulary: soundings are
     hosts, contours are depth of exposure and hazard marks are findings.
     The blank quarter that used to be stamped NOT SCANNED / NO DATA HELD was
     removed on 2026-08-27; the no-total-coverage constraint it stood for is
     held by the copy and by the scan-depth section's computed NOT SCANNED
     slice, not by a label on a background field.
     ======================================================================== */
  /* ==========================================================================
     THE SURVEY — the whole first viewport IS the chart, edge to edge. The title
     sits in a cartouche printed ON the survey, the way a chart's title block is
     inset into the sheet. Not a text column beside a picture of a chart: that
     arrangement is the template this page is replacing.
     ======================================================================== */
  .fh-survey {
    position: relative;
    display: flex;
    align-items: center;
    min-height: min(94svh, 920px);
    padding-block: var(--l-space-14);
    overflow: hidden;
    border-block-end: var(--l-hairline) solid var(--l-neatline);
  }

  .fh-survey__field {
    position: absolute;
    inset: 0;
    z-index: var(--l-z-substrate);
    pointer-events: none;
  }

  .fh-survey__field svg {
    width: 100%;
    height: 100%;
    display: block;
  }

  /* The pointer-tracked position readout: a chart with a live fix on it. It is
     driven entirely by the pointer, so it never animates on its own. */
  .fh-survey__readout {
    position: absolute;
    z-index: var(--l-z-content);
    inset-block-start: 1.25rem;
    inset-inline-end: var(--l-gutter);
    display: none;
    gap: 1.25rem;
    padding: 0.5rem 0.875rem;
    background-color: var(--l-ground);
    border: var(--l-hairline) solid var(--l-contour-hi);
    border-radius: var(--l-radius-sm);
    color: var(--l-sounding);
    font-family: var(--l-face-data);
    font-size: var(--l-fs-label);
    font-weight: var(--l-fw-semibold);
    letter-spacing: 0.08em;
    white-space: nowrap;
  }

  .fh-survey__readout b {
    color: var(--l-gold);
    font-weight: var(--l-fw-bold);
  }

  /* --l-label (6.7:1), not --l-faint (3.4:1): the token file restricts faint to
     large text and non-text, and these labels are 11px. */
  .fh-survey__readout span {
    color: var(--l-label);
  }

  @media (hover: hover) and (min-width: 62rem) {
    .fh-survey__readout {
      display: flex;
    }
  }

  /* ---- The cartouche: a solid inset title block with ruled corners. Solid,
          never translucent — legibility over the survey is not negotiable, and
          frosted glass is the anti-reference. ---- */
  .fh-cartouche {
    position: relative;
    z-index: var(--l-z-content);
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 1.25rem;
    min-width: 0;
    /* 48rem, not 46: the search field is the widest thing inside, and its
       62-character placeholder needs 484px of text area at 13px in JetBrains
       Mono (exactly 0.6em per glyph). At 46rem the field offered 480px and
       ellipsised the example domain -- the part that teaches the query form. */
    max-width: min(48rem, 100%);
    padding: clamp(1.5rem, 3vw, 2.5rem);
    background-color: var(--l-ground);
    border: var(--l-hairline) solid var(--l-land-edge);
  }

  /* Corner ticks, drawn with two gradients rather than four extra elements. */
  .fh-cartouche::before,
  .fh-cartouche::after {
    content: "";
    position: absolute;
    width: 14px;
    height: 14px;
    border: 2px solid var(--l-gold);
    pointer-events: none;
  }

  .fh-cartouche::before {
    inset-block-start: -1px;
    inset-inline-start: -1px;
    border-inline-end: 0;
    border-block-end: 0;
  }

  .fh-cartouche::after {
    inset-block-end: -1px;
    inset-inline-end: -1px;
    border-inline-start: 0;
    border-block-start: 0;
  }

  /* The series line is RETIRED (owner call 2026-08-27), zero call sites.
     A chart cartouche really does carry a series designation, so the component
     was legitimate -- but what it held was "Complete Attack Surface Intelligence
     For Security Vendors", which is a marketing kicker wearing chart marginalia,
     not a series. That is a tracked-caps eyebrow above the h1, the one pattern
     the craft floor bans outright and DESIGN.md already removed ten of. Its own
     first word was also a total-coverage claim.
     Not re-pointed at a real series designation, because this survey has no
     real one and inventing "SERIES 4 / 1:50 000" would be fabricated data --
     the worst failure available on this product. Deleted instead. */

  .fh-cartouche h1 {
    margin: 0;
  }

  .fh-cartouche h1 em {
    display: block;
    color: var(--l-gold);
    font-style: normal;
  }

  .fh-cartouche__lead {
    max-width: 34rem;
  }

  .fh-cartouche__actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem;
  }

  .fh-cartouche__proof {
    display: flex;
    align-items: center;
    gap: 0.625rem;
    margin: 0;
    color: var(--l-label);
    font-size: var(--l-fs-data);
  }

  /* ---- Field paint ---- */
  .fh-survey .p-water-shoal { fill: var(--l-shoal); }
  .fh-survey .p-land {
    fill: var(--l-land);
    stroke: var(--l-land-edge);
    stroke-width: 2;
  }
  .fh-survey .p-contour {
    fill: none;
    stroke: var(--l-contour);
    stroke-width: 1.5;
  }
  .fh-survey .p-contour-hi { stroke: var(--l-contour-hi); stroke-width: 2; }
  .fh-survey .p-grat { stroke: rgb(var(--l-neatline-rgb) / 0.4); stroke-width: 1; fill: none; }
  .fh-survey .p-sounding {
    fill: var(--l-sounding);
    font-family: var(--l-face-data);
    font-size: 13px;
    font-variant-numeric: tabular-nums;
  }
  .fh-survey .p-sounding--faint { fill: var(--l-faint); }
  .fh-survey .p-label {
    fill: var(--l-label);
    font-family: var(--l-face-data);
    font-size: 11px;
    letter-spacing: 0.16em;
    text-transform: uppercase;
  }
  .fh-survey .p-label--crit { fill: var(--fh-sev-critical); }
  .fh-survey .p-hazard { fill: none; stroke: var(--l-caution); stroke-width: 2; }
  .fh-survey .p-hazard-crit { stroke: var(--fh-sev-critical); }
  .fh-survey .p-mark { fill: var(--l-gold); }
  /* .p-unsurveyed is RETIRED (owner call 2026-08-27), zero call sites. The
     dashed quarter stamped NOT SCANNED / NO DATA HELD read as a floating dashed
     box in a background graphic -- decoration wearing a chart's clothes, which
     is the tell this rule set warns about elsewhere. See the note at the top of
     THE PLATE: the honesty constraint it served is now carried by the COPY, not
     by a stamp on the field. The scan-depth section keeps its own NOT SCANNED
     slice (.sc-unsurveyed), where the blank is computed from the real host list
     and therefore does mark a measured absence. */

  /* ---- Signature reveal: the survey runs itself once. Contours draw along
          their own length, then the soundings drop in behind them. One shot,
          fully visible at rest, and entirely absent under reduced motion. ---- */
  @media (prefers-reduced-motion: no-preference) {
    .fh-survey .p-draw {
      stroke-dasharray: 3200;
      stroke-dashoffset: 3200;
      animation: fh-draw 2000ms var(--l-ease) forwards;
      animation-delay: calc(var(--fh-draw-i, 0) * 170ms);
    }

    .fh-survey .p-sweep {
      opacity: 0;
      animation: fh-sweep-in 700ms var(--l-ease) forwards;
      animation-delay: calc(560ms + var(--fh-sweep-i, 0) * 26ms);
    }
  }

  @keyframes fh-draw {
    to {
      stroke-dashoffset: 0;
    }
  }

  @keyframes fh-sweep-in {
    from { opacity: 0; }
    to { opacity: 1; }
  }

  /* ==========================================================================
     COMPASS ROSE — the integrations hub. A rose is a radial diagram of
     bearings from one centre, which is exactly the relationship the hub
     describes, so the form is the information rather than an illustration of
     it. Eight tools sit on eight bearings.
     ======================================================================== */
  .fh-rose {
    position: relative;
    display: grid;
    place-items: center;
    padding-block: var(--l-space-12);
  }

  .fh-rose__svg {
    width: 100%;
    max-width: 42rem;
    height: auto;
  }

  .fh-rose__svg .r-ring {
    fill: none;
    stroke: var(--l-neatline);
    stroke-width: 1;
  }

  .fh-rose__svg .r-ring--inner {
    stroke: var(--l-contour-hi);
  }

  .fh-rose__svg .r-bearing {
    stroke: rgb(var(--l-neatline-rgb) / 0.8);
    stroke-width: 0.75;
  }

  .fh-rose__svg .r-point {
    fill: var(--l-deep);
    stroke: var(--l-contour-hi);
    stroke-width: 1;
  }

  .fh-rose__svg .r-hub {
    fill: var(--l-land);
    stroke: var(--l-gold);
    stroke-width: 1.5;
  }

  .fh-rose__svg .r-name {
    fill: var(--l-ink);
    font-family: var(--l-face-display);
    font-size: 11px;
    font-weight: 700;
  }

  .fh-rose__svg .r-sub {
    fill: var(--l-label);
    font-family: var(--l-face-data);
    font-size: 8.5px;
  }

  .fh-rose__svg .r-hub-name {
    fill: var(--l-gold);
    font-family: var(--l-face-display);
    font-size: 13px;
    font-weight: 800;
    letter-spacing: 0.02em;
  }

  .fh-rose__svg .r-hub-sub {
    fill: var(--l-sounding);
    font-family: var(--l-face-data);
    font-size: 8.5px;
    letter-spacing: 0.12em;
  }

  /* ==========================================================================
     STATIONS — the survey procedure. Four numbered stations run in sequence,
     ruled like a chart's source diagram rather than four floating cards.
     ======================================================================== */
  .fh-stations {
    display: grid;
    gap: 1px;
    background-color: var(--l-neatline);
    border: var(--l-hairline) solid var(--l-neatline);
    border-radius: var(--l-radius-md);
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 14rem), 1fr));
  }

  .fh-station {
    position: relative;
    padding: 1.75rem 1.5rem;
    background-color: var(--l-deep);
  }

  /* ---- Pointer-tracked edge light.

     The incumbent card had this: a radial gradient following the cursor,
     `--x`/`--y` driven by JS. It was a soft violet wash bled across the whole
     card, which is the glow this world does not do. Same interaction, drawn
     instead of blurred — the gradient is masked into a 1px ring, so it reads as
     the neatline catching light as the pointer passes. Gold, not violet, since
     violet is the caution job here. ---- */
  @media (prefers-reduced-motion: no-preference) {
    .fh-station::after,
    .fh-sheet::after,
    .fh-panel::after,
    .fh-block::after,
    .fh-edition::after,
    .fh-section::after,
    .fh-figure::after,
    .fh-source__cell::after {
      content: "";
      position: absolute;
      inset: 0;
      padding: var(--l-hairline);
      background: radial-gradient(
        14rem circle at var(--x, 50%) var(--y, 0%),
        var(--l-gold),
        transparent 45%
      );
      -webkit-mask:
        linear-gradient(#000 0 0) content-box exclude,
        linear-gradient(#000 0 0);
      mask:
        linear-gradient(#000 0 0) content-box exclude,
        linear-gradient(#000 0 0);
      mask-composite: exclude;
      opacity: 0;
      transition: opacity var(--l-dur-base) var(--l-ease);
      pointer-events: none;
    }

    .fh-station:hover::after,
    .fh-station:focus-within::after,
    .fh-sheet:hover::after,
    .fh-sheet:focus-within::after,
    .fh-panel:hover::after,
    .fh-panel:focus-within::after,
    .fh-block:hover::after,
    .fh-block:focus-within::after,
    .fh-edition:hover::after,
    .fh-edition:focus-within::after,
    .fh-section:hover::after,
    .fh-section:focus-within::after,
    .fh-figure:hover::after,
    .fh-figure:focus-within::after,
    .fh-source__cell:hover::after,
    .fh-source__cell:focus-within::after {
      opacity: 1;
    }

    /* These cards must establish a stacking context or the ring lands over
       their own content. */
    .fh-sheet,
    .fh-panel,
    .fh-block,
    .fh-edition,
    .fh-section,
    .fh-figure,
    .fh-source__cell {
      position: relative;
    }
  }

  /* ---- Stroke figures draw themselves on first sight.

     Both of these are genuinely stroke-based, so unlike the filled sprite icons
     they take the hero's own contract: the line draws along its own length.
     pathLength="1" normalises each one, so a single dash spans it exactly
     whatever its real geometry. One shot, on the way in, then it stays. ---- */
  @media (prefers-reduced-motion: no-preference) {
    .fh-section.is-drawn .sc-floor,
    .fh-section.is-drawn .sc-drop {
      animation: fh-sym-draw var(--l-dur-sweep) var(--l-ease) both;
    }

    .fh-section.is-drawn .sc-drop {
      animation-duration: var(--l-dur-draw);
      animation-delay: calc(var(--l-dur-sweep) * 0.45);
    }

    .fh-rose.is-drawn .r-ring {
      animation: fh-sym-draw var(--l-dur-sweep) var(--l-ease) both;
      animation-delay: calc(var(--r, 0) * 160ms);
    }
  }

  .fh-station__step {
    display: flex;
    align-items: center;
    gap: 0.625rem;
    color: var(--l-label);
    font-size: var(--l-fs-label);
    font-weight: var(--l-fw-bold);
    letter-spacing: var(--l-ls-label);
    text-transform: uppercase;
  }

  .fh-station__title {
    margin: 0.75rem 0 0;
    color: var(--l-ink);
    font-size: var(--l-fs-d3);
    font-weight: var(--l-fw-bold);
  }

  .fh-station__body {
    margin-block-start: 0.5rem;
    color: var(--l-sounding);
    font-size: var(--l-fs-small);
    line-height: var(--l-lh-relaxed);
  }

  /* ==========================================================================
     EDITIONS — the survey history. Three editions of one chart.
     ======================================================================== */
  .fh-editions {
    display: grid;
    gap: var(--l-space-12);
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr));
    counter-reset: edition;
  }

  /* A hairline all the way round, not a 2px accent on one edge: once these
     gained a card radius the top border read as the coloured accent-border
     pattern the craft floor bans. The incumbent card was a plain bordered box
     too. */
  .fh-edition {
    padding: 1.25rem;
    background-color: var(--l-deep);
    border: var(--l-hairline) solid var(--l-neatline);
    border-radius: var(--l-radius-card);
  }

  .fh-edition__title {
    margin: 0;
    color: var(--l-ink);
    font-size: var(--l-fs-d3);
    font-weight: var(--l-fw-bold);
  }

  .fh-edition__body {
    margin-block-start: 0.625rem;
    color: var(--l-sounding);
    font-size: var(--l-fs-small);
    line-height: var(--l-lh-relaxed);
  }

  /* THE PLAN CARD. `.fh-edition` carries two unrelated things: a story panel
     (home.html's three-beat origin sequence, no price and no action) and a
     priced plan. They diverge on three points, so the plan takes a modifier
     rather than bending the base card under both.

     All three are the incumbent's own structure, read off master rather than
     estimated: `.pc-card` was `display: flex; flex-direction: column`
     (pricing/console.html:649), `.pc-card__body` was `flex-grow: 1` (:688),
     and `.pc-card__price` was 2rem/700 on white over a 0.875rem term (:669).
     The migration flattened all of it into one `small` paragraph. */
  .fh-edition--plan {
    display: flex;
    flex-direction: column;
    padding: 1.5rem;
  }

  /* 1. THE PRICE IS THE ANSWER. Set at `small` inside a body paragraph it
        landed a full step BELOW the plan's own name -- the card said
        "Builder" louder than it said "$149", on a page whose entire job is
        the second number. `d2` is 34px at desktop against the incumbent's
        32px, and stays under the h2 that titles the group. Tabular figures
        so $149 / $1,499 / $5,500 align down the row. */
  .fh-edition--plan .fh-edition__body strong {
    display: block;
    margin-block-end: 0.25rem;
    color: var(--l-ink);
    font-size: var(--l-fs-d2);
    font-weight: var(--l-fw-bold);
    font-variant-numeric: tabular-nums;
    letter-spacing: var(--l-ls-cartouche);
    line-height: var(--l-lh-tight);
  }

  /* 2. ONE CTA BASELINE. The three self-serve plans carry 7, 9 and 7 features
        into cards the grid has already stretched to equal height, so the
        buttons landed at three different heights and the shortest card ended
        in 155px of nothing. Pushing the actions row to the floor spends that
        space instead of leaving it, and gives the three CTAs a shared line to
        compare across -- which is the whole point of putting plans side by
        side. Its own class rather than `.fh-mt-3`: a utility sets a fixed
        margin from the `utilities` layer and would win over an `auto` set
        here. */
  .fh-edition__actions {
    margin-block-start: auto;
    padding-block-start: 1.25rem;
  }
}

@layer patterns {
  /* ==========================================================================
     INSET DETAIL PLATE — a real chart carries magnified insets for harbours,
     ruled off inside the sheet with their own title and scale note. That is
     exactly the right frame for a product screenshot: it makes the media
     structural rather than decorative.
     ======================================================================== */
  .fh-inset {
    position: relative;
    display: flex;
    flex-direction: column;
    min-width: 0;
    margin: 0;
    background-color: var(--l-deep);
    border: var(--l-hairline) solid var(--l-land-edge);
  }

  .fh-inset::before,
  .fh-inset::after {
    content: "";
    position: absolute;
    width: 11px;
    height: 11px;
    border: 2px solid var(--l-gold);
    pointer-events: none;
    z-index: var(--l-z-content);
  }

  .fh-inset::before {
    inset-block-start: -1px;
    inset-inline-start: -1px;
    border-inline-end: 0;
    border-block-end: 0;
  }

  .fh-inset::after {
    inset-block-end: -1px;
    inset-inline-end: -1px;
    border-inline-start: 0;
    border-block-start: 0;
  }

  .fh-inset__bar {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.375rem 0.75rem;
    padding: 0.875rem 1rem;
    border-block-end: var(--l-hairline) solid var(--l-neatline);
    font-size: var(--l-fs-small);
  }

  .fh-inset__name {
    color: var(--l-ink);
    font-weight: var(--l-fw-semibold);
    letter-spacing: -0.005em;
  }

  .fh-inset__scale {
    margin-inline-start: auto;
    color: var(--l-label);
    font-size: var(--l-fs-label);
    font-weight: var(--l-fw-semibold);
    letter-spacing: var(--l-ls-label);
    text-transform: uppercase;
  }

  /* Matted like the product plates: even surround, own hairline frame. */
  .fh-inset {
    padding: 0;
  }

  .fh-inset__mat {
    padding: clamp(0.875rem, 2vw, 1.5rem);
    background-color: var(--l-ground);
  }

  .fh-inset img {
    display: block;
    width: 100%;
    height: auto;
    background-color: var(--l-deep);
    border: var(--l-hairline) solid var(--l-neatline);
    border-radius: var(--l-radius-sm);
  }

  .fh-inset figcaption {
    padding: 0.9375rem 1rem;
    border-block-start: var(--l-hairline) solid var(--l-neatline);
    color: var(--l-sounding);
    font-size: var(--l-fs-small);
    line-height: var(--l-lh-normal);
  }

  /* No global aspect override: the product shot is shown whole, because
     cropping a dense dashboard to a marketing ratio is what made these
     unreadable. --trim exists only for shots that carry dead space below their
     content; it removes emptiness, never information. */
  .fh-inset--trim img {
    aspect-ratio: 2.1 / 1;
    object-fit: cover;
    object-position: center top;
  }

  /* ==========================================================================
     DEPTH PROFILE — the chart's vertical section along a survey line. Here it
     profiles exposure across the perimeter: the deeper the trace, the more
     that segment exposes.
     ======================================================================== */
  /* ==========================================================================
     SECTION THROUGH THE PERIMETER — a cross-section, which is the one chart
     form this world had claimed but never actually drawn. Five strata, one
     column per host, and a stepped floor: discovery is discrete, so nothing
     between two hosts may be interpolated. The old profile smoothed a curve
     through invented soundings; this cannot, because the geometry is derived
     from the host list.
     ======================================================================== */
  .fh-section {
    margin: 0;
    border: var(--l-hairline) solid var(--l-neatline);
    border-radius: var(--l-radius-md);
    background-color: var(--l-deep);
    overflow: hidden;
  }

  .fh-section__head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.5rem 1.5rem;
    padding: 0.75rem 1rem;
    border-block-end: var(--l-hairline) solid var(--l-neatline);
  }

  /* The domain is data, so it is the one thing here set in mono. */
  .fh-section__subject {
    color: var(--l-ink);
    font-family: var(--l-face-data);
    font-size: var(--l-fs-small);
  }

  .fh-section__count {
    color: var(--l-label);
    font-size: var(--l-fs-label);
    font-weight: var(--l-fw-semibold);
    letter-spacing: var(--l-ls-label);
    text-transform: uppercase;
  }

  .fh-section__scroll {
    overflow-x: auto;
    contain: inline-size;
  }

  /* SVG text scales with the viewBox, so a full-width fit would render these
     11px labels at ~3.6px on a phone. The section holds a legible minimum and
     scrolls instead -- a section is a wide drawing and shrinking it is a lie
     about legibility. */
  .fh-section__plate {
    display: block;
    width: 100%;
    min-width: 58rem;
    height: auto;
  }

  /* Strata deepen by flat colour only — The Flat Sheet Rule holds, so no
     gradient and no shadow does the work of depth. */
  /* One hairline per stage instead of five graduated fills. Stacked tints read
     as an infographic; a drawing rules its divisions and leaves the ground
     alone. Everything therefore sits on --l-deep, where the critical mark holds
     4.71:1 and the printed counts 13:1. */
  .sc-rule { fill: none; stroke: rgb(var(--l-neatline-rgb) / 0.8); stroke-width: 1; }

  /* Stage names are labels, so they take the house face. Mono here would be a
     costume for "technical"; it is reserved for the numbers, which are measured. */
  .sc-stratum {
    fill: var(--l-label);
    font-size: 13px;
    text-anchor: end;
  }

  /* Same 11px as .sc-stratum: both are marginalia on this 1200-unit viewBox, so
     they share a size and are separated by weight of colour instead. */
  .sc-tally {
    fill: var(--l-ink);
    font-family: var(--l-face-data);
    font-size: 11px;
    font-variant-numeric: tabular-nums;
    text-anchor: start;
  }

  .sc-surface { fill: none; stroke: var(--l-contour-hi); stroke-width: 2; }

  .sc-edge {
    fill: var(--l-label);
    font-family: var(--l-face-data);
    font-size: 10px;
    letter-spacing: 0.1em;
    text-transform: uppercase;
  }

  .sc-edge--end { text-anchor: end; }

  /* One host, one drop line, stopping where the survey stopped. */
  .sc-drop { fill: none; stroke: rgb(var(--l-sounding-rgb) / 0.34); stroke-width: 1; }

  .sc-count {
    fill: var(--l-sounding);
    font-family: var(--l-face-data);
    font-size: 10px;
    font-variant-numeric: tabular-nums;
    text-anchor: middle;
  }

  .sc-floor {
    fill: none;
    stroke: var(--l-gold);
    stroke-width: 2;
    stroke-linejoin: miter;
  }

  .sc-fix { fill: var(--l-gold); }

  .sc-unsurveyed {
    fill: rgb(var(--l-faint-rgb) / 0.06);
    stroke: var(--l-faint);
    stroke-width: 1;
    stroke-dasharray: 4 5;
  }

  .sc-unsurveyed-label {
    fill: var(--l-label);
    font-family: var(--l-face-data);
    font-size: 10px;
    letter-spacing: 0.1em;
    text-transform: uppercase;
  }

  /* The hazard is a cross, so the finding survives without its colour. */
  .sc-hazard { fill: none; stroke: var(--fh-sev-critical); stroke-width: 2.5; }

  /* Neutral, not critical red: the severity colours run 4.7:1 to 9.1:1 on this
     ground, under the house label floor, so none of them ever sets text. */
  .sc-hazard-label {
    fill: var(--l-ink);
    font-family: var(--l-face-data);
    font-size: 11px;
    letter-spacing: 0.04em;
    text-anchor: end;
  }

  /* The strata are the response keys, said out loud. */
  .fh-section__keys {
    display: grid;
    gap: 1px;
    margin: 0;
    background-color: var(--l-neatline);
    border-block-start: var(--l-hairline) solid var(--l-neatline);
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 11rem), 1fr));
  }

  .fh-section__key {
    padding: 0.75rem 1rem;
    background-color: var(--l-deep);
  }

  .fh-section__key dt {
    display: flex;
    align-items: center;
    gap: 0.4375rem;
    color: var(--l-label);
    font-size: var(--l-fs-label);
    font-weight: var(--l-fw-bold);
    letter-spacing: var(--l-ls-label);
    text-transform: uppercase;
  }

  .fh-section__key dd {
    margin: 0.375rem 0 0;
    color: var(--l-sounding);
    font-family: var(--l-face-data);
    font-size: var(--l-fs-data);
    overflow-wrap: anywhere;
  }

  /* ==========================================================================
     SOURCE DIAGRAM — every chart prints a small box declaring which surveys it
     was compiled from and when. That is what a data-freshness panel is.
     ======================================================================== */
  .fh-source {
    display: grid;
    gap: 1px;
    background-color: var(--l-neatline);
    border: var(--l-hairline) solid var(--l-land-edge);
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 12rem), 1fr));
  }

  .fh-source__cell {
    padding: 1.25rem;
    background-color: var(--l-deep);
  }


  .fh-source__key {
    display: block;
    color: var(--l-label);
    font-size: var(--l-fs-label);
    font-weight: var(--l-fw-bold);
    letter-spacing: var(--l-ls-label);
    text-transform: uppercase;
  }

  .fh-source__val {
    display: block;
    margin-block-start: 0.5rem;
    color: var(--l-gold);
    font-family: var(--l-face-data);
    /* Was the literal `clamp(1.5rem, 2.6vw, 2rem)` -- the SAME floor and the
       SAME 2.6vw slope as --l-fs-d2, with the upper endpoint drifted 2px low.
       That is a hand-copied token, which is the drift the Owned Scale Rule
       exists to stop, and it is fixed the way .fh-legend__title's 1.3125rem
       was: moved onto the step rather than kept as a private one. Identical
       below ~1231px; 32px -> 34px at the wide end only. */
    font-size: var(--l-fs-d2);
    font-weight: var(--l-fw-bold);
    line-height: 1;
    font-variant-numeric: tabular-nums;
  }


  /* ==========================================================================
     LEGEND ICONS — the restored feature symbols, set in the legend's symbol
     gutter at chart-symbol scale.
     ======================================================================== */
  .fh-legend__icon {
    width: 2rem;
    height: 2rem;
    margin-block-start: 0.125rem;
  }

  /* ---- Symbol scan.

     The icons are fill-based SVG files with zero `stroke` attributes, so the
     hero's `stroke-dashoffset` draw-on cannot be applied to them; and they load
     as image elements, which nothing can reach inside to stagger their paths —
     two of the six are a single path anyway. The reveal therefore acts on the whole
     element, through `clip-path`, which is already this world's masking device.

     Timed in `steps()`, not smoothly: the same rule that makes the section's
     floor stepped rather than curved. A scan resolves in increments, so the
     symbol builds in eight bands instead of dissolving in.

     `both` holds the clipped state through the stagger delay, so no icon
     flashes before its turn. JS adds the class and removes it on animationend,
     which leaves the default state fully visible — with JS off, or under
     reduced motion, every symbol is simply there. ---- */
  @media (prefers-reduced-motion: no-preference) {
    .fh-legend__item.is-scan .fh-legend__icon {
      animation: fh-scan var(--l-dur-scan, 460ms) steps(8, end) both;
      animation-delay: calc(var(--fh-scan-i, 0) * 70ms);
    }
  }

  /* The sprite itself never renders; only its <symbol>s are referenced. */
  .fh-symbols { display: none; }

  /* ---- Symbol part pulse.

     The four Lottie players in the 4-step section are Lordicon outline icons:
     stroke line art with trim-path animation over seven layers, static until
     hovered (`loop hover`). These chart icons are filled silhouettes with no
     strokes, so trim-path has nothing to draw on. What transfers is the
     behaviour, not the technique: static at rest, alive while hovered, one part
     at a time.

     Each icon is one <symbol> referenced by <use>, so 22.6 KB of path data is
     paid once rather than twelve times — inlining all twelve copies would have
     added ~41 KB to an 88 KB page. Selectors reach into a use-element shadow
     tree, and custom properties inherit into it, which is what lets a hover on
     the row drive parts inside the clone: at rest `--pulse` is 0, so the
     duration computes to 0s and no animation exists.

     Parts are the paths the artwork already has. Subpaths are never split — a
     later subpath is often a counter drawn in the opposite winding direction,
     and promoting one to its own element fills the hole in. Two of the six
     icons are a single path and pulse as one beat; same motion, fewer steps. ---- */
  @media (prefers-reduced-motion: no-preference) {
    .fh-legend__item:hover .fh-legend__icon,
    .fh-legend__item:focus-within .fh-legend__icon {
      --pulse: 1;
    }

    /* A TRANSITION, NOT AN ANIMATION -- and the reason is the whole bug.

       This used to be `animation-duration: calc(var(--pulse,0) * ...)`, which
       CANNOT WORK: changing a duration never restarts an animation. The
       animation was created at load with duration 0s, completed instantly, and
       `fill-mode: both` pinned it at its `to` state. Flipping --pulse to 1 on
       hover moved the duration to 900ms, but the animation's clock was already
       minutes past the end, so it stayed exactly where it was. Verified by
       pixel hash: EIGHT identical frames across a hover, and identical again on
       a second hover. Hover produced no visual change at all.

       No gate could have caught it -- axe, the width sweep and both parity
       checks measure a resting page, and the counts stayed correct because the
       animation genuinely existed. It was only ever going to show up by
       looking, which is how it was reported.

       `.fh-sym__part` lives inside a <use> shadow tree, so an outside selector
       cannot match it and only INHERITED custom properties reach in -- that
       constraint is why --pulse exists and it still holds. A transition works
       within it: the parts' computed values depend on --pulse, so flipping it
       drives them every time, in both directions, with no clock to get stuck.

       Brightness rather than the old opacity/scale keyframe, because a
       transition has no midpoint to dip through: at rest this computes to
       brightness(1) scale(1), which is the resting look the icons already had,
       and on hover each part lifts 70ms after the one before it. One pass,
       ending at rest -- the contract DESIGN.md describes -- and it now replays
       on every pointer arrival instead of once, never. */
    .fh-sym__part {
      transform-box: fill-box;
      transform-origin: center;
      filter: brightness(calc(1 + 0.5 * var(--pulse, 0)));
      transform: scale(calc(1 + 0.07 * var(--pulse, 0)));
      transition: filter var(--l-dur-scan) ease-in-out,
                  transform var(--l-dur-scan) ease-in-out;
      transition-delay: calc(var(--p, 0) * 70ms);
    }
  }

  /* ---- Stroke symbols draw themselves.

     The four OEM symbols are inline, stroke-based line art, so unlike the
     filled sprite icons they can take the hero's actual contract: the line
     draws along its own length. Each path carries pathLength="1", which
     normalises its length so one dash spans exactly the whole stroke — a fixed
     dasharray does not work here, because a value larger than the real path
     length contains the whole thing and never opens a gap. ---- */
  @media (prefers-reduced-motion: no-preference) {
    .fh-legend__item:hover .fh-legend__symbol [stroke],
    .fh-legend__item:focus-within .fh-legend__symbol [stroke] {
      animation: fh-sym-draw var(--l-dur-draw) var(--l-ease) both;
    }
  }

  @keyframes fh-sym-draw {
    from { stroke-dasharray: 1; stroke-dashoffset: 1; }
    to { stroke-dasharray: 1; stroke-dashoffset: 0; }
  }

  /* One pass, ending where it rests: a part brightens as the sequence reaches
     it and stays. Looping this read as a repeating twitch that was harder to
     follow than no motion at all. */
  @keyframes fh-sym-pulse {
    from { opacity: 0.45; transform: scale(0.9); }
    to { opacity: 1; transform: scale(1); }
  }

  @keyframes fh-scan {
    from { clip-path: inset(0 100% 0 0); }
    to { clip-path: inset(0 0 0 0); }
  }

  /* ==========================================================================
     PLATE TEXTURE — engraved stipple. A chart sheet is not a flat colour
     field; this is the plate grain, static and cheap (two gradients).
     ======================================================================== */
  .fh-plate--engraved {
    position: relative;
    isolation: isolate;
  }

  .fh-plate--engraved::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    background-image:
      radial-gradient(circle at 1px 1px, rgb(var(--l-sounding-rgb) / 0.055) 1px, transparent 0),
      linear-gradient(115deg, rgb(var(--l-gold-rgb) / 0.028), transparent 55%);
    background-size: 7px 7px, 100% 100%;
  }

  /* Story figure with its surveyed dotted lines reinstated. */
  .fh-story {
    position: relative;
    min-width: 0;
  }

  .fh-story__art {
    display: block;
    width: 100%;
    height: auto;
  }

  .fh-story__rule {
    position: absolute;
    pointer-events: none;
    opacity: 0.85;
  }

  .fh-story__rule--a {
    inset-block-start: 8%;
    inset-inline-start: -6%;
    width: 108px;
  }

  .fh-story__rule--b {
    inset-block-end: 10%;
    inset-inline-end: -4%;
    width: 46px;
  }

  @media (max-width: 61.99rem) {
    .fh-story__rule { display: none; }
  }
}

@layer patterns {
  /* The third title line: the tempo. Restored copy, set as a chart annotation
     rather than a perpetually retyping animation. */
  .fh-cartouche__tempo {
    display: block;
    margin-block-start: 0.375rem;
    color: var(--l-sounding);
    font-size: clamp(var(--l-fs-body), 1.5vw, var(--l-fs-d3));
    font-weight: var(--l-fw-medium);
    letter-spacing: 0.06em;
    text-transform: uppercase;
  }

  /* Station media: the Lottie symbols play on hover, never on a loop of their
     own, so nothing runs forever. */
  .fh-station__media {
    width: 76px;
    height: 76px;
    margin-block-end: 1rem;
  }

  @media (prefers-reduced-motion: reduce) {
    .fh-station__media { display: none; }
  }
}

@layer patterns {
  .fh-editions--stacked {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--l-space-12);
  }

  /* The incumbent stepped the middle card in, so the three read as a path
     rather than a stack. */
  @media (min-width: 48rem) {
    .fh-editions--stacked .fh-edition:nth-child(2) {
      margin-inline-start: var(--l-space-12);
    }
  }
}

@layer patterns {
  /* The tempo line types itself, the way the incumbent hero did. The caret is
     part of the type, so the line never collapses while a phrase is deleting. */
  .fh-cartouche__tempo[data-fh-typing] {
    color: var(--l-gold);
  }

  .fh-cartouche__tempo[data-fh-typing]::after {
    content: "";
    display: inline-block;
    width: 0.6ch;
    height: 1em;
    margin-inline-start: 0.15em;
    background-color: var(--l-gold);
    translate: 0 0.14em;
    animation: fh-caret 1s steps(1, end) infinite;
  }

  @keyframes fh-caret {
    0%, 49% { opacity: 1; }
    50%, 100% { opacity: 0; }
  }
}

@layer patterns {
  /* ==========================================================================
     NOTICES TICKER — restored to the incumbent's shape: a live "recently added"
     dot, then a horizontal run of cards that scrolls and pauses on hover, with
     the run masked to fade at both edges. Unlike the incumbent, the whole thing
     stops and reflows into a still grid under prefers-reduced-motion.
     ======================================================================== */
  .fh-ticker__live {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin: 0 0 1.25rem;
    color: var(--l-label);
    font-size: var(--l-fs-label);
    font-weight: var(--l-fw-semibold);
    letter-spacing: var(--l-ls-label);
    text-transform: uppercase;
  }

  /* Was a blinking green circle. Two problems: green is --fh-sev-low, a
     severity value borrowed to mean "live", and a 50% radius is the only circle
     in a world whose radius ceiling is 3px. A pulsing dot is also the most
     generic live indicator there is. This is the chart's own mark instead -- a
     gold diamond, which is already the symbology vocabulary -- and it holds
     still, because the feed's freshness is carried by the real dates on the
     cards beside it, not by something twitching. */
  .fh-ticker__dot {
    width: 7px;
    height: 7px;
    flex: 0 0 auto;
    background-color: var(--l-gold);
    rotate: 45deg;
  }

  .fh-ticker__wrap {
    position: relative;
    width: 100%;
    overflow: hidden;
    -webkit-mask-image: linear-gradient(to right, transparent, black 7%, black 93%, transparent);
    mask-image: linear-gradient(to right, transparent, black 7%, black 93%, transparent);
  }

  .fh-ticker__track {
    display: flex;
    gap: 0.75rem;
    width: max-content;
    padding-inline: 0.75rem;
  }

  @media (prefers-reduced-motion: no-preference) {
    .fh-ticker__track {
      animation: fh-ticker 58s linear infinite;
    }

    .fh-ticker__track:hover,
    .fh-ticker__track:focus-within {
      animation-play-state: paused;
    }
  }

  @keyframes fh-ticker {
    from { transform: translateX(0); }
    to { transform: translateX(-50%); }
  }

  /* Reduced motion: no mask, no duplicate run, just a still grid. */
  @media (prefers-reduced-motion: reduce) {
    .fh-ticker__wrap {
      -webkit-mask-image: none;
      mask-image: none;
    }

    .fh-ticker__track {
      flex-wrap: wrap;
      width: auto;
      padding-inline: 0;
    }

    .fh-ticker__card[data-fh-clone] { display: none; }
  }

  .fh-ticker__card {
    flex: 0 0 auto;
    width: 21rem;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    padding: 1.0625rem 1.25rem;
    background-color: var(--l-deep);
    border: var(--l-hairline) solid var(--l-neatline);
    border-radius: var(--l-radius-md);
  }

  .fh-ticker__card:hover {
    border-color: rgb(var(--l-gold-rgb) / 0.45);
    background-color: var(--l-shoal);
  }

  .fh-ticker__name {
    color: var(--l-ink);
    font-family: var(--l-face-data);
    font-size: var(--l-fs-data);
    line-height: 1.5;
    overflow-wrap: anywhere;
  }

  .fh-ticker__meta {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    color: var(--l-label);
    font-size: var(--l-fs-label);
    font-weight: var(--l-fw-semibold);
    letter-spacing: 0.06em;
  }

  .fh-ticker__meta {
    justify-content: space-between;
    margin-block-start: auto;
    padding-block-start: 0.25rem;
  }

  .fh-ticker__empty {
    padding: 1.25rem 0;
    color: var(--l-label);
    font-size: var(--l-fs-data);
  }
}

@layer patterns {
  /* ==========================================================================
     LOGO MARQUEE — the incumbent's integration row, restored: a scrolling run
     of logo cards, masked at both edges, paused on hover. Reuses the notices
     ticker's motion contract, so it stops and reflows into a still grid under
     prefers-reduced-motion rather than running regardless.
     ======================================================================== */
  /* Full-bleed by position in the markup, not by viewport units: 100vw counts
     the scrollbar while percentage widths do not, so it overflowed by the
     scrollbar's width in any browser with classic scrollbars. */
  .fh-logos {
    position: relative;
    width: 100%;
    overflow: hidden;
    -webkit-mask-image: linear-gradient(to right, transparent, black 8%, black 92%, transparent);
    mask-image: linear-gradient(to right, transparent, black 8%, black 92%, transparent);
  }

  .fh-logos__track {
    display: flex;
    align-items: stretch;
    gap: 0.75rem;
    width: max-content;
    padding-inline: 0.75rem;
  }

  @media (prefers-reduced-motion: no-preference) {
    .fh-logos:not(.fh-logos--static) .fh-logos__track {
      animation: fh-ticker 46s linear infinite;
    }

    /* FOCUS PAUSES, HOVER DOES NOT -- deliberately NOT the ticker's contract.

       The ticker pauses on hover because its cards carry readable text and
       dates: stopping the run is how you finish reading one. This band carries
       logos. Measured on `/`: full-bleed 1440x68px, ZERO links and ZERO
       focusable elements inside it, so there is nothing to read and nothing to
       click -- and because it spans the full width, moving the pointer down the
       page across it freezes the run and holds it frozen while the cursor
       rests there. Reported as the animation being broken, which is a fair
       reading of what it looks like.

       `:focus-within` stays. It fires on nothing today, and that is the point:
       if a logo ever becomes a link, keyboard users get the pause back without
       anyone having to remember this rule.

       WCAG 2.2.2 still has a mechanism here -- `prefers-reduced-motion`
       already drops the run entirely (below), which is a user-agent-level
       control rather than one that depends on holding a pointer still. */
    .fh-logos:not(.fh-logos--static) .fh-logos__track:focus-within {
      animation-play-state: paused;
    }
  }

  /* A STILL logo wall, by owner call 2026-08-27 on /products/oem-apis/.

     A run of four partner marks is a short, closed list, and scrolling a closed
     list past the reader costs them the ability to read it: the first card was
     clipped by the edge mask at every moment, so the wall never showed all four
     at once. The ticker contract earns its motion where the run is long and
     genuinely continuous; here it was motion applied to a set that fits.

     This is NOT a new presentation. It is the one the reduced-motion branch
     below already defines, promoted to the default for this instance -- so the
     still form is a contract the component already had to honour rather than a
     second layout to keep in sync. Same reason the clones hide: they exist only
     to make a loop seamless, and there is no loop. */
  .fh-logos--static {
    -webkit-mask-image: none;
    mask-image: none;
  }

  /* Left-ruled, not centred. The reduced-motion branch centres, which is right
     for a fallback nobody composes against -- but as this instance's DEFAULT it
     is a layout decision, and a centred row under a left-aligned heading puts
     the logos off the spine every other element on the sheet starts from.
     Adopting .fh-shell's own measure and gutter lands the first card exactly on
     the heading's left edge, so the wall reads as part of the plate rather than
     as a floating strip. The Search Entry Exception is the only surface in this
     world that centres, and it is scoped to its own page for this reason. */
  .fh-logos--static .fh-logos__track {
    flex-wrap: wrap;
    justify-content: flex-start;
    width: 100%;
    max-width: var(--l-container);
    margin-inline: auto;
    padding-inline: var(--l-gutter);
  }

  .fh-logos--static .fh-logos__card[data-fh-clone] {
    display: none;
  }

  @media (prefers-reduced-motion: reduce) {
    .fh-logos {
      -webkit-mask-image: none;
      mask-image: none;
    }

    .fh-logos__track {
      flex-wrap: wrap;
      justify-content: center;
      width: auto;
      padding-inline: 0;
    }

    .fh-logos__card[data-fh-clone] {
      display: none;
    }
  }

  .fh-logos__card {
    display: flex;
    flex: 0 0 auto;
    align-items: center;
    justify-content: center;
    gap: 0.75rem;
    min-width: 11.5rem;
    padding: 1.125rem 1.5rem;
    background-color: var(--l-deep);
    border: var(--l-hairline) solid var(--l-neatline);
    border-radius: var(--l-radius-card);
    color: var(--l-ink);
    font-size: 1.0625rem;
    white-space: nowrap;
    transition: border-color var(--l-dur-fast) var(--l-ease);
  }

  .fh-logos__card:hover {
    border-color: rgb(var(--l-gold-rgb) / 0.45);
  }

  .fh-logos__card img {
    max-height: 26px;
    max-width: 132px;
    width: auto;
    flex: 0 0 auto;
  }

  /* A wordmark logo carries its own name; only icon-only marks get the label. */
  .fh-logos__card--mark img {
    max-height: 30px;
    max-width: 148px;
  }

  .fh-logos__note {
    margin: 0;
    color: var(--l-ink);
    font-weight: var(--l-fw-semibold);
  }
}

@layer patterns {
  /* ==========================================================================
     INSTRUMENT — a legal instrument printed as one continuous sheet: the
     privacy policy, the terms and conditions, the GDPR statement.

     Why a pattern rather than twenty .fh-panel boxes. The incumbent set all
     three of these pages as ONE bordered card whose `card-header`/`card-body`
     pairs ruled off each clause, and that reading is correct: a policy is one
     document, not twenty tiles. Boxing every clause would print nineteen
     nested borders on terms_and_conditions alone, which is the card-everything
     habit this chart exists to stop. So the divider here is the device the
     plate already uses at section scale — a hairline, and nothing else.

     Measure comes from .fh-shell--narrow. Ink is --l-sounding on --l-ground
     (14.2:1) and the base gold link is 11.4:1 on the same ground, both clear
     of the house label floor, so clause text and its references need no colour
     of their own.
     ======================================================================== */
  /* Any clause that follows something — the preamble as much as another clause
     — is ruled off from it. Scoped to `+ .fh-instrument__clause` rather than
     `.fh-instrument__clause + .fh-instrument__clause` because the first clause
     follows the document's unheaded preamble, and that join needs the rule
     just as much as the ones between clauses. */
  .fh-instrument > * + .fh-instrument__clause {
    margin-block-start: var(--l-space-8);
    padding-block-start: var(--l-space-8);
    border-block-start: var(--l-hairline) solid var(--l-neatline);
  }

  /* Clause headings hold --l-fs-d3, the size the incumbent's `h4` already set.
     They are `h2` elements because the document's only `h1` is its title and a
     legal page may not skip a level: axe's `heading-order` was one of the four
     real violations these three pages carried before the migration. */
  .fh-instrument__title {
    color: var(--l-ink);
    font-size: var(--l-fs-d3);
    font-weight: var(--l-fw-bold);
    letter-spacing: -0.01em;
  }

  /* The issuing party's mark, printed at the foot of the instrument the way a
     chart's title block carries the hydrographic office's imprint. It is also
     the second wordmark the old shell printed in its footer and the new shell
     does not, so the graphic count does not fall when a page moves. */
  .fh-instrument__imprint {
    margin-block-start: var(--l-space-12);
    padding-block-start: var(--l-space-8);
    border-block-start: var(--l-hairline) solid var(--l-neatline);
  }

  .fh-instrument__imprint img {
    width: 132px;
    height: auto;
    opacity: 0.72;
  }

  /* ==========================================================================
     PROSE — running text: paragraphs, ordered and unordered lists, inline
     references. The reset zeroes every block margin, which is right for
     composed layout and wrong for a document that is nothing but paragraphs
     and lists, so the rhythm is restored here for that one case — at body size
     rather than the panel's --l-fs-small, because a reader has to get through
     four hundred lines of it and 14px is a caption size.
     ======================================================================== */
  .fh-prose {
    color: var(--l-sounding);
    line-height: var(--l-lh-relaxed);
  }

  .fh-prose > * + * {
    margin-block-start: 1rem;
  }

  /* An unclassed list is a document list and keeps its markers. The reset
     already strips markers and padding from any list carrying a class
     (`ul[class]`), so a composed list opts out simply by being classed. */
  .fh-prose ul:not([class]),
  .fh-prose ol:not([class]) {
    padding-inline-start: 1.5rem;
  }

  .fh-prose li + li {
    margin-block-start: 0.5rem;
  }

  /* A list that is a block of lines rather than an enumeration — an address, a
     set of contact details. The reset already strips markers from any list
     carrying a class; this states the intent so it is not a side-effect. */
  .fh-prose__lines {
    list-style: none;
    padding-inline-start: 0;
  }
}

@layer patterns {
  /* ==========================================================================
     AUTH SHEET — the password-reset pair. One narrow column printed directly
     on the plate: the wordmark, one heading, one or two controls, one action.

     Deliberately NOT inside a .fh-panel. `.fh-control` fills itself with
     --l-deep so it reads as cut into the sheet's --l-ground; drop it inside a
     panel, which is also --l-deep, and the field loses its fill and survives
     only as an outline. Printing on the ground also lifts the shared field
     label from 9.9:1 to 10.7:1, which is the side of the house floor it
     belongs on — and a floating card is the glass this chart does not use.

     Every control comes from the shared form contract (.fh-form / .fh-fields /
     .fh-control, via the macros in components/chart/form.html). Nothing about
     a field is restated here, because restating it is how two pages drift.
     ======================================================================== */
  .fh-auth {
    max-width: 26rem;
    margin-inline: auto;
  }

  /* The sheet's heading sits at --l-fs-d3 like a panel title: this is a form,
     not a page's argument, so the h1 does not take the cartouche scale. */
  .fh-auth__title {
    font-size: var(--l-fs-d3);
    font-weight: var(--l-fw-bold);
    letter-spacing: -0.01em;
  }

  /* .fh-btn is `white-space: nowrap` by contract, which is right for a button
     sitting in a row of buttons. This one is the sheet's only action and its
     label runs to twenty-four uppercase characters: on a 320px sheet, inside
     the shell's gutter, nowrap pushes the plate into horizontal overflow and
     the 320-1600px sweep fails. A full-width submit is allowed to wrap. */
  .fh-auth__submit {
    width: 100%;
    white-space: normal;
  }
}

@layer components {
  /* The pointer edge-light below is `position: absolute; inset: 0`, so each card
     must be its own containing block. Without this, the ::after escapes to the
     nearest positioned ancestor -- `.fh-plate` -- and the ring paints around the
     whole plate instead of the hovered card. `.fh-station`, `.fh-source__cell`
     and `.fh-plate` were already positioned; these six were not.
     Found by the WAVE B worker, which routed around it by preferring
     `.fh-station`; the defect was authored with the edge-light itself. */
  .fh-panel,
  .fh-block,
  .fh-sheet,
  .fh-figure,
  .fh-edition,
  .fh-section {
    position: relative;
  }
}
