/* apkproof — the single public stylesheet (DESIGN.md §A10).
 *
 * Guided Brief + Scopeframe is the current exterior language (agreed 2026-09-01,
 * superseding the Field Guide exterior, which is live history). Evidence Record stays
 * the interior information logic of a detail card. The Scopeframe document (kicker rule →
 * document H1 → lede → ruled sections) is BUILT and live for page families 1–4 — Methodology,
 * Privacy, About and Contact (2026-09-03), and the two report forms (2026-09-04, where the
 * form comes first in the body). The Sprint 4A–4C folio is deleted. The receipt (family 5),
 * the 404 (family 6) and Home (family 7) all landed 2026-09-04, each as its own composition,
 * and the kind index (family 8) and the term page (family 9) on 2026-09-05. All nine page
 * families now have a live composition (DESIGN.md §A6.1).
 * This file is the migration of the two former inline <style> blocks
 * (base.html + app_detail.html) onto the semantic palette, self-hosted
 * typography, and the shared spacing/width/breakpoint scales. The existing
 * class tree (.prev, .sec, .dl, .badge, …) is intentionally kept —
 * only the tokens it draws from and the rules the semantic restrictions
 * require were changed. Exact live values are recorded in DESIGN.md Part B.
 */

/* ---- Self-hosted typography (DESIGN.md §A2/§B2) --------------------------
 * Only the three weights actually used ship. Latin + Cyrillic confirmed in every file;
 * provenance/checksums in fonts/SOURCE.md. No IBM Plex Mono — exact data uses the system
 * monospace stack.
 *
 * LOADING: `font-display:optional`, plus preload (DESIGN.md §A2/§B2, 2026-09-03).
 *
 * `swap` was tried and REVERTED the same day. `swap` lets a face arrive after first paint
 * and re-lay the text, so it is only safe where every fallback in the chain is metric-
 * matched. That could be measured for macOS and (through the Arial rule) Windows, but NOT
 * for Android: see the block below. `optional` needs no such proof — the face either wins
 * the short block period or is not used on that load at all, so nothing reflows late on any
 * platform. The cost is honest and accepted: a cold visitor may read the page in a fallback.
 *
 * Preload is kept and is what makes `optional` worth having: it is the difference between
 * usually missing that block period on a cold visit and usually making it. The two sans
 * Both faces are preloaded on every page — the whole set is two files, and every page draws
 * both of them.
 *
 * The fallback faces below are NOT a new download: each is `local()` only, over a family
 * the platform already has, wrapped in its own @font-face so its own `size-adjust` applies.
 * Under `optional` they do not prevent a mid-load swap (nothing can swap); they make the
 * fallback load and the Plex load LOOK like the same page — same wrapping, same heights -
 * so the design is not judged on a different geometry.
 *
 * Distinct family names (rather than several rules sharing one name) make the cascade
 * deterministic: an unavailable `local()` simply fails to resolve and `font-family` moves
 * to the next entry, instead of relying on which duplicate declaration wins.
 *
 * A 600 rule must name the BOLD FACE, not the family. `local()` matches a full font or
 * PostScript name, so `local("Helvetica Neue")` inside a `font-weight:600` rule would bind
 * the REGULAR face and then suppress synthetic bold, rendering measurably narrower than Plex
 * SemiBold. The 600 rules name `Helvetica Neue Bold` / `Arial Bold`; the two 400 rules
 * correctly name the family.
 */
@font-face{
  font-family:"IBM Plex Sans";font-style:normal;font-weight:400;font-display:optional;
  src:url("fonts/IBMPlexSans-Regular.5aaadb685d3c.woff2") format("woff2");
}
@font-face{
  font-family:"IBM Plex Sans";font-style:normal;font-weight:600;font-display:optional;
  src:url("fonts/IBMPlexSans-SemiBold.51d76d33ef85.woff2") format("woff2");
}

/* Metric-compatible fallbacks — every number MEASURED against these exact shipped files.
 *
 * Derived from what CSS actually uses: the `line-height:normal` box and the unwrapped
 * advance width of a Latin+Cyrillic sample at 100px. (Canvas `fontBoundingBox` does NOT
 * drive line layout — deriving from it produced a fallback 10px shorter than Plex, i.e.
 * worse than no overrides at all.) Plex's used line box is 130% of the em in all three
 * faces; `size-adjust` equalises advance width, and the ascent/descent overrides — which
 * apply to the ALREADY adjusted em — restore that 130% in Plex's own 102:27 proportion.
 *
 * The sans needs separate values per weight: 400 and 600 have different advance widths.
 *
 * ANDROID IS BLOCKED, and this is why there is no Roboto rule here. The metrics are known:
 * Roboto was measured from the pinned google/fonts file (commit 1c627bfa375fc51cf86fabeca4
 * f6e08a95f0aa5c, ofl/roboto/Roboto[wdth,wght].ttf, 488584 bytes, sha256 d7598e12c5dbef095f
 * f8272cfc55da0250bd07fbdecbac8a530b9b277872a134; measurement only — the file is NOT in this
 * repository and NOT served). Against the shipped Plex faces it would need size-adjust
 * 101.2% / ascent 101.58% / descent 26.89% at 400, and 102.75% / 100.04% / 26.48% at 600.
 *
 * Those numbers are not shippable, because three links in the chain cannot be verified from
 * a macOS machine and a user-agent string does not change which fonts a device has installed:
 * (1) WHICH Roboto — Android 12+ ships a variable Roboto with a real 600 instance, earlier
 * releases ship static Regular/Medium/Bold and would synthesise 600 from Regular, which is
 * not the geometry measured above; (2) WHETHER `local("Roboto")` resolves at all — Samsung,
 * Xiaomi and others replace the system sans, and that is the same population `system-ui`
 * currently serves; (3) WHICH NAME resolves, since `local()` matching against installed
 * families is platform-specific. Declaring a rule on unverified name resolution would be
 * inventing a face name, so the rule is not declared and `optional` is what keeps Android
 * safe meanwhile. Revisit with a real device or a device lab, not with emulation.
 *
 * Windows is covered through the Arial rule, measured on this machine like the rest.
 */
@font-face{
  font-family:"Plex Sans Fallback";font-weight:400;src:local("Helvetica Neue");
  size-adjust:100.1%;ascent-override:102.69%;descent-override:27.18%;line-gap-override:0%;
}
@font-face{
  font-family:"Plex Sans Fallback 2";font-weight:400;src:local("Arial");
  size-adjust:102.29%;ascent-override:100.49%;descent-override:26.6%;line-gap-override:0%;
}
@font-face{
  font-family:"Plex Sans Fallback";font-weight:600;src:local("Helvetica Neue Bold");
  size-adjust:98.64%;ascent-override:104.21%;descent-override:27.59%;line-gap-override:0%;
}
@font-face{
  font-family:"Plex Sans Fallback 2";font-weight:600;src:local("Arial Bold");
  size-adjust:99.35%;ascent-override:103.46%;descent-override:27.39%;line-gap-override:0%;
}

/* ---- Tokens (DESIGN.md §B1) ---------------------------------------------
 * :root is the LIGHT palette. With no explicit data-theme, the system
 * preference picks dark via the media query below; [data-theme="light|dark"]
 * overrides the system. color-scheme follows so form controls/scrollbars match.
 */
:root{
  /* Scopeframe semantic palette — light (DESIGN.md §A0/§A1, PR 1; measured ratios in
     Part B §5). Base tokens (text/muted/page/sheet/soft/border/link/coral) and the six
     role pairs (action/app/game/datalink/limitation/danger) are the approved mapping.
     The wordmark bracket accent is --link (header) / --footer-brand (footer, always a
     dark block) — coral is a reserved approved base accent, not yet consumed by any
     component; do not repaint the wordmark or any role pair with it speculatively. */
  --page:#F4F6FA; --sheet:#FFFFFF; --soft:#E9EEF6;
  --text:#17202C; --muted:#596578; --border:#CAD3E0; --link:#2759D7; --coral:#BD513D;
  /* PR 2C select-arrow chevron, stroke hardcoded to match --muted: `currentColor`
     inside a `background-image: url(data:image/svg+xml,...)` does NOT reliably resolve
     against the element's computed `color` (verified — it painted solid black in both
     themes), so this is a real per-theme token like every other pair above, not a
     currentColor trick. */
  --select-arrow:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23596578' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6l4 4 4-4'/%3E%3C/svg%3E");
  /* A required control boundary (theme button, language/menu summary, locale links,
     form input/select/textarea) reads --control-border, not --border: --border alone
     is only 1.40:1 on --page (below the 3:1 non-text bar — Part B §5) and is reserved
     for decorative section hairlines that never carry meaning on their own. */
  --control-border:#748196;
  --action-bg:#2759D7; --action-fg:#FFFFFF;
  --datalink-bg:#E8E6F5; --datalink-fg:#594B8F;
  --limitation-bg:#E9EEF6; --limitation-fg:#596578;
  --danger-bg:#F7E1E5; --danger-fg:#923247;
  /* Footer is always a dark block regardless of theme (matches the approved
     reference); its ink/muted reuse the dark palette's already-verified pairs
     because the footer background stays dark in both themes. */
  --footer-bg:#17202C; --footer-ink:#F1F5FB; --footer-muted:#ABB6C8; --footer-brand:#87A9FF;
  /* Overlay elevation. Mixed from the ink in the light theme; in the dark one it stays
     BLACK rather than following the ink, because a light halo on a dark surface reads as
     the panel glowing, not lifting — the panel is already lighter than the page there. */
  --shadow-overlay:0 14px 36px color-mix(in srgb,#17202C 14%,transparent);

  /* legacy aliases — the existing class tree references these names; they now
     resolve to the semantic palette so no component rule had to be renamed. */
  --bg:var(--page); --surface:var(--sheet); --surface2:var(--soft);
  --line:var(--border); --mono:var(--text);
  --accent:var(--link); --accentbg:var(--datalink-bg);

  /* type families */
  /* Plex first, then the metric-matched fallbacks above (each carries its own
     size-adjust), then the generic stack as a last resort. */
  --font-sans:"IBM Plex Sans","Plex Sans Fallback","Plex Sans Fallback 2",system-ui,sans-serif;
  --font-mono:ui-monospace,"SF Mono",Menlo,Consolas,monospace;

  /* ---- Type roles (DESIGN.md §A2.1) --------------------------------------------
   * One `font` shorthand per RECURRING role, so a role is applied by name and no size
   * is invented at a call site. A role with a single consumer stays inline rather than
   * becoming a token nobody reuses (§A10 rule of two). Only 400 and 600 ship.
   * The four roles that switch at the content breakpoint (document H1, page H1, lede,
   * 404 rail) are redefined once, together, in the `max-width:600px` block below —
   * that breakpoint is CONTENT typography and is deliberately not the 800px shell one.
   */
  --t-h1-page:600 30px/36px var(--font-sans);   /* h1, .detail-title */
  --t-h2-interface:600 22px/28px var(--font-sans); /* interface section H2 */
  --t-h3:600 16px/24px var(--font-sans);        /* h3, card title, legend */
  --t-body:400 16px/24px var(--font-sans);      /* UI/body copy, input value */
  --t-meta:400 14px/20px var(--font-sans);      /* secondary / meta / facts */
  --t-strong:600 14px/20px var(--font-sans);    /* scope+status, field label, button/nav */
  --t-kicker:600 12px/18px var(--font-sans);    /* eyebrow (uppercase set per rule) */
  --t-toc:400 13px/18px var(--font-sans);       /* TOC / page-index link */
  /* Document roles. Shared by every consumer of the Scopeframe composition —
     families 1–4 — so they are one token each rather than literal declarations that can
     drift apart. They were promoted from inline values when a second consumer appeared;
     the closed use-site sets are pinned in tests/test_visual_foundation.py. The two
     adaptive ones are redefined in the content-breakpoint block at the end of this file. */
  --t-h1-document:600 34px/40px var(--font-sans);  /* document/task H1 */
  --t-h2-document:600 22px/28px var(--font-sans);  /* document section H2 */
  --t-lede:400 17px/27px var(--font-sans);      /* document lede */
  --t-prose:400 16px/26px var(--font-sans);     /* document prose, looser than --t-body */
  --t-data:400 14px/20px var(--font-mono);      /* exact data */
  --t-hash:400 14px/24px var(--font-mono);      /* long hash / command */
  --t-foot:400 13px/20px var(--font-sans);      /* footer link, shell utility text */

  /* spacing scale (px): 4 8 12 16 24 32 48 64 */
  --s1:4px; --s2:8px; --s3:12px; --s4:16px; --s5:24px; --s6:32px; --s7:48px; --s8:64px;

  /* width & measure */
  --page-max:1120px; --measure:70ch;

  /* radii — small and flat */
  --radius:12px; --radius-sm:8px; --radius-xs:6px;

  color-scheme:light;
}

/* dark palette values, shared by the system-preference path and the explicit
   opt-in. Two copies because plain CSS cannot include a token set. */
@media (prefers-color-scheme:dark){
  :root:not([data-theme]){
    --page:#0F141D; --sheet:#171E29; --soft:#202A38;
    --text:#F1F5FB; --muted:#ABB6C8; --border:#344154; --link:#87A9FF; --coral:#FF9B83;
    --control-border:#697A91;
    --select-arrow:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23ABB6C8' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6l4 4 4-4'/%3E%3C/svg%3E");
    --action-bg:#87A9FF; --action-fg:#09142D;
    --datalink-bg:#2C2843; --datalink-fg:#C5BAF3;
    --limitation-bg:#202A38; --limitation-fg:#ABB6C8;
    --danger-bg:#481F2B; --danger-fg:#F5A0B2;
    --footer-bg:#0A0E14; --footer-ink:#F1F5FB; --footer-muted:#ABB6C8; --footer-brand:#87A9FF;
    --shadow-overlay:0 14px 36px rgb(0 0 0 / .44);
    color-scheme:dark;
  }
}
[data-theme="dark"]{
  --page:#0F141D; --sheet:#171E29; --soft:#202A38;
  --text:#F1F5FB; --muted:#ABB6C8; --border:#344154; --link:#87A9FF; --coral:#FF9B83;
  --control-border:#697A91;
  --select-arrow:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23ABB6C8' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6l4 4 4-4'/%3E%3C/svg%3E");
  --action-bg:#87A9FF; --action-fg:#09142D;
  --datalink-bg:#2C2843; --datalink-fg:#C5BAF3;
  --limitation-bg:#202A38; --limitation-fg:#ABB6C8;
  --danger-bg:#481F2B; --danger-fg:#F5A0B2;
  --footer-bg:#0A0E14; --footer-ink:#F1F5FB; --footer-muted:#ABB6C8; --footer-brand:#87A9FF;
  --shadow-overlay:0 14px 36px rgb(0 0 0 / .44);
  color-scheme:dark;
}
[data-theme="light"]{ color-scheme:light; }

/* ---- Base ---------------------------------------------------------------- */
*{box-sizing:border-box}
[hidden]{display:none!important}
body{margin:0;background:var(--bg);color:var(--text);
  font:var(--t-body);
  -webkit-font-smoothing:antialiased}
.mono{font-family:var(--font-mono)}
a{color:var(--link)}
/* Visually hidden but exposed to assistive tech — the kind marker's optional
   translated "App"/"Game" label rides on this (DESIGN.md §A6/§A8). */
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
  clip:rect(0 0 0 0);white-space:nowrap;border:0}

/* Headings: one sans family across the site, weight and size carry the hierarchy. */
h1{margin:0 0 3px;font:var(--t-h1-page)}
h2,h3{font-weight:600}
h3{font:var(--t-h3)}
/* `.lede` is a legacy SHARED CLASS, not a role. Its shared part is presentation only —
   muted colour, rhythm, measure — and its type role depends on where it sits:
     · UI surfaces (home, kind index, term, filters, receipt, 404) → UI/body 16/24, here;
     · `.scopeframe__masthead` → Document lede 17/27 (→ 16/25 at the content breakpoint);
     · `.scopeframe__body`     → Document prose 16/26.
   The last two are scoped with the Scopeframe shell further down. Treating every `.lede` as
   the Document lede put 17/27 on Home's intro and on the receipt and 404, which are not
   documents at all (§A2.1). */
.lede{font:var(--t-body);color:var(--muted);margin:0 0 18px;max-width:var(--measure)}

/* ---- Layout & skip link -------------------------------------------------- */
/* The TRUE content width (DESIGN.md §A3): `width:min(--page-max, 100% - gutters)`, not
   `max-width` plus padding. A border-box container with max-width:1120px and 24px of
   padding-inline measures 1120px but CONTAINS 1072px, so the approved column was 48px
   narrow on every surface. This form puts the gutters outside the column, so the content
   really is 1120px at 1440px and still keeps a safe gutter when the viewport is smaller.
   One shared rule for the three bands — no second nested max-width anywhere. */
main,.header-inner,.foot-inner{width:min(var(--page-max),calc(100% - var(--s5) * 2));
  margin-inline:auto}
/* Narrower gutters on a phone: 24px each side costs a tenth of a 320px screen, so the
   approved shell drops to 16px there — still a real gutter, never edge-to-edge. */
@media (max-width:520px){
  main,.header-inner,.foot-inner{width:calc(100% - var(--s4) * 2)}
}
/* `main` IS the content column: it takes the width rule directly, with no wrapper element
   and no class standing in for one. The breadcrumb trail is main's own first block, so it
   inherits both the column and this top padding instead of carrying either itself. */
main{padding-block:var(--s6) var(--s8)}
.skip-link{position:absolute;left:var(--s2);top:-48px;z-index:10;
  background:var(--sheet);color:var(--link);border:1px solid var(--border);
  border-radius:var(--radius-sm);padding:10px 14px;text-decoration:none}
.skip-link:focus{top:var(--s2)}
main:focus{outline:none}

/* Visible keyboard focus everywhere, including native disclosures (summary) and any
   future select control (DESIGN.md §A8 focus contract).

   Flush against the element and with no radius of its own: an offset ring drew a second
   shape a step away from the control instead of marking the control, and a forced 2px
   radius did not follow the control's own corners. This is the ring for things that have
   no border of their own to mark — a link, a summary, anything given a tabindex. */
a:focus-visible,button:focus-visible,input:focus-visible,textarea:focus-visible,
select:focus-visible,summary:focus-visible,
[tabindex]:focus-visible{outline:2px solid var(--link)}

/* A control that HAS a border is marked by that border turning, not by a ring added
   outside it. An outline is painted beyond the border edge, so even flush it leaves the
   grey border showing underneath: 1px grey and 2px blue, three pixels of line in two
   colours. Here the border itself goes to the link colour and the outline drops to 1px on
   top of it — one crisp 2px blue edge, exactly where the grey one was, and nothing else
   moves. */
.field :is(input,textarea,select):focus-visible,
.btn:not(.btn--primary):focus-visible{border-color:var(--link);
  outline:1px solid var(--link)}
/* The one control the flush ring cannot mark: a primary button is filled with the link
   colour and its border is transparent, so a blue ring against it is a blue ring against
   blue. Its ring stands off onto the page, which is the only light ground it has. */
.btn--primary:focus-visible{outline-offset:2px}

/* ---- Chrome: header, nav, breadcrumbs, footer ---------------------------- */
/* .header-inner / .foot-inner share the true-width rule above: the band itself spans
   the viewport, the inner column is the page measure (DESIGN.md §A6). */
/* Full-bleed paper-colour header band with a bottom divider — never a coloured
   card floating inside the page's own gutters. */
.site-header{background:var(--sheet);border-bottom:1px solid var(--border)}
/* One row at >=801px: brand, the desktop Sections nav copy, then language + theme,
   left to right — plain flex flow in DOM order (brand -> .nav--desktop -> .toggles),
   no CSS `order`. 68px is the approved desktop band height (measured band 69.0px
   with the header's own 1px rule).

   At <=800px the top row is 62px and the mobile nav moves OUT of it into `.nav-band`
   below — the approved reference is two separate strips, 62 + 51, not one flex row
   that wraps. Wrapping it inside .topbar inherited the 68px min-height and produced a
   121px header where the reference measures 114. */
.topbar{display:flex;align-items:center;gap:var(--s3);min-height:68px;
  padding-block:var(--s3)}
/* The mobile nav strip: its own full-bleed band, so the divider spans the viewport
   like the header's own, with the same inner column as .topbar. Hidden on desktop,
   where the .nav--desktop copy inside .topbar is the visible one. */
.nav-band{display:none}
@media (max-width:800px){
  .topbar{min-height:62px;gap:var(--s2);padding-block:0}
  .nav-band{display:block;border-top:1px solid var(--border)}
}
/* Brand meets the 44×44 touch-target rule. `flex:0 0 auto` keeps the wordmark at its
   own width — a wide nav must take the free space, never squeeze the mark. */
.brand{display:inline-flex;flex:0 0 auto;align-items:center;min-height:44px;
  text-decoration:none}
/* ---- Wordmark (DESIGN.md §A6) — one shared component, reused identically in the
   header brand and the footer brand column. Visual "apk" gets a bracket-scope
   frame in the brand colour; "proof" stays plain text colour. Decorative only —
   aria-hidden; the accessible name lives on the enclosing .brand link. The
   footer is always a dark block, so its wordmark uses --footer-brand/--footer-ink
   instead of the theme-following --link/--text (site.css §"Footer chrome"). */
.wordmark{display:inline-flex;align-items:baseline;font:600 25px/25px var(--font-sans);
  letter-spacing:-.045em;color:var(--text)}
/* 22px on a phone — the approved reference's own mobile wordmark size. The footer
   wordmark keeps its own size at every width. */
/* The wordmark is the one type role that follows the SHELL breakpoint, not the 600px
   content one — it belongs to the header's own geometry (§A2.1). */
@media (max-width:800px){.topbar .wordmark{font:600 22px/22px var(--font-sans)}}
/* ---- Brand bracket (DESIGN.md §A6, §A10) ---------------------------------
 * The `[ ]` mark, drawn as two pseudo-element side rules with caps turning in at the top and
 * bottom. Promoted to a shared component on 2026-09-04, at its second consumer: the wordmark
 * had it inline, page family 5 needed the same mark at a different scale, and §A10's rule of
 * two says the second consumer promotes rather than forks. Two knobs and nothing else —
 * `--bracket-w` for the cap length and `currentColor` for the ink, so a consumer sets its own
 * colour the ordinary way and the header/footer wordmark keeps working unchanged.
 * Decorative by construction: empty `content`, no DOM node, nothing in the accessibility
 * tree. Borders rather than a background or shadow, so forced-colors repaints it as a shape.
 */
.bracket{position:relative}
.bracket::before,.bracket::after{content:"";position:absolute;inset-block:0;
  width:var(--bracket-w,5px);border-block:2px solid currentColor;pointer-events:none}
.bracket::before{left:0;border-left:2px solid currentColor}
.bracket::after{right:0;border-right:2px solid currentColor}

/* Geometry from the approved shell: a 6px bracket cap, 3px before "proof", and the
   asymmetric 5/4 vertical padding that sits the frame optically level on the baseline. */
.wordmark__apk{--bracket-w:6px;margin-right:3px;padding:5px 7px 4px;color:var(--link)}
.foot-brand .wordmark,.foot-brand .wordmark__proof{color:var(--footer-ink)}
.foot-brand .wordmark__apk{color:var(--footer-brand)}
/* Utilities are flush right at every width — on desktop after the nav, on mobile at
   the far end of the wordmark row. `margin-left:auto` is the whole mechanism; nothing
   is reordered (DESIGN.md §A6 primary nav). */
.toggles{display:flex;gap:var(--s2);align-items:center;margin-left:auto}
@media (max-width:800px){.toggles{gap:3px}}
/* Icon-only theme control: fixed 44×44 square, no visible text — the accessible
   name is the truthful localized aria-label site.js sets (DESIGN.md §A6 theme).
   Being a fixed square regardless of locale, it cannot itself change intrinsic
   width across EN/RU, which keeps the reveal-CLS contract simple. */
.theme-toggle{width:44px;height:44px;padding:0;display:inline-flex;align-items:center;
  justify-content:center;background:transparent;color:var(--muted);
  border:1px solid var(--control-border);border-radius:var(--radius-sm);cursor:pointer}
.theme-toggle:hover{color:var(--text)}
/* Both icons ship in the button and CSS picks one off the state site.js writes on the
   BUTTON. The icon shows the NEXT ACTION, not the current theme — moon while the light
   theme is in force ("switch to dark"), sun while the dark one is — so the picture and
   the aria-label agree, as they do in the approved reference. The other way round put a
   sun on a button labelled "Use light theme". Same 20×20 box either way inside the fixed
   44×44 square, so the swap cannot reflow. Until the attribute exists neither paints,
   which is the JS-off case: the button is hidden anyway. Not `hidden` on the SVGs —
   `el.hidden` is an HTMLElement property and assigning it to an <svg> sets a JS expando
   that never reaches the attribute a selector could match. */
.theme-toggle__icon{display:none}
.theme-toggle[data-theme-state="light"] [data-theme-icon="moon"],
.theme-toggle[data-theme-state="dark"] [data-theme-icon="sun"]{display:block}
/* Progressive-enhancement exception, NOT a general hidden pattern: the theme
   control ships native `hidden` and is revealed by JS. Reserve its exact final
   intrinsic (localized) geometry up front so the reveal cannot reflow the
   header — otherwise .topbar wraps at reveal and shifts already-painted layout.
   visibility:hidden keeps it out of the a11y tree and unfocusable until reveal. */
#theme-toggle[hidden]{display:inline-flex!important;visibility:hidden}
/* Native language disclosure — extensible to any number of locales; the summary
   shows the active one, the panel lists every real locale link (DESIGN.md §A6). */
.lang-menu{position:relative}
/* Transparent, not filled: the --control-border boundary is what makes it read as a
   control, and a fill would put a second surface on top of the header's own paper. */
.lang-menu summary{display:inline-flex;align-items:center;justify-content:center;gap:7px;
  min-height:44px;min-width:44px;padding:6px 10px;border-radius:var(--radius-sm);
  border:1px solid var(--control-border);background:transparent;color:var(--muted);
  cursor:pointer;font:var(--t-foot);list-style:none}
/* Wider on a phone, where "EN" plus the chevron would otherwise sit in a cramped square. */
@media (max-width:800px){.lang-menu summary{min-width:58px}}
.lang-menu summary::-webkit-details-marker{display:none}
.lang-menu[open] summary{color:var(--text);border-color:var(--link)}
/* Decorative dropdown affordance only — the native disclosure marker stays suppressed
   above; this is the shared inline-SVG chevron (_chevron.html), aria-hidden, rotated by
   the native open state so no JS or ARIA is involved (DESIGN.md §A6). */
.chevron{flex:0 0 auto;transition:transform 140ms ease}
details[open] > summary .chevron{transform:rotate(180deg)}
/* Overlay panel, shared geometry with the mobile Menu panel below: absolutely
   positioned so opening it never changes the header's height or moves main. */
/* Both header panels lift off the page: they overlay content rather than displacing it,
   and the border alone does not say so. */
.lang-menu__panel,.nav-menu__panel{box-shadow:var(--shadow-overlay)}
/* --border, not --control-border: the 3:1 non-text bar applies to a boundary that
   IDENTIFIES a control, and that job belongs to the trigger. A panel is a container —
   its own surface plus --shadow-overlay separate it from the page, so the heavier
   control ink only made the edge read as a hard outline. */
.lang-menu__panel{position:absolute;right:0;top:calc(100% + 6px);z-index:20;
  display:flex;flex-direction:column;gap:2px;min-width:176px;padding:6px;
  background:var(--sheet);border:1px solid var(--border);border-radius:var(--radius-sm)}
/* One row per real locale: the language named in itself, then its short code — the
   summary shows only the code, so the panel is where that code is explained. Rows are
   plain (no chip border): the panel already frames them, and a bordered chip inside a
   bordered panel reads as a control inside a control. */
.lang{display:flex;align-items:center;justify-content:space-between;gap:var(--s4);
  text-decoration:none;font:var(--t-foot);padding:9px 10px;border-radius:var(--radius-sm);
  min-height:44px;color:var(--muted)}
.lang:hover{color:var(--text)}
.lang[aria-current]{color:var(--text);font-weight:600;background:var(--surface2)}
.lang__code{color:var(--muted)}
.lang[aria-current] .lang__code{color:var(--text)}

/* Flat text nav (DESIGN.md §A6/§A11 — the 999px pill is a rejected decorative
   target, not reused here). aria-current — "page" on the exact index/route,
   "location" on a page that lives inside that section without being it (an
   app/game detail, a typed category/tag page) — reads identically: text
   weight + a 3px brand underline, never colour alone (the approved reference
   pattern covers both states the same way). */
.nav{gap:var(--s5);flex-wrap:wrap;align-items:center;padding:0;list-style:none}
.nav a{display:inline-flex;position:relative;align-items:center;justify-content:center;gap:6px;
  font:var(--t-meta);min-height:44px;min-width:44px;color:var(--muted);text-decoration:none}
.nav a:hover{color:var(--text)}
.nav a[aria-current]{color:var(--text);font-weight:600}
.nav a[aria-current]::after{content:"";position:absolute;left:0;right:0;bottom:0;
  height:3px;background:var(--link)}
/* Two responsive DOM copies of the Sections nav, not one reflowed via CSS
   `order` — DESIGN.md §A6 primary nav. DOM order is brand -> nav--desktop ->
   toggles -> nav--mobile (base.html); at any width exactly one copy is
   display:flex and the other display:none, so visual, DOM, tab and AX order
   are the identical sequence at every breakpoint, and only one "Sections" nav
   landmark is ever exposed to assistive tech. Mobile (default): .nav--mobile
   is the visible copy, full-width so it wraps to its own row under
   brand+toggles. Desktop (>=801px, the approved freeze brief's shared header
   collapse point): .nav--desktop takes over, inline in the single topbar row.
   No ARIA application menu, no nested nav, no pill radius on nav links —
   controls (not nav links) may keep the small flat --radius-sm. */
/* `align-self:stretch` makes each link as tall as the 68px band, so the 3px current-page
   rule below sits on the band's own bottom edge instead of floating at the bottom of a
   44px link box. `margin-left` is the approved gap between the brand and the first item. */
.nav--desktop{display:none;align-self:stretch;margin-left:34px}
/* The mobile copy fills its own band and is 50px tall — the approved nav strip
   (measured 51px with the band's 1px divider above it). It no longer needs
   flex-basis:100%: it is not a wrapped .topbar item any more, it owns .nav-band. */
.nav--mobile{display:flex;align-items:stretch;min-height:50px;gap:var(--s5)}
.nav--mobile a{min-height:50px}
/* One pixel lower than on desktop, so the rule covers the band's own 1px divider and
   reads as sitting on the strip's edge rather than just above it. */
.nav--mobile a[aria-current]::after{bottom:-1px}
@media (min-width:801px){
  .nav--desktop{display:flex;flex:1 1 auto}
  .nav--mobile{display:none}
}
/* Menu sits at the far right of the nav strip; Apps/Games stay left. Its summary is a
   BORDERLESS flat action inside the 50px strip — the approved reference's own treatment,
   the same flat-text language as the nav links beside it, not a bordered control floating
   in a nav row. Its 50px height clears the 44px target minimum on its own. The panel it
   opens keeps the --control-border boundary. */
.nav-menu{position:relative;margin-left:auto}
.nav-menu summary{display:inline-flex;align-items:center;gap:6px;min-height:50px;
  padding-inline:var(--s2) 0;border:0;background:none;color:var(--muted);cursor:pointer;
  font:var(--t-meta);list-style:none}
.nav-menu summary::-webkit-details-marker{display:none}
.nav-menu[open] summary{color:var(--text)}
/* An OVERLAY, not an in-flow block: the panel used to be `margin-top` inside the row,
   so opening Menu grew the header and pushed main down the page. Absolute + z-index
   keeps the header exactly as tall open as closed and leaves main where it was. It is
   still a plain native <details> — no ARIA menu roles, no focus trap, no JS. `right:0`
   plus the max-width keeps it inside a 320px viewport. */
.nav-menu__panel{position:absolute;right:0;top:calc(100% + 6px);z-index:20;
  display:flex;flex-direction:column;gap:2px;min-width:176px;max-width:min(280px,86vw);
  padding:6px;background:var(--sheet);border:1px solid var(--border);
  border-radius:var(--radius-sm)}
/* A panel row is not a strip link: it resets the centring and the 600 weight it would
   otherwise inherit from `.nav a` two levels up, and matches the language panel's rows —
   both header panels are the same component in two places. Only the current row is bold. */
.nav-menu__panel a{display:flex;align-items:center;justify-content:space-between;
  gap:var(--s4);min-height:44px;padding:9px 10px;border-radius:var(--radius-sm);
  font:var(--t-foot);color:var(--muted);text-decoration:none}
.nav-menu__panel a:hover{color:var(--text)}
.nav-menu__panel a[aria-current]{color:var(--text);font-weight:600;background:var(--surface2)}

.crumbs{font:400 13px/20px var(--font-sans);color:var(--muted);margin-bottom:14px;
  overflow-wrap:anywhere}
/* Honest flow layout, not an inline padding trick: the list wraps and can carry long
   RU/current text without overlapping neighbours (DESIGN.md §A8). The current (non-link)
   crumb is not a target and stays natural. Never truncated, never ellipsised: the trail
   wraps onto more lines instead. */
.crumbs ol{list-style:none;margin:0;padding:0;display:flex;flex-wrap:wrap;align-items:center}
.crumbs li{display:inline-flex;align-items:center;min-width:0}
/* A crumb link draws the link colour and is underlined — colour is never the only thing
   that separates it from the muted current item (§A8).

   Height carries the touch target; width does NOT. A min-width of 44px used to widen the
   box past its label, and since the box is left-aligned the leftover landed between the
   text and the separator — a gap that grew as the label got shorter ("Home" 9px against
   "Games" 3px), so the trail read as unevenly spaced. The approved page spaces its crumbs
   with one flat gap and no min-width. Centring the label inside a 44px box would only
   halve the variance, not remove it. */
.crumbs a{display:inline-flex;align-items:center;min-height:44px;
  color:var(--link);text-decoration:underline}
.crumbs a:hover{color:var(--text)}
.crumbs__current{color:var(--muted);min-width:0}
/* The `/` separator: decorative, aria-hidden in the markup, and never a target. */
.crumbs__sep{margin:0 var(--s2);color:var(--border);user-select:none}

/* Footer chrome — always a dark block regardless of theme (the approved reference's
   fixed footer-bg), so every footer text colour reads --footer-ink/--footer-muted
   rather than the theme-following --text/--muted (DESIGN.md §A6/§A1, PR 1; measured
   ratios in Part B §5: footer-ink ≥15:1, footer-muted ≥8:1 on both footer-bg values). */
/* No margin-top: main already ends in var(--s8) of bottom padding before this
   full-bleed sibling band, so the gap above main content isn't doubled. */
footer{background:var(--footer-bg)}
/* The always-dark footer background fails AA against the light theme's --link
   (2.73:1, measured) — the generic focus ring above must not draw it here.
   --footer-brand is verified ≥7:1 on both footer-bg values (Part B §5). */
footer :is(a,button,summary,select):focus-visible{outline-color:var(--footer-brand)}
/* Grouped, extensible footer. The wrapper owns the single top border and the outer
   margin, so adding a group never triples them. A brand/mission column plus three link
   groups (Explore / Understand / Help & legal): 4 columns above 800px, 2 columns
   521-800px, 1 column at 520px and below — in DOM order, no reordering. Column minima,
   gaps and the 38/32 padding come from the approved shell layout; they sit off the
   --s* scale on purpose rather than being rounded onto it. */
.foot-groups{display:grid;grid-template-columns:minmax(220px,1.5fr) repeat(3,minmax(130px,.65fr));
  gap:38px;margin:0;padding-block:38px 32px;
  border-top:1px solid var(--footer-muted);color:var(--footer-ink);font:var(--t-foot)}
/* A long package name or a compound label must wrap inside its own column, never widen
   the grid track it sits in. */
.foot-groups > *,.foot-base > *{min-width:0;max-width:100%;overflow-wrap:anywhere}
.foot-brand{display:flex;flex-direction:column;min-width:0}
/* Two points smaller than the header wordmark: the footer brand is a sign-off, not the
   page's primary identity. Colours stay with the wordmark component above. */
.foot-brand .wordmark{font:600 23px/23px var(--font-sans);letter-spacing:-.04em}
.foot-brand__mission{margin:14px 0 0;color:var(--footer-muted);max-width:300px}
.foot-group{display:flex;flex-direction:column;min-width:0}
/* A visible non-link group heading — not a section heading, so it is styled here, small and
   quiet, never inheriting the document h2. */
.foot-group__title{margin:0 0 12px;font:600 11px/18px var(--font-sans);
  text-transform:uppercase;letter-spacing:.09em;color:var(--footer-muted)}
.foot-group__links{list-style:none;margin:0;padding:0;display:flex;flex-direction:column}
.foot-group__links li{display:flex}
/* The LINK is the target, via its own min-height rather than spacing between rows, so the
   hit area matches what the reader sees. 32px is a 32px pitch between adjacent targets —
   comfortably clear of the 24px WCAG 2.2 AA floor (2.5.8), where the approved shell's own
   19px+7px rhythm clears it by two pixels. The 44px enhanced size (2.5.5, AAA) is kept for
   primary controls — header nav, buttons, form fields — not for secondary footer links,
   where it would inflate each group by two thirds and detach the hit area from the text.
   Plain footer-ink, underlined on hover/current — not the theme --link blue, which fails
   AA against the always-dark footer background in the light theme. */
.foot-group__links a{display:inline-flex;align-items:center;min-height:32px;
  color:var(--footer-ink);text-decoration:none;overflow-wrap:anywhere;
  text-decoration-thickness:1px;text-underline-offset:3px}
.foot-group__links a:hover,.foot-group__links a[aria-current]{text-decoration:underline}
/* The separated base line: the copyright, on its own rule. Language is switched from the
   header disclosure only, so nothing competes with it here. The border is a translucent
   tint of the footer ink, not --footer-muted, so it reads as a hairline inside the dark
   band rather than a second structural divider. */
.foot-base{display:flex;flex-wrap:wrap;gap:18px;align-items:center;
  justify-content:space-between;min-height:58px;padding-block:var(--s4);
  border-top:1px solid color-mix(in srgb,var(--footer-ink) 22%,transparent);
  color:var(--footer-muted);font:400 12px/18px var(--font-sans)}
.foot-base > *{margin:0}

/* ---- Generic card shell -------------------------------------------------- */
.card{background:var(--surface);border:1px solid var(--line);border-radius:var(--radius);overflow:hidden}
.sec{padding:var(--s5);border-top:1px solid var(--line)}
.sec:first-child{border-top:none}
/* `.sec h2` is the INTERFACE section H2 (§A2.1): Sans 600 22/28, normal case, ordinary
   text colour. It heads real sections — Home's "Two kinds of card" and "Categories", the
   receipt's "Who handles reports" — and those are section headings, not eyebrows. Calling
   the generic `.sec h2` a kicker was wrong; the kicker role belongs to the explicit kicker
   classes only (`.term-role`, `.scopeframe__toc-title`, `.page-index-h`, `.receipt__ref dt`,
   `thead th`, `.perms-heading`). Inside a document body the document H2 overrides
   this, scoped with the document shell further down. */
.sec h2{margin:0 0 12px;font:var(--t-h2-interface);letter-spacing:0;
  text-transform:none;color:var(--text)}

/* ---- Badges -------------------------------------------------------------- */
/* No live consumer. The directory row and the home legend/categories dropped badges
   first; PR 3B then replaced the app-detail flagged ratio with a `.vt-line` sentence,
   which was the last one. Both rules are kept for now rather than deleted in a copy
   cleanup — but do not add a consumer: a VirusTotal reading is a sentence, not a pill
   (DESIGN.md §4). */
.badge{display:inline-flex;align-items:center;gap:6px;font:var(--t-meta);padding:4px 10px;
  border-radius:999px;border:1px solid var(--line);background:var(--surface2);color:var(--mono)}
/* Dead with the rule above: this was the danger variant of the flagged badge. Never a
   "ratio" — the two numbers were never a fraction of each other. */
.badge.vt--flagged{color:var(--danger-fg);background:var(--danger-bg);border-color:transparent;font-weight:600}

/* ---- Term role label + curated-tag chips (DESIGN.md §A6/§A7) -------------
 * A PRIMARY category and a curated TAG must read as different roles by words and
 * geometry, never colour alone. The role label is a quiet uppercase eyebrow; the
 * TAG variant adds a hairline outlined small-radius rectangle. Tag chips (card
 * detail) are the same neutral outlined rectangle — a flat 6px radius, NOT the
 * decorative 999px pill and NOT a mode/trust colour. Chips are interactive links,
 * so they meet the 44px touch-target DoD (not weakened for secondary chips);
 * they wrap at 360px and stay legible in forced-colors (real border). */
.term-role{display:inline-block;margin:0 0 4px;font:var(--t-kicker);
  letter-spacing:.6px;text-transform:uppercase;color:var(--muted)}
.term-role--tag{padding:2px 8px;border:1px solid var(--border);border-radius:var(--radius-xs)}
.tags{margin:10px 0 0}
.tags .term-role{margin:0 0 6px}
.tag-list{display:flex;flex-wrap:wrap;gap:var(--s2);margin:0;padding:0;list-style:none}
.tag-list li{display:inline-flex}
.tag-chip{display:inline-flex;align-items:center;min-height:44px;padding:5px 12px;
  border:1px solid var(--border);border-radius:var(--radius-xs);background:var(--surface2);
  color:var(--link);font:var(--t-meta);text-decoration:none;overflow-wrap:anywhere}
.tag-chip:hover{color:var(--text)}

/* ---- Public forms (legal intake) ---------------------------------------- */
.field{margin:0 0 14px}
.field label{display:block;margin:0 0 4px;font:var(--t-strong);color:var(--muted)}
.field input[type=text],.field input[type=email],.field input[type=url],
.field input[type=search],.field textarea,
.field select{
  width:100%;padding:10px;background:var(--sheet);color:var(--text);
  border:1px solid var(--control-border);border-radius:var(--radius-xs);font:inherit;
  font:var(--t-body);min-height:44px}
/* Safari draws its own inset box and a non-focusable clear affordance on a search
   input, both of which would break the shared control geometry above. */
.field input[type=search]{appearance:none;-webkit-appearance:none}
.field input[type=checkbox]{margin-right:6px}

/* ---- Public intake form (DESIGN.md §A6) ---------------------------------
 * The one form pattern behind both report pages. Native structure only: a named
 * <form> labelled by its own H2, three <fieldset>/<legend> groups, a real checkbox,
 * and a submit that is a form action rather than a download CTA. No JS, no live
 * validation, no switch, no new palette.
 */
.legal-form__group{margin:0 0 var(--s5);padding:0;border:none;
  border-top:1px solid var(--line);padding-top:var(--s4)}
.legal-form__group > legend{padding:0;font:var(--t-h3);color:var(--text)}
.legal-form__hint{margin:0 0 var(--s4);font:var(--t-meta);color:var(--muted)}
/* 16px minimum on every legal-form control: iOS Safari zooms the page on focus below
   that. Scoped to this form so the catalogue filter controls keep their own size. */
/* The base field rules already carry the input and label roles (§A2.1); the legal form
   only raises the label's contrast. */
.legal-form .field label{color:var(--text)}
/* "Required" in words, not a bare asterisk — the form's hint says what it means. A
   secondary weight/colour, never a badge and never colour as the only carrier. */
.field__required{font-weight:400;color:var(--muted)}

/* Good-faith confirmation: input first inside one clickable row, long RU sentence
   wrapping beside it, whole row ≥44px. `align-items:flex-start` keeps the box on the
   first line rather than centred against a three-line sentence. */
/* `.field label` is (0,1,1) and would otherwise win `display:block` over a bare
   `.check-row` (0,1,0) — the row would lose its flex layout and its gap. Matched at the
   same specificity on purpose, not raised with !important. */
.field label.check-row{display:flex;align-items:flex-start;gap:var(--s3);min-height:44px;
  padding:10px 0;margin:0;cursor:pointer;font:var(--t-body);color:var(--text)}
.check-row input[type=checkbox]{flex:none;width:20px;height:20px;margin:2px 0 0}
.check-row__text{font:var(--t-body);color:var(--text)}
.check-row:focus-within{outline:2px solid var(--link);border-radius:var(--radius-xs)}

/* Validation. Deliberately NOT `.note.danger`: that variant means a confirmed
   VirusTotal flag and a blocked download, and a mistyped email is not a malware or
   integrity claim. Same danger TOKENS, its own components, and the words carry the
   state — the border and colour only reinforce them. */
.form-errors{margin:0 0 var(--s5);padding:var(--s4);border:1px solid var(--danger-fg);
  border-radius:var(--radius-xs);background:var(--danger-bg)}
.form-errors__title{margin:0 0 var(--s3);font:var(--t-h3);
  color:var(--danger-fg)}
.form-errors__list{margin:0;padding-left:1.2em;font:var(--t-meta)}
.form-errors__list a{color:var(--danger-fg)}
.field__error{margin:6px 0 0;font:var(--t-meta);color:var(--danger-fg)}
.field--invalid input,.field--invalid textarea,.field--invalid select{
  border-color:var(--danger-fg)}


/* ---- Service / recovery state — page family 6 (DESIGN.md §A6.1) ----------
 * The one 404, for an unknown address and for every `download` refusal alike. The split
 * rail: the marker in its own column, the words beside it. Its own frame, so it reads as a
 * state rather than as one more content card — the generic `.card` it used to sit in went
 * with the composition on 2026-09-04.
 *
 * DOM order is rail → body, and so is the visual order at every width: the rail is the left
 * column above the content breakpoint and the top band below it, switched by
 * `grid-template-columns` alone. No `order`, no `grid-area`, no JS.
 *
 * The marker is a calm SERVICE label — `--muted` and the shared hairline, never the brand,
 * a danger or a status colour, and never a live region. It carries no reason, no path, no
 * timer: this page is byte-identical for eleven different refusals, and the CSS must not be
 * the thing that gives one away.
 */
.service-state{display:grid;grid-template-columns:150px minmax(0,1fr);max-width:780px;
  margin:40px auto 0;overflow:hidden;border:1px solid var(--border);
  border-radius:var(--radius);background:var(--sheet)}
.service-state__rail{display:flex;min-height:300px;align-items:flex-start;
  justify-content:center;padding:34px 18px;border-right:1px solid var(--border);
  font:400 29px/36px var(--font-mono);letter-spacing:.6px;color:var(--muted)}
.service-state__body{min-width:0;padding:34px 38px 38px}
.service-state h1{margin:0 0 var(--s3)}
.service-state .lede{max-width:var(--measure);margin:0}
/* Two ways out, wrapping rather than colliding when a long RU label meets a narrow column. */
.service-state__actions{display:flex;flex-wrap:wrap;align-items:center;
  gap:var(--s3) var(--s4);margin-top:var(--s5)}
/* The secondary route is an ordinary underlined text link, never a second primary button. */
.service-state__link{display:inline-flex;align-items:center;min-height:44px;
  font:var(--t-strong);color:var(--link);text-decoration:underline;text-underline-offset:4px}

/* ---- Report receipt — page family 5 (DESIGN.md §A6.1) ---------------------
 * A compact state, not a document: no shell, masthead, breadcrumb, kicker or TOC.
 *
 * It sits on a sheet again (owner, 2026-09-07). It was made a plain centred section on
 * 2026-09-04 because the generic `.card` then made it read as one more document page —
 * true at the time, when a card was what a document wore. The intake forms have since
 * taken a sheet of their own under a heavy top rule, and the receipt wearing the same one
 * now says the opposite of what the old card said: not "another document" but "the end of
 * that flow". Inside it the ONE framed object is the reference block, because the
 * reference is the only thing the page exists for. No success colour, checkmark or progress indicator: nothing was
 * decided by sending the form, so the page must not look like something was. A copy
 * control for the reference is allowed (owner, 2026-09-07) — it claims nothing and
 * makes the one thing this page exists for easier to keep.
 */
.receipt{max-width:680px;margin-inline:auto;padding:var(--s6)}
.receipt h1{margin:0 0 var(--s3)}
/* Both the receipt's lede and its prose are UI/body (§A2.1) — the receipt is not a
   document — so this rule only holds the measure. */
.receipt > p{max-width:var(--measure)}

/* The reference block. Soft ground plus the brand BRACKET — the same grammar as the
 * wordmark's `[apk]`: two decorative side rules drawn as `::before`/`::after`, 2px in
 * --link, with short caps turning in at the top and bottom. Family-5 geometry, and the
 * reusable contract: rule 2px, cap 10px, side inset 0, block inset 0, and --s5 of inline
 * padding so the value clears the brackets at 320px. Purely decorative: empty `content`,
 * no element in the DOM, nothing in the accessibility tree. Borders (not backgrounds or
 * box-shadows) so the shape survives forced-colors, where system colours repaint it.
 */
.receipt__ref{margin:var(--s5) 0;padding:var(--s4) var(--s5);background:var(--surface2);
  --bracket-w:10px;color:var(--link)}
.receipt__ref dt{margin:0 0 var(--s2);font:var(--t-kicker);letter-spacing:.6px;
  text-transform:uppercase;color:var(--muted)}
.receipt__ref dd{margin:0;font:400 18px/28px var(--font-mono);
  color:var(--text);overflow-wrap:anywhere;word-break:break-all;user-select:text}

/* The scope paragraph is quiet text, not a second framed panel: a `.note` box here read as
   a competing card and pulled the eye off the reference. Same words, no border, no fill. */
.receipt__scope{margin:var(--s5) 0 0;color:var(--muted)}
/* Secondary section, separated by one hairline — no card, no fill. */
.receipt__contact{margin-top:var(--s6);border-top:1px solid var(--line);
  padding-top:var(--s5)}
.receipt__contact h2{margin:0 0 var(--s3);font:var(--t-h2-interface);color:var(--text)}

/* ---- Catalogue result card (PR 2B, DESIGN.md §A6) ------------------------
 * The one shared card for EVERY listing surface: Home's two per-kind sections,
 * /apps/, /games/, a PRIMARY category and a curated TAG. The Directory Field
 * Guide ledger it used to be contrasted with was home.html's own component and
 * was deleted with family 7 (2026-09-04). `ul.result-cards` is a flat
 * grid, 2 columns above the shared 560px breakpoint and 1 column at or below
 * it (DESIGN.md §A4); each `li.result-card` holds exactly one full-card
 * `a.result-card__link` — no nested links/buttons, no download/source CTA.
 * DOM order = reading order: identity → mode/scope → facts → a decorative
 * "View details" hint inside the same link. Flat card: hairline border, flat
 * radius, no shadow/gradient.
 */
.result-cards{list-style:none;margin:0;padding:0;display:grid;
  grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--s4)}
.result-card{min-width:0}
.result-card__link{display:flex;flex-direction:column;height:100%;min-width:0;
  padding:var(--s5);border:1px solid var(--border);border-radius:var(--radius);
  background:var(--sheet);color:inherit;text-decoration:none;overflow-wrap:anywhere}
.result-card__link:hover{border-color:var(--link)}
.result-card__link:focus-visible{outline:2px solid var(--link);outline-offset:2px}
.result-card__identity{display:flex;align-items:flex-start;gap:var(--s3);min-width:0}
.result-card__icon{width:48px;height:48px;border-radius:var(--radius-sm);background:var(--soft);
  border:1px solid var(--border);flex:0 0 auto;display:flex;align-items:center;justify-content:center;
  color:var(--muted);font-weight:600}
img.result-card__icon{object-fit:cover;background:var(--surface2)}
.result-card__text{min-width:0}
.result-card__title{display:flex;align-items:center;gap:6px;font:var(--t-h3);overflow-wrap:anywhere}
.result-card__sub{display:block;color:var(--muted);font:var(--t-meta);overflow-wrap:anywhere}
.result-card__scope{margin-top:var(--s4);color:var(--muted);font:var(--t-strong)}
.result-card__facts{margin-top:6px;display:flex;flex-direction:column;gap:2px;
  font:var(--t-meta);color:var(--muted)}
.result-card__fact{overflow-wrap:anywhere}
.result-card__fact-label{color:var(--muted)}
.result-card__fact--danger{color:var(--danger-fg);font-weight:600}
.result-card__link-hint{margin-top:auto;padding-top:var(--s4);color:var(--link);font:var(--t-meta)}
@media (max-width:560px){
  .result-cards{grid-template-columns:1fr}
}

/* ---- Taxonomy discovery (PR 2A, DESIGN.md §A6) ---------------------------
 * Same-kind indexable PRIMARY categories + curated TAG/task terms on a kind
 * index (/apps/, /games/), between the H1/lede and the unchanged record list.
 * One flat wrapper — border-block hairlines only, no .card/panel fill/shadow/
 * gradient/pill. Two sibling <section>s, DOM order = visual order. Either
 * group (or the whole wrapper) is entirely absent from the template when
 * empty — no CSS hides an empty heading/list here.
 */
.taxonomy-discovery{border-block:1px solid var(--border);padding-block:var(--s5);
  margin-bottom:var(--s6);display:flex;flex-direction:column;gap:var(--s5)}
.taxonomy-group__title{margin:0;font:var(--t-h3);color:var(--text)}
/* TAG/task reads visually quieter than PRIMARY. The two headings share the --t-h3 role
   and differ only in colour, so the ROLE is not carried by that colour: it is carried by
   the heading words ("Categories" vs "Features and tasks") and by the row geometry below
   — ruled 48px rows at --t-strong for PRIMARY against unruled 44px underlined --t-meta
   links for TAG (§A8). Do not remove either of those and leave the colour standing. */
.taxonomy-group--tag .taxonomy-group__title{font:var(--t-h3);color:var(--muted)}
.taxonomy-group__role{margin:4px 0 var(--s3);font:var(--t-meta);color:var(--muted)}
.taxonomy-group__list{list-style:none;margin:0;padding:0;
  display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:0 var(--s5)}
.taxonomy-group__list a{display:flex;align-items:center;justify-content:space-between;
  gap:var(--s3);min-width:0;overflow-wrap:anywhere;text-decoration:none}
/* PRIMARY: flat ruled rows — a hairline per cell, not a card/box. */
.taxonomy-group--primary .taxonomy-group__list li{border-top:1px solid var(--border)}
.taxonomy-group--primary .taxonomy-group__list a{min-height:48px;padding-block:8px;
  font:var(--t-strong);color:var(--text)}
.taxonomy-group--primary .taxonomy-group__list a:hover{color:var(--link)}
/* TAG/task: quieter still — no item border/background/radius, underlined links. */
.taxonomy-group--tag .taxonomy-group__list{gap:2px var(--s5)}
.taxonomy-group--tag .taxonomy-group__list a{min-height:44px;font:var(--t-meta);
  color:var(--link);text-decoration:underline}
.taxonomy-group__arrow{color:var(--muted);flex:0 0 auto}
@media (max-width:800px){
  .taxonomy-group__list{grid-template-columns:repeat(2,minmax(0,1fr))}
}
@media (max-width:560px){
  .taxonomy-discovery{padding-block:var(--s4);margin-bottom:var(--s5);gap:var(--s4)}
}
@media (max-width:520px){
  .taxonomy-group__list{grid-template-columns:1fr}
}

/* ---- Catalogue filters and sorting (PR 2C, DESIGN.md §A6) ---------------
 * One always-visible SSR GET panel on /apps/, /games/, a PRIMARY category and a
 * curated TAG page — never a collapsed <details>/<summary> gate, never a modal, no
 * behavioural JS. Reuses `.field` (label + 44px control) unchanged; adds two new
 * reusable primitives this PR introduces: `.btn`/`.btn--ghost` (neutral action
 * buttons — deliberately NOT `.dl`, which stays the claim-carrying HOST-download /
 * DATA_LINK-official-source CTA and would blur that meaning if reused here) and the
 * `.field select` arrow geometry (native select, `appearance:none` + the explicit
 * per-theme `--select-arrow` SVG token below — NOT `currentColor`: a `currentColor`
 * stroke inside a `background-image` data-URI does not resolve against the element's
 * computed colour, verified by pixel-sampling the rendered arrow).
 */
.filters{border-block:1px solid var(--border);padding-block:var(--s5);
  margin-bottom:var(--s6);display:flex;flex-direction:column;gap:var(--s4)}
.filters__title{margin:0;font:var(--t-h3);color:var(--text)}
/* Count-aware, not auto-fit: auto-fit's implicit column count depends on available
   width, so a 5-field row stranded its 5th field alone at in-between widths (e.g.
   900px). Four is now the only count there is — a kind index shows category + tag +
   availability + sort with `q` hoisted into `_catalog_search.html` above the discovery
   blocks (§A6.3), and a term page shows `q` + one of category/tag + availability +
   sort. The `.filters__grid--5` modifier covered the kind index while it also carried
   `q`; that combination is gone and the rule was deleted with it, not left unreferenced. */
.filters__grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));
  gap:var(--s4);align-items:end}
.filters__field{min-width:0}
.filters__actions{display:flex;align-items:center;gap:var(--s3);flex-wrap:wrap}
/* Native select, restyled arrow only — the field's own border/height/bg is `.field`
   (unchanged). --select-arrow is a real per-theme token (see :root/dark blocks above),
   not a currentColor trick — verified that data-URI currentColor does not resolve here. */
.field select{appearance:none;-webkit-appearance:none;-moz-appearance:none;
  color:var(--muted);padding-right:calc(var(--s5) + 4px);
  background-image:var(--select-arrow);
  background-repeat:no-repeat;background-position:right var(--s3) center;background-size:12px}
/* Neutral secondary action button — same visual language as `.theme-toggle`/`.lang`
   (surface2 fill, control-border, radius-sm), but its own class: those are chrome
   controls, this is a form action. Never the claim-carrying `.dl`. */
.btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;
  min-height:44px;padding:10px 18px;border-radius:var(--radius-sm);
  border:1px solid var(--control-border);background:var(--surface2);color:var(--text);
  font:var(--t-strong);cursor:pointer;text-decoration:none}
.btn:hover{border-color:var(--link);color:var(--link)}
.btn:focus-visible{outline:2px solid var(--link)}
/* Ghost variant — the Reset link: quieter, no fill, only shown when a filter is
   actually applied (template-conditional, not CSS-hidden). */
.btn--ghost{background:transparent}
/* Primary form action (PR 5) — the same action tokens the HOST CTA draws from, but its
   own variant: a submit button, never the claim-carrying `.dl`. Declared AFTER `.btn`
   on purpose: a variant placed before its base loses every shared property, since both
   are (0,1,0) and source order decides. Inherits .btn's 44px and focus ring. */
.btn--primary{background:var(--action-bg);color:var(--action-fg);border-color:transparent}
.btn--primary:hover{color:var(--action-fg);border-color:transparent;filter:brightness(1.06)}
.filters__applied{list-style:none;margin:0;padding:0;display:flex;flex-wrap:wrap;gap:var(--s2)}
.filters__chip{display:inline-flex;align-items:center;gap:6px;min-height:44px;
  padding:5px 12px;border:1px solid var(--border);border-radius:var(--radius-xs);
  background:var(--surface2);font:var(--t-meta);color:var(--text)}
.filters__chip-remove{color:var(--muted);text-decoration:none;font-weight:600}
.filters__chip-remove:hover{color:var(--link)}
.filters__empty{margin:0}
@media (max-width:800px){
  .filters__grid{grid-template-columns:repeat(2,minmax(0,1fr))}
}
@media (max-width:520px){
  .filters__grid{grid-template-columns:1fr}
  .filters{padding-block:var(--s4);margin-bottom:var(--s5)}
}

/* ---- Kind index — page family 8 (DESIGN.md §A6.1/§A6.3) ------------------
 * /apps/ and /games/. The page is a plain vertical sequence in the fixed §A6.3 order,
 * so it needs almost no layout of its own: header, search, the existing taxonomy
 * discovery wrapper, the existing filter panel, then the results section. No `.card`
 * shell, no rail, no legend, no illustration. DOM order is the visual, tab and CSS-off
 * order at every width — nothing here uses `order` or `grid-area`.
 *
 * TYPE NORMALIZATION (§A2.1) is done with CONTEXT selectors, deliberately. `.filters__title`
 * and `.taxonomy-group__title` are shared with page family 9 (term pages, which scope their
 * own copy of the `.filters__title` rule) and, for the taxonomy title, with Home's namespace
 * entries, where it is an H3 and must stay at the H3 role. Repointing the base classes would
 * move all three surfaces at once;
 * scoping to `.kind-index` moves exactly the family-8 headings, which §A2.1 puts at the
 * interface section H2. The two rules below are (0,2,0) and are placed after the
 * components they refine, so they beat `.taxonomy-group--tag .taxonomy-group__title`'s
 * font on equal specificity while leaving its `--muted` colour — the TAG group stays
 * quieter than PRIMARY by colour AND by its own row geometry, exactly as before.
 */
.kind-index__header{margin-bottom:var(--s5)}
.kind-index .taxonomy-group__title{font:var(--t-h2-interface)}
.kind-index .filters__title{font:var(--t-h2-interface)}

/* Search — the control lives here in the DOM but belongs to the filter form further
   down (HTML `form` attribute, see `_catalog_search.html`). Reuses `.field` for the
   label, the 44px control height and the 16px value that stops iOS zooming on focus;
   only the row and the field's stacking margin are new. */
.catalog-search{margin-bottom:var(--s5)}
.catalog-search .field{margin:0}
.catalog-search__row{display:flex;flex-wrap:wrap;gap:var(--s3)}
/* Same wrapping rule the Home search uses: the field keeps a readable minimum and the
   button drops to its own line on content width alone, so no breakpoint is spent on it. */
.catalog-search__row input[type=search]{flex:1 1 16rem;min-width:0}
.catalog-search__row .btn{flex:0 0 auto}

/* Results — a labelled section with its own heading and a plain status line. The status
   is server-rendered text, never a live region: the page reloads in full, so there is no
   dynamic change to announce. */
.catalog-results__head{display:flex;flex-wrap:wrap;align-items:baseline;
  gap:var(--s2) var(--s4);margin-bottom:var(--s4)}
.catalog-results__title{margin:0;font:var(--t-h2-interface);color:var(--text)}
.catalog-results__status{margin:0;font:var(--t-meta);color:var(--muted)}
/* Zero-match state: one flat bordered block on the shared radius — no shadow, no
   gradient, no illustration, and never a danger or success state. */
.catalog-results__empty{padding:var(--s5);border:1px solid var(--border);
  border-radius:var(--radius);background:var(--sheet)}
.catalog-results__empty h3{margin:0 0 var(--s2)}
.catalog-results__empty p{margin:0;font:var(--t-body);color:var(--muted);
  max-width:var(--measure)}
/* The recovery link is itself a <p> inside the block, so the rule above — (0,1,1) —
   out-specifies a bare `.catalog-results__empty-action` (0,1,0) and its margin-top was
   being discarded, leaving the link flush against the explanation. Matched at (0,2,0)
   instead of reaching for !important. */
.catalog-results__empty .catalog-results__empty-action{margin-top:var(--s3)}
.catalog-results__empty-action a{display:inline-flex;align-items:center;min-height:44px}

/* ---- Term page — page family 9 (DESIGN.md §A6.1/§A6.3) -------------------
 * One template for all four combinations (APP/GAME x PRIMARY/curated TAG). Like the
 * kind index it is a plain vertical sequence in the fixed §A6.3 order, so it owns almost
 * no layout: a back row, one muted context line, then family 8's own search control,
 * filter panel and `.catalog-results` section, reused rather than forked. No `.card`
 * shell, no kicker, no discovery block, no illustration. DOM order is the visual, tab and
 * CSS-off order at every width — nothing here uses `order` or `grid-area`.
 */
.term-page__back-row{margin:0 0 var(--s3)}
/* Action-label role (§A2.1, Sans 600 14/20), underlined, 44px — the same role the 404's
   recovery route and Home's namespace link take. A plain text link on purpose: no arrow,
   glyph or icon, because direction is already carried by its position and its words and a
   one-off icon system is the near-duplicate §A10 forbids. */
.term-page__back{display:inline-flex;align-items:center;min-height:44px;
  font:var(--t-strong);color:var(--link);text-decoration:underline;text-underline-offset:4px}
/* Kind + role, as one quiet sentence. Deliberately NOT `.term-role--tag`'s outlined
   plate, which read as a status chip on the page it described; the roles here differ by
   their words alone — no border, background, radius or colour split (§A7/§A8). */
.term-page__context{margin:0 0 var(--s1);font:var(--t-meta);color:var(--muted)}
/* The filter panel keeps `q` hoisted (external_search), and a term page fixes its own
   role's term, so exactly three selects remain: the opposite-role term, Record type and
   Sort. Scoped to `.term-page` so the kind index keeps its four-column grid; the two
   column counts step down on the SAME existing breakpoints, and no `auto-fit` — its
   implicit count depends on available width and strands a trailing field. */
.term-page .filters__grid{grid-template-columns:repeat(3,minmax(0,1fr))}
/* Stepped down HERE, immediately after the rule they refine, and not inside the filter
   panel's own media blocks further up the file: those come earlier, and at equal (0,2,0)
   specificity source order decides — the base rule would have won and the grid would have
   stayed three columns all the way down to 320px, squeezing each select to 83px. Same
   shared breakpoints as the four-column grid; no new one. */
@media (max-width:800px){
  .term-page .filters__grid{grid-template-columns:repeat(2,minmax(0,1fr))}
}
@media (max-width:520px){
  .term-page .filters__grid{grid-template-columns:1fr}
}
/* Interface section H2 (§A2.1), by context: `.filters__title` is shared with the kind
   index, which scopes its own copy of this rule, so neither page moves the base class. */
.term-page .filters__title{font:var(--t-h2-interface)}

/* ---- Home — page family 7 (DESIGN.md §A6.1/§A6.2) ------------------------
 * Hero, one kind-scoped search, two namespace entries, two card sections. No card
 * shell, no legend, no rail: the retired `.mode-legend`/`.cat-rail*`/`.recent*`/
 * `.record-*`/`.kind-word` rules were DELETED with their markup, not left unreferenced.
 * Everything below is a Home-owned wrapper around already-shared components — `.field`,
 * `.btn`, `.taxonomy-group*`, `.result-cards` — so this file gains layout, not a second
 * copy of a control or a list.
 *
 * Both Home-owned grids collapse to one column at the SHARED 560px breakpoint (the one
 * `.result-cards` already uses); no new breakpoint is introduced. Collapsing is
 * `grid-template-columns` only — DOM order is the reading, keyboard and CSS-off order at
 * every width, with no `order`/`grid-area`.
 */
/* Flex, not grid, and deliberately: with a fixed `auto` button column the field kept
   whatever was left, which at 561px in Russian was 192px of input beside 309px of
   buttons. Wrapping instead keeps the field readable — it is given a 16rem flex-basis,
   the width below which a query stops being legible while typing, so the buttons drop to
   their own line rather than squeezing it. That happens on content width alone, so no
   breakpoint is spent on it. */
.home-search{display:flex;flex-wrap:wrap;align-items:end;gap:var(--s3);
  margin-bottom:var(--s6)}
/* The shared `.field` carries the label, the 16px value (so iOS Safari does not zoom on
   focus) and the 44px control height; only its stacking margin is dropped here. */
.home-search .field{margin:0;min-width:0;flex:1 1 16rem}
.home-search__actions{display:flex;flex-wrap:wrap;gap:var(--s3)}
.ns-entries{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--s4);
  margin-bottom:var(--s6)}
.ns-entry{border-top:1px solid var(--border);padding-top:var(--s3);min-width:0}
.ns-entry__title{margin:0 0 var(--s1);font:var(--t-h2-interface);color:var(--text);
  overflow-wrap:anywhere}
/* Third consumer of the 600-weight secondary-action ROLE (DESIGN.md §A10 "Two
   secondary-action links", now three): a namespace index link is an entry action, like
   the 404's Contact route, not the 400-weight `.action-secondary` supplement. It picks
   that role rather than inventing a fourth; it keeps its own selector because family 6
   owns `.service-state__*` as a closed set (tests/test_pr6_service_states.py), so the
   two may not be merged into one rule. */
/* A full-width row, not an inline label: the arrow sits at the far edge of the entry the
   way the approved home draws it, so the whole line reads as the door into that kind. */
.ns-entry__link{display:flex;align-items:center;justify-content:space-between;gap:var(--s2);
  min-height:44px;font:600 16px/24px var(--font-sans);color:var(--link);
  text-decoration:underline;text-underline-offset:4px}
.ns-entry__group{margin-top:var(--s3)}
/* A flat inline list of that kind's categories, not the kind index's ruled component:
   here it is a preview inside one of two columns, so it wraps as quiet links with no
   rules and no per-cell chrome. Link-coloured, no arrow — the entry link above already
   carries the one arrow this block needs. */
.ns-entry__terms{list-style:none;margin:var(--s2) 0 0;padding:0;
  display:flex;flex-wrap:wrap;gap:var(--s1) var(--s4)}
.ns-entry__terms a{display:inline-flex;align-items:center;min-height:44px;
  font:var(--t-meta);color:var(--link);text-decoration:underline;text-underline-offset:4px}
.ns-entry__terms a:hover{color:var(--text)}
.home-section{margin-bottom:var(--s6)}
.home-section:last-child{margin-bottom:0}
.home-section__title{margin:0;font:var(--t-h2-interface);color:var(--text);
  letter-spacing:-.025em}
/* What the feed under it actually reports — the sentence that keeps "recently added" from
   being read as "newly released", and "new version" from being read as an edited page. */
.home-section__subtitle{margin:var(--s2) 0 var(--s4);max-width:var(--measure);
  font:var(--t-meta);color:var(--muted)}
/* Three across: a feed row is identity + scope + one event, so it stays legible narrower
   than a result card, which also carries version, size and the VirusTotal reading. */
.home-feed{list-style:none;margin:0;padding:0;display:grid;
  grid-template-columns:repeat(3,minmax(0,1fr));gap:var(--s4)}
.home-feed__item{min-width:0}
/* Two columns, not a stack: the icon holds a fixed 48px track and everything else lives in
   the second one. That is what keeps the footer rule starting at the title's left edge
   instead of running under the icon — the alignment that broke once the card narrowed. */
.home-feed__link{display:grid;grid-template-columns:48px minmax(0,1fr);gap:var(--s3);
  align-items:start;height:100%;padding:var(--s4);border:1px solid var(--border);
  border-radius:var(--radius-sm);background:var(--sheet);color:inherit;
  text-decoration:none;overflow-wrap:anywhere}
.home-feed__link:hover{border-color:var(--link)}
.home-feed__link:focus-visible{outline:2px solid var(--link);outline-offset:2px}
.home-feed__icon{width:48px;height:48px;border-radius:var(--radius-sm);background:var(--soft);
  display:flex;align-items:center;justify-content:center;
  color:var(--link);font:600 22px/28px var(--font-sans)}
img.home-feed__icon{object-fit:cover}
/* Full height so the footer's `margin-top:auto` can sit it on the card's own bottom edge,
   keeping the event line level across a row of cards with different title lengths. */
.home-feed__copy{min-width:0;height:100%;display:flex;flex-direction:column}
.home-feed__title{display:block;font:var(--t-h3)}
.home-feed__meta{display:block;margin-top:2px;color:var(--muted);font:var(--t-meta)}
/* The event line is separated by its own hairline: the scope above it is a standing fact
   about the card, the line below is one dated thing that happened. */
.home-feed__footer{margin-top:auto;padding-top:10px;display:grid;gap:6px;
  border-top:1px solid var(--border)}
.home-feed__scope{display:block;font:var(--t-strong);color:var(--text)}
.home-feed__event{display:block;font:var(--t-meta);color:var(--muted)}

/* Placed AFTER the component's own rules: same specificity, so source order decides, and
   these sat above it before — which meant the feed never left three columns. */
@media (max-width:900px){
  .home-feed{grid-template-columns:repeat(2,minmax(0,1fr))}
}
@media (max-width:600px){
  .home-feed{grid-template-columns:minmax(0,1fr)}
  /* A single-column card is already full width; it buys back the room it no longer needs. */
  .home-feed__link{gap:11px;padding:14px}
}

.home-section__all{margin:var(--s4) 0 0}
/* Meets the 44x44 target rule (DESIGN.md §A8), the same native inline-flex pattern the
   nav and footer links use. */
.home-section__all a{display:inline-flex;align-items:center;min-height:44px;min-width:44px}

/* ==== App detail hero (DESIGN.md §A6) =====================================
 * Desktop: two-column grid, identity left / mode-specific action right, both
 * start-aligned. Mobile: one column via grid-template-columns only (no CSS
 * order/grid-area reorder), at the shared 560px breakpoint. No description
 * slot: `App` has no such field.
 */
.detail-hero{display:grid;grid-template-columns:minmax(0,1fr) minmax(280px,.62fr);
  gap:var(--s7);align-items:start;padding:var(--s5) var(--s5) 0}
.head{display:flex;gap:var(--s5);align-items:flex-start}
/* Placeholder icon: flat fill, square, never stretched. */
.icon{width:88px;height:88px;border-radius:19px;background:var(--soft);border:1px solid var(--border);
  flex:0 0 auto;display:flex;align-items:center;justify-content:center;color:var(--muted);
  font-size:34px;font-weight:600}
img.icon{object-fit:cover;background:var(--surface2)}
.htext{flex:1;min-width:0}
/* Baseline, not centre: the identity row never stacks, so a long title wraps to several
   lines here and a centred marker floats out beside the middle of the name. */
.identity{display:flex;align-items:baseline;gap:10px}
/* Short and long titles share this size — wrap, never clip/ellipsis/shrink. */
.detail-title{margin:0;font:var(--t-h1-page);color:var(--text);
  letter-spacing:-.01em;overflow-wrap:anywhere}
.dev{color:var(--muted);font:var(--t-meta);margin:9px 0 0}
/* Underline supplies the non-colour cue WCAG requires on muted-text links. */
.dev a,.vt a{color:var(--link);text-decoration:underline}
.detail-action-col{min-width:0}
/* Text mode marker — a plain 600 line, never a shield/trust pill. */
.mode-line{margin:0 0 var(--s3);padding:0;font:var(--t-strong);color:var(--muted);
  letter-spacing:.4px}
/* One shared action-panel shell for every mode/claim state; only its contents differ. */
.action{padding:var(--s5);display:flex;flex-direction:column;gap:var(--s3);
  align-items:flex-start;background:var(--sheet);border:1px solid var(--border);
  border-radius:var(--radius)}
.action>.dl,.action>.dl.ext{align-self:stretch;justify-content:center}
/* HOST download: neutral primary action, not a safety colour. */
.dl{background:var(--action-bg);color:var(--action-fg);border:none;border-radius:10px;
  padding:12px 20px;font:var(--t-strong);cursor:pointer;min-height:44px;
  display:inline-flex;gap:9px;align-items:center;justify-content:center;text-decoration:none}
.dl.ext{background:var(--datalink-bg);color:var(--datalink-fg)}
.action-limit{margin:0;font:var(--t-meta);color:var(--muted)}
@media (max-width:560px){
  .detail-hero{grid-template-columns:1fr;gap:var(--s5);padding:var(--s4) var(--s4) 0}
  .icon{width:64px;height:64px;border-radius:16px;font-size:26px}
}

table{width:100%;border-collapse:collapse;font:var(--t-meta)}
th,td{text-align:left;padding:8px 10px;border-bottom:1px solid var(--line)}
/* Only column headers are the uppercase eyebrow; a scope="row" version cell is a row
   header but must read as data, not a second header row. */
thead th{color:var(--muted);font:var(--t-kicker);text-transform:uppercase;letter-spacing:.4px}
tr:last-child td,tr:last-child th{border-bottom:none}
/* Version cell (td in older markup, now th scope="row"): mono data, not bold. */
.v{font:var(--t-data);color:var(--mono)}
/* "current" marker — neutral, the ● + version name carry it, not colour alone. */
.cur{color:var(--text);font-weight:600}
/* A version table can hold a long value; let it scroll inside the card, not the viewport. */
.table-scroll{overflow-x:auto}

/* Compatibility is text: the label is the source of truth, so there is no gauge or
   meter (a bar reads like a score, DESIGN.md §A0). */
.compat-line{font:var(--t-meta);color:var(--muted);margin:0}
.compat-line b{color:var(--text)}

/* The full current set gets its own small heading, distinct from the section h2. */
.perms-heading{margin:16px 0 0;font:var(--t-kicker);letter-spacing:.4px;color:var(--muted)}
/* Current permissions are a real list. */
.perms{display:grid;grid-template-columns:1fr 1fr;gap:6px 18px;margin-top:var(--s4);
  list-style:none;padding:0}
.perm{display:flex;align-items:center;gap:8px;font:var(--t-meta);padding:3px 0;min-width:0;overflow-wrap:anywhere}
.perm .tag{color:var(--muted)}
.dot{width:7px;height:7px;border-radius:50%;background:var(--muted);flex:0 0 auto}
/* dangerous permission: a limitation note, not danger red. */
.dot.d{background:var(--limitation-fg)}

/* Permission delta is a list too; the empty single-version note is a paragraph. */
.diff{font:var(--t-meta);list-style:none;margin:0;padding:0}
.diff.empty{color:var(--muted);margin:0}
/* Permission delta: added/removed are neutral facts, not good/bad verdicts. The +/−
   glyph carries the direction and is the ONLY carrier — BOTH rows draw the ordinary
   reading colour. `.add` used to draw --app-fg (the APP namespace token, unrelated to
   a permission delta) and `.rem` the taupe --limitation-fg; either one alone still read
   as a verdict pair. No success green, no danger red, no limitation taupe, no kind
   colour. The classes remain as semantic hooks. */
.add,.rem{color:var(--text)}

/* ---- Fact list (DESIGN.md §A6) --------------------------------------------
 * The ONE label→value pattern for informational pages: the operator block and Privacy's
 * per-path provider table. A native `dl`, so the pairing is real structure rather than a
 * visual column.
 *
 * It replaces `.prov`, whose `.val` set system mono AND `word-break:break-all` on every
 * value. That is right for a hash and wrong for everything these pages actually carry:
 * an operator's name, a country, a status line and Privacy's multi-sentence descriptions
 * all rendered as technical strings, broken mid-word. Ordinary values are Sans and wrap
 * on word boundaries; `dd.mono` is the opt-in for genuinely exact data such as an
 * address, and it is the only place `overflow-wrap:anywhere` applies.
 */
.fact-list{display:grid;grid-template-columns:150px minmax(0,1fr);gap:var(--s2) var(--s4);
  margin:var(--s4) 0 0;font:var(--t-meta)}
.fact-list dt{color:var(--muted);overflow-wrap:break-word}
.fact-list dd{margin:0;min-width:0;color:var(--text);overflow-wrap:break-word}
.fact-list dd.mono{font-family:var(--font-mono);color:var(--mono);overflow-wrap:anywhere}

/* Evidence list — one native dl/dt/dd contract for the detail record: the fact
   summary and every label→value evidence block (signature, licence, DATA_LINK
   reference). A div.evidence-row wraps each dt/dd pair so the pair keeps valid,
   accessible dl semantics while a local grid lays it out (DESIGN.md §A6). */
.evidence-list{margin:0;display:flex;flex-direction:column;gap:8px;font:var(--t-meta)}
.evidence-row{display:grid;grid-template-columns:150px 1fr;gap:2px 14px;align-items:baseline}
.evidence-row dt{margin:0;color:var(--muted)}
/* dd is overflow-safe by default: any exact value (certificate subject, DATA_LINK
   publisher) wraps rather than pushing the card wider — no per-row class needed. */
.evidence-row dd{margin:0;min-width:0;color:var(--text);overflow-wrap:anywhere}
/* Exact data (hashes, certificates, packages): mono, selectable, wraps without a
   global break-all so ordinary URLs elsewhere are unaffected. */
.evidence-row dd.mono{font-family:var(--font-mono);overflow-wrap:anywhere}
.evidence-row dd.long{overflow-wrap:anywhere}
.evidence-row dd a{overflow-wrap:anywhere}

.vt{font:var(--t-meta);color:var(--muted)}

/* Secondary action link — a visibly secondary route, never a second primary button.
   Its original consumer was the App Store link on a DATA_LINK card, removed from the
   site on 2026-09-07; the class itself has no markup now. */
.action-secondary{display:inline-flex;align-items:center;min-height:44px;color:var(--link);
  font:var(--t-meta);text-decoration:underline}

/* Notes / callouts: neutral note, limitation (taupe), danger (real block only). */
.note{font:var(--t-meta);color:var(--muted);margin-top:10px;padding:10px 12px;background:var(--surface2);
  border-radius:var(--radius-sm);border-left:3px solid var(--border)}
.note.fact{border-left-color:var(--limitation-fg)}
.note.warn-strong{border-left-color:var(--limitation-fg)}
/* Download gate: replaces the button when a file is flagged — a real danger. */
.note.danger{border-left-color:var(--danger-fg);background:var(--danger-bg);color:var(--text);
  margin-top:0;flex:1 1 100%}

/* verification guide (DATA_LINK) */
.guide h3{margin:14px 0 6px;font:var(--t-h3);color:var(--text)}
.guide ul,.guide ol{margin:0 0 6px;padding-left:20px;font:var(--t-meta);color:var(--muted)}
.guide li{margin:3px 0}
.cmd{margin:6px 0;padding:8px 10px;background:var(--surface2);border:1px solid var(--line);
  border-radius:var(--radius-xs);font:var(--t-data);color:var(--mono);overflow-wrap:anywhere}

/* ---- Scopeframe document (DESIGN.md §A6.1, families 1–4) -------------------
 * Methodology and Privacy (family 1, with a TOC and numbered sections), About (family 2),
 * Contact (family 3) and — since 2026-09-04 — the two report forms (family 4) share this
 * composition: a short brand rule + kicker, the document H1, the document lede, then
 * ruled sections. Flat — no card, no boxed folio, no 4px title bar, no shadow, no gradient.
 * Rules and spacing separate (§A3).
 *
 * Family 4 adds its own body order (the form first) and the few form-only rules further
 * down; it changes nothing here.
 */
.scopeframe{min-width:0}
.scopeframe__masthead{padding-block:0 var(--s6);border-bottom:1px solid var(--line)}
/* The rule is the kicker's own ::before, so the DOM carries no empty presentational box. */
.scopeframe__kicker{display:flex;align-items:center;gap:var(--s3);margin:0 0 var(--s4);
  font:var(--t-kicker);letter-spacing:.09em;text-transform:uppercase;color:var(--muted)}
.scopeframe__kicker::before{content:"";flex:0 0 32px;height:3px;background:var(--link)}
.scopeframe__kicker:empty{display:none}
/* Document/task H1 role: 600 34/40, 28/34 below the content breakpoint.
   overflow-wrap is the safeguard for a long RU title; the measure keeps it from running
   the full 1120px column. */
.scopeframe__title{max-width:21ch;margin:0;font:var(--t-h1-document);
  overflow-wrap:anywhere}
/* Scoped by ANCESTOR, not by a new class: `.lede` is a legacy shared class whose role
   depends on where it sits (§A2.1), and scoping keeps that true for every page in this
   shell without each one having to opt in. */
.scopeframe__masthead .lede{max-width:var(--measure);margin:var(--s4) 0 0;
  font:var(--t-lede);color:var(--muted)}
.scopeframe__body .lede{font:var(--t-prose)}

.scopeframe__layout{display:block}
.scopeframe__body{min-width:0;max-width:var(--measure);font:var(--t-prose)}
/* Ruled sections: a hairline between them, never a box around each one. */
.scopeframe__section{padding-block:var(--s6) 0}
.scopeframe__section + .scopeframe__section{border-top:1px solid var(--line)}
.scopeframe__section > :last-child{margin-bottom:0}
.scopeframe__section p{margin:0 0 var(--s4)}
.scopeframe__section ul,.scopeframe__section ol{margin:0 0 var(--s4);padding-left:22px}
.scopeframe__section li + li{margin-top:var(--s2)}
/* Document section H2: 600 22/28, normal case, full text colour. */
.scopeframe__heading{display:flex;align-items:baseline;gap:var(--s3);margin:0 0 var(--s4)}
.scopeframe__section h2{margin:0;font:var(--t-h2-document);letter-spacing:0;
  text-transform:none;color:var(--text);overflow-wrap:anywhere;scroll-margin-top:var(--s5)}
/* Native fragment orientation: mark a jumped-to heading with a shape, not colour alone —
   legible under forced-colors. */
.scopeframe__section h2[id]:target{outline:2px solid var(--link);outline-offset:4px;
  border-radius:var(--radius-xs)}
.scopeframe__section h3{margin:var(--s5) 0 var(--s2);font:var(--t-h3);color:var(--text)}

/* Section numbers are a CSS counter poured into an EMPTY aria-hidden span, so the number
   is never part of a heading's accessible name — a screen reader hears the heading text
   alone. The counter lives on the numbered variant only (family 1). */
.scopeframe__layout--with-toc .scopeframe__body{counter-reset:scopeframe-section}
.scopeframe__layout--with-toc .scopeframe__section{counter-increment:scopeframe-section}
.scopeframe__number{flex:0 0 auto;font:var(--t-data);color:var(--link)}
.scopeframe__layout--with-toc .scopeframe__number::before{
  content:counter(scopeframe-section,decimal-leading-zero)}

/* Semantic comparison table (HOST vs DATA_LINK on Methodology): a real table with row and
   column headers, not a layout grid. Fixed layout plus overflow-wrap keeps a long RU cell
   inside the measure instead of pushing the page sideways. */
.scopeframe__modes{width:100%;margin:var(--s4) 0 0;border-collapse:collapse;
  table-layout:fixed;font:var(--t-meta)}
.scopeframe__modes th,.scopeframe__modes td{min-width:0;padding:var(--s3);
  border-bottom:1px solid var(--line);text-align:left;vertical-align:top;
  overflow-wrap:anywhere}
.scopeframe__modes tr:first-child > *{border-top:1px solid var(--line)}
.scopeframe__modes tr > * + *{border-left:1px solid var(--line)}
.scopeframe__modes th{font:var(--t-strong);color:var(--text)}
.scopeframe__modes th[scope=row]{width:26%;color:var(--muted)}

/* Task rows (family 3, the Contact router). ONE link per row wrapping its title, its
   supporting line and a decorative arrow: a single tab stop with one accessible name, and
   no nested interactive element. Ruled, never boxed, and never numbered. */
.task-list{list-style:none;margin:var(--s5) 0 0;padding:0;border-top:1px solid var(--line)}
.task-row{border-bottom:1px solid var(--line)}
.task-row a{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:var(--s4);
  align-items:center;min-height:76px;padding:var(--s3) 0;color:var(--text);
  text-decoration:none}
.task-row__text{min-width:0}
.task-row__title{display:block;font:var(--t-h3);overflow-wrap:anywhere}
.task-row__copy{display:block;margin-top:2px;font:var(--t-meta);color:var(--muted);
  overflow-wrap:anywhere}
/* Decorative and aria-hidden, but still text on the page, so it takes a role from the
   scale rather than a size of its own (§A2.1). */
.task-row__arrow{color:var(--link);font:var(--t-body)}
.task-row a:hover .task-row__title,.task-row a:focus-visible .task-row__title{
  color:var(--link);text-decoration:underline;text-underline-offset:4px}

/* TOC rail. DOM order is TOC → body at every width: below the content breakpoint it simply
   stacks, above it the grid places the rail beside the body. No CSS `order`, no JS, not
   sticky, no active-section tracking.
   Stacked, the list is two columns (the approved reference's `repeat(2, minmax(0,1fr))`):
   eight full-width 44px rows were taller than a phone screen and pushed the document below
   the fold. Grid flows in DOM order, left to right and
   then down — 1 2 / 3 4 / 5 6 / 7 8 — so the visual order, the reading order, the tab
   order and the numbering all still agree. The 44px target and the
   `minmax(0,…)` tracks hold for the longer Russian labels. */
.scopeframe__toc{margin:var(--s5) 0 0;padding:0 0 var(--s5);
  border-bottom:1px solid var(--line)}
.scopeframe__toc-title{margin:0 0 var(--s3);font:var(--t-kicker);letter-spacing:.09em;
  text-transform:uppercase;color:var(--muted)}
.scopeframe__toc ol{list-style:none;margin:0;padding:0;counter-reset:scopeframe-toc;
  display:grid;grid-template-columns:repeat(2,minmax(0,1fr));column-gap:var(--s4)}
.scopeframe__toc li{counter-increment:scopeframe-toc;border-top:1px solid var(--line)}
.scopeframe__toc a{display:grid;grid-template-columns:25px minmax(0,1fr);gap:var(--s2);
  align-items:center;min-height:44px;font:var(--t-toc);color:var(--muted);
  text-decoration:none;overflow-wrap:anywhere}
.scopeframe__toc a::before{content:counter(scopeframe-toc,decimal-leading-zero);
  font:var(--t-data);color:var(--link)}
.scopeframe__toc a:hover,.scopeframe__toc a:focus-visible{color:var(--text);
  text-decoration:underline;text-underline-offset:4px}
@media (min-width:601px){
  .scopeframe__layout--with-toc{display:grid;grid-template-columns:210px minmax(0,1fr);
    gap:0 var(--s6);align-items:start}
  .scopeframe__toc{margin:var(--s6) 0 0;padding:0 var(--s5) 0 0;
    border-bottom:0;border-right:1px solid var(--line)}
  /* One column in the rail — the two-column pairing exists only for the stacked
     phone layout, where eight full-width rows pushed the document off the screen. */
  .scopeframe__toc ol{grid-template-columns:minmax(0,1fr);column-gap:0}
}

/* ---- FAMILY 4: the form-task page (DESIGN.md §A6.1 family 4, §A6.5) ---------
 * The 4A–4C folio that lived here — `.document-page*`, its 4px title rule and its card —
 * was deleted on 2026-09-04 when family 4 moved onto the Scopeframe composition above.
 * Nothing rendered it afterwards, in any template, view or test.
 *
 * Family 4 reuses the Scopeframe masthead, sections, tokens and widths unchanged; what it
 * adds is below, and only what the form genuinely needs. THE FORM IS THE TASK, so the body
 * puts it first and the supporting sections after it — that is template order, not CSS
 * reordering: there is no `order` and no `grid-area` anywhere in this block.
 */
/* The task body is narrower than document prose by nothing at all — same --measure — but
   it does not inherit the 16/26 reading role, because a form is controls and labels, not
   prose. Its own paragraphs keep the document role where they appear in a section. */
.scopeframe__body--task > .legal-form{padding-block:var(--s6) 0}
.scopeframe__body--task > .legal-form + .scopeframe__section{
  border-top:1px solid var(--line)}

/* The form's own heading — "Send report", the H2 the <form> is labelled by. It is a
   document/task section heading and takes that role, exactly like `.scopeframe__section h2`
   beside it. It needs saying explicitly: the form is not a `.scopeframe__section` (it is the
   task, not a supporting section) and stopped being a `.sec` when family 4 left the folio, so
   with no context rule it fell through to the bare `h2` element and rendered Sans 600 24/26 —
   a size that is not in §A2.1 at all. It uses the same document-H2 typographic role
   (`--t-h2-document`, plus the same case/letter-spacing/colour reset), so the two headings
   cannot drift apart typographically. The layout declarations differ on purpose: this one owns
   its own bottom margin, and it takes no `scroll-margin-top`, because a form heading is not a
   fragment target the way a numbered section heading is. */
.legal-form > h2{margin:0 0 var(--s4);font:var(--t-h2-document);letter-spacing:0;
  text-transform:none;color:var(--text);overflow-wrap:anywhere}

/* Field grid. Two columns above the shared 560px stacking breakpoint, one below it —
   `grid-template-columns` only, so DOM order IS visual order and tab order at every width.
   `.field--full` spans both tracks for the rows that need the room (the textarea, the URLs,
   the hash). minmax(0,…) lets a long RU label wrap instead of forcing the track wider. */
.legal-form__grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));
  gap:0 var(--s4)}
.legal-form__grid > .field--full{grid-column:1 / -1}

/* Optional identifiers, Copyright only (§A6.5). A native <details>: the state is the
   element's own, `open` is decided server-side, and there is no JS. The summary is a real
   control — 44px, its own focus ring, the shared chevron rotating on open. */
.legal-form__disclosure{margin:0 0 var(--s4);border:1px solid var(--line);
  border-radius:var(--radius-xs);background:var(--surface2)}
.legal-form__summary{display:flex;align-items:center;justify-content:space-between;
  gap:var(--s3);min-height:44px;padding:var(--s3) var(--s4);cursor:pointer;
  font:var(--t-strong);color:var(--text);list-style:none}
.legal-form__summary::-webkit-details-marker{display:none}
.legal-form__summary:focus-visible{outline:2px solid var(--link);
  border-radius:var(--radius-xs)}
.legal-form__disclosure[open] .legal-form__summary{border-bottom:1px solid var(--line)}
.legal-form__disclosure[open] .chevron{transform:rotate(180deg)}
.legal-form__disclosure-body{padding:var(--s4) var(--s4) var(--s2)}

/* ---- Responsive: shared behaviour breakpoints (DESIGN.md §A4) ------------ */
@media (max-width:560px){
  /* Kind index (family 8): the search button goes FULL WIDTH rather than sharing a row
     with the input — at 320px the content column is 288px and a Russian "Искать" beside
     a usable field would squeeze one of them. Same shared breakpoint, no new one. */
  .catalog-search__row .btn{flex:1 1 100%}
  /* Home (family 7): the namespace grid goes one-column here, in DOM order. The two
     search buttons go FULL WIDTH rather than sharing a row — at 320px the content column
     is 288px and two Russian labels ("Искать приложения" / "Искать игры") cannot sit
     side by side without squeezing below the 44px target. The field's own wrapping is
     content-driven (above) and needs no rule here. */
  .home-search__actions{flex:1 1 100%}
  .home-search__actions .btn{flex:1 1 100%}
  .ns-entries{grid-template-columns:1fr}
  .perms{grid-template-columns:1fr}
  /* Label + value stack naturally below the shared breakpoint (DESIGN.md §A4). */
  .evidence-row{grid-template-columns:1fr;gap:0}
  .evidence-row dd{margin-top:2px}
  /* 130px of label left the value column unusably narrow on a phone (legal pages). */
  /* Family 4's field grid stacks to one column, in the same DOM order — grid-template
     only, no `order` and no `grid-area`, so tab order is untouched (§A4). */
  .legal-form__grid{grid-template-columns:minmax(0,1fr)}
  /* Label above value on a phone — 150px of label left the value unusable. */
  .fact-list{grid-template-columns:1fr;gap:0}
  .fact-list dt{margin-top:var(--s3)}
  .fact-list dt:first-of-type{margin-top:0}
}

/* Footer reflow — its own dedicated breakpoints (brand/mission + Explore/Understand/
   Help & legal): 2 columns 521–800px, 1 column at 520px and below, brand/mission first
   in DOM order either way. The approved shell drives these off a `container` query
   because it renders inside a resizable preview frame; here the footer always spans the
   viewport, so the same two thresholds are plain media queries and no containment
   context has to be created. */
@media (max-width:800px){
  .foot-groups{grid-template-columns:repeat(2,minmax(0,1fr));gap:26px 38px}
}
@media (max-width:520px){
  .foot-groups{grid-template-columns:minmax(0,1fr);gap:26px}
  /* The base line stops being a two-end row and stacks, so neither end truncates. */
  .foot-base{min-height:auto;padding-block:18px 22px;
    flex-direction:column;align-items:flex-start;gap:7px}
}

/* ---- Forced colors: keep the marker's shape legible when fills drop ------ */
@media (forced-colors:active){
  /* The disclosure triangle is the only open/closed cue; let the system colour it
     rather than a token the forced palette has replaced. (The A/G marker's own
     forced-colors rules went with the marker itself.) */
  .disclose summary::marker{color:CanvasText}
  .disclose summary::-webkit-details-marker{color:CanvasText}
}

/* ---- Reduced motion: the foundation ships no motion; guard defensively --- */
@media (prefers-reduced-motion:reduce){
  *{animation-duration:.001ms!important;animation-iteration-count:1!important;transition-duration:.001ms!important}
}

/* ---- Detail evidence record (PR 3B) -------------------------------------
 * Everything here is scoped to the detail page: the evidence roles below use
 * `.evi-h`/`.evi-h3` so the record can carry its own heading scale without moving
 * section headings on the catalogue, the term pages or the document pages.
 * Roles (DESIGN.md §A2): heading 22/28, prose 16/24, labels+values 14/20,
 * secondary notes 14/20, standalone hash mono 14/24. No new sizes beyond those.
 */

/* Local index — a plain anchor list, not a sticky sidebar or a progress rail. */
.page-index{padding:var(--s5);border-top:1px solid var(--line)}
.page-index-h{margin:0 0 var(--s3);font:var(--t-kicker);letter-spacing:.6px;
  text-transform:uppercase;color:var(--muted)}
.page-index-list{list-style:none;margin:0;padding:0;display:flex;flex-wrap:wrap;
  gap:var(--s2) var(--s4)}
/* ≥44px target: the index is a real navigation control, not a caption. */
.page-index-list a{font:var(--t-toc);display:inline-flex;align-items:center;
  min-height:44px}

/* Section headings inside the record. The number is decorative and aria-hidden.
 * Selected as `.evidence-record .evi-h` on purpose: `.sec h2` is (0,1,1) and a bare
 * `.evi-h` is (0,1,0), so the `.sec h2` section style would win on specificity and these
 * headings would silently keep it. Caught in browser QA, not by a test — computed style is
 * exactly what a DOM assertion does not see. The extra class also keeps the override inside
 * the record, so no other page's `.sec h2` moves. */
.evidence-record .evi-h{margin:0 0 var(--s3);font:var(--t-h2-interface);
  color:var(--text);text-transform:none;letter-spacing:0;
  display:flex;align-items:baseline;gap:var(--s3);scroll-margin-top:var(--s5)}
/* Section numbers come from a counter, never from the template: a hand-written number
   has to be re-derived every time a section becomes conditional, and the flagged/no-licence
   states are exactly where the hand-written ones went out of step. The span stays
   `aria-hidden` in the markup, so the generated content is decorative in every engine. */
.evidence-record{counter-reset:evi}
.evidence-record .sec{counter-increment:evi}
.evi-n{font:var(--t-data);color:var(--muted);flex:none}
.evi-n::before{content:counter(evi)}
.evidence-record .evi-h3{margin:var(--s5) 0 var(--s3);font:var(--t-h3);
  color:var(--text)}
.evidence-record .sec{scroll-margin-top:var(--s5)}

/* Prose roles. */
.evi-scope{margin:0 0 var(--s4);font:var(--t-body);color:var(--text);
  max-width:var(--measure)}
.evi-note{margin:var(--s4) 0 0;font:var(--t-meta);color:var(--muted);
  max-width:var(--measure)}
.evi-sub{margin:var(--s3) 0 var(--s2);font:var(--t-meta);color:var(--muted)}
.evidence-list{font:var(--t-meta)}

/* Native disclosure — flat, hairline, no shadow. 44px summary target. */
.disclose{margin-top:var(--s4);border-top:1px solid var(--line)}
/* `display:list-item` (the default) is load-bearing: `display:flex` removes the native
   ::marker, and the triangle IS the open/closed affordance. Padding, not flex centring,
   buys the 44px target — a list-item is a block, so min-height applies. */
.disclose summary{display:list-item;list-style-position:outside;margin-left:1.1em;
  min-height:44px;padding:10px 0;cursor:pointer;
  font:var(--t-body);color:var(--link)}
.disclose summary::marker{color:var(--muted)}
.disclose summary::-webkit-details-marker{color:var(--muted)}
.disclose[open] summary{color:var(--text)}
.disclose-body{padding:0 0 var(--s4);max-width:var(--measure)}
.disclose-body p{font:var(--t-body);margin:0 0 var(--s3)}
.disclose-body .evi-note{font:var(--t-meta)}

/* A standalone hash/fingerprint: mono 14/24, selectable, wraps, never overflows. */
.hash-value{font:var(--t-hash);
  color:var(--text);margin:0 0 var(--s3);overflow-wrap:anywhere;word-break:break-all;
  user-select:text}
.evidence-row dd.hash-value{margin:0}

/* Copy button: ships hidden, revealed by site.js. Status is text, never colour alone. */
.copy-btn{min-height:44px;min-width:44px}
.copy-status{margin:var(--s2) 0 0;font:var(--t-meta);color:var(--muted);
  min-height:20px}

/* Command / full-name lists inside disclosures. */
.cmd-list{list-style:none;margin:0 0 var(--s3);padding:0;display:flex;
  flex-direction:column;gap:var(--s2)}
.cmd-list code{font:var(--t-hash);overflow-wrap:anywhere;
  word-break:break-all;background:var(--surface2);border-radius:var(--radius-xs);
  padding:2px 6px;display:inline-block;max-width:100%}

/* VirusTotal line — state carried by text, with the danger variant also marked. */
.vt-line{margin:0;font:var(--t-body);color:var(--text);max-width:var(--measure)}
.vt-line.danger{color:var(--danger-fg);font-weight:600}

/* Feedback / useful links — real links in a list, never buttons or a dialog. */
.link-row{list-style:none;margin:0;padding:0;display:flex;flex-wrap:wrap;
  gap:var(--s2) var(--s4)}
.link-row a{font:var(--t-body);display:inline-block;min-height:24px;padding:10px 0}

@media (max-width:560px){
  .evidence-row{grid-template-columns:1fr}
}

/* ---- Content typography breakpoint (DESIGN.md §A2.1) ----------------------------
 * The four roles whose size changes on a phone, switched in ONE place at 600px. This
 * is NOT the 800px shell/navigation breakpoint: the wordmark is the single type role
 * that follows the shell instead and keeps its own 800px rule.
 *
 * It lives at the END of the stylesheet on purpose: a media query does not raise
 * specificity, so placed earlier it would lose to the base rules for the document H1
 * and the 404 rail, which are declared further down. Measured: at 601px the document
 * H1 is 34/40 and at 600px it is 28/34.
 */
@media (max-width:600px){
  :root{
    --t-h1-page:600 28px/34px var(--font-sans);
    /* Both document compositions follow the role, so neither is named here. */
    --t-h1-document:600 28px/34px var(--font-sans);
    --t-lede:400 16px/25px var(--font-sans);
  }
  /* Family 6: the rail becomes a top band, in the same DOM order — grid-template only. */
  .service-state{grid-template-columns:1fr;margin-top:12px}
  .service-state__rail{min-height:auto;justify-content:flex-start;padding:20px 22px 12px;
    border-right:0;border-bottom:1px solid var(--border);font:400 22px/28px var(--font-mono)}
  .service-state__body{padding:24px 22px 28px}
  /* Family 5's compact padding follows the CONTENT breakpoint, with its H1 — the receipt is
     a reading surface, not a shell element (DESIGN.md §A6.1). */
  .receipt{padding:var(--s5) var(--s4)}
}
