/* ==========================================================================
   Air Market - design system replicating the macOS App Store reference
   (see "AppStore Images/"). Original content & artwork.
   ========================================================================== */

:root {
  /* ---- Air_DS bridge ----------------------------------------------------
     These were hand-picked hex values sampled from App Store screenshots.
     They are now thin aliases onto tokens.css, so the whole store retones
     from Figma without touching 150-odd call sites. Kept as an alias layer
     rather than find-replaced away: the short names carry store-local
     meaning ("the hairline", "the pill grey") that the token names do not,
     and one indirection is cheaper to read than the fully-qualified token
     at every use. New code should reach for the token directly. */

  /* Apple blue -> Air's near-black primary action. This is the single biggest
     visual shift in the reskin: Air puts brand teal on focus and links, and
     makes actions black. */
  --accent: var(--color-action-primary);
  --accent-hover: var(--color-action-primary-hover);

  /* ---- accessibility deviations from the semantic layer -----------------
     Two of Air's text tokens do not reach WCAG AA on Air's own surfaces.
     Measured against #ffffff (and surface/sunken #f8fafa in brackets):

       text/link      brand/700  #00977a   3.68:1  [3.51]   FAILS 4.5:1
       text/tertiary  neutral/500 #95a3a3  2.61:1  [2.49]   FAILS 4.5:1

     Rather than ship text nobody with low vision can read, the alias layer
     steps each one down the same ramp until it passes, and the store's three
     text levels stay visually distinct:

       --text    neutral/900  18.12:1
       --text-2  neutral/700   7.54:1   (was text/secondary, neutral/600)
       --text-3  neutral/600   4.81:1   (was text/tertiary,  neutral/500)
       --link    brand/800     6.63:1   (was text/link,      brand/700)

     This is a deviation, not a fix. The durable repair is to re-point
     text/tertiary and text/link in Figma, after which these four lines
     collapse back to the plain semantic tokens. */
  --link: var(--color-brand-800);
  --text: var(--color-text-primary);
  --text-2: var(--color-neutral-700);
  --text-3: var(--color-neutral-600);
  --hair: var(--color-border-default);
  --hair-2: var(--color-border-subtle);
  --pill: var(--color-action-secondary);
  --pill-hover: var(--color-action-secondary-hover);
  --sidebar-bg: var(--color-surface-sunken);
  --sidebar-sel: var(--color-action-secondary-hover);
  /* brand/500 is #00dcb0 -- byte-identical to the value the certification
     masks already used, so that artwork keeps working untouched. */
  --air-teal: var(--color-brand-500);
  --content-bg: var(--color-surface-default);
  --card-r: var(--radius-lg);
  --card-surface: var(--color-surface-sunken);
  --font: var(--font-main), -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
    Helvetica, Arial, sans-serif;
  --font-mono-stack: var(--font-mono), ui-monospace, SFMono-Regular, Menlo, monospace;

  /* ---- store-local layout, no Figma equivalent ---- */
  --sidebar-width: 232px;
  --sidebar-width-icon: 56px;
  --sidebar-ease: 200ms ease-linear;
  /* Single source of truth for sidebar width; the rail follows it. State lives
     on <html> so the persisted value can be applied before first paint. */
  --sidebar-w: var(--sidebar-width);
  /* App-icon squircle. Deliberately not radius-lg: these are product icons
     imitating a rounded-square app mark, so the radius scales with the icon. */
  --icon-r: 22.5%;
}

:root[data-sidebar="collapsed"] { --sidebar-w: var(--sidebar-width-icon); }
:root[data-sidebar="collapsed"][data-collapsible="offcanvas"] { --sidebar-w: 0px; }

* { margin: 0; padding: 0; box-sizing: border-box; }

html, body {
  height: 100%;
  font-family: var(--font);
  color: var(--text);
  background: var(--color-neutral-100);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

button { font-family: var(--font); border: 0; background: none; cursor: var(--cur-pointer); }
a { color: var(--link); text-decoration: none; cursor: var(--cur-pointer); }
a:hover { text-decoration: underline; }
img, svg { display: block; }

/* ---------------- Window shell ---------------- */

.window {
  position: relative;
  display: flex;
  height: 100vh;
  overflow: hidden;
  background: var(--content-bg);
}

@media (min-width: 1500px) {
  body { padding: 24px; }
  .window {
    height: calc(100vh - 48px);
    max-width: 1760px;
    margin: 0 auto;
    border-radius: 12px;
    box-shadow: 0 25px 70px rgba(0, 0, 0, 0.35), 0 0 0 0.5px rgba(0,0,0,.25);
  }
}

/* ---------------- Sidebar ---------------- */

.sidebar {
  position: relative;
  width: var(--sidebar-w);
  flex: 0 0 var(--sidebar-w);
  min-width: 0;
  background: var(--sidebar-bg);
  border-right: 1px solid var(--hair-2);
  display: flex;
  flex-direction: column;
  padding: 12px 14px;
  user-select: none;
  transition: width var(--sidebar-ease), flex-basis var(--sidebar-ease),
    padding var(--sidebar-ease);
}
/* Icon mode keeps a rail of glyphs; offcanvas slides the whole panel away. */
:root[data-sidebar="collapsed"] .sidebar { padding-left: 10px; padding-right: 10px; }
:root[data-sidebar="collapsed"][data-collapsible="offcanvas"] .sidebar {
  padding-left: 0; padding-right: 0;
  overflow: hidden;
  border-right-color: transparent;
}

.side-head { display: flex; align-items: center; gap: 8px; padding: 6px 4px 16px; }
:root[data-sidebar="collapsed"] .side-head { justify-content: center; padding: 6px 0 14px; }

.sidebar-trigger {
  margin-left: auto; flex: none;
  width: 24px; height: 24px;
  display: grid; place-items: center;
  border-radius: 6px;
  color: var(--text-2);
  transition: background 120ms, color 120ms;
}
.sidebar-trigger:hover { background: rgba(0,0,0,.06); color: var(--text); }
.sidebar-trigger:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.sidebar-trigger svg { width: 16px; height: 16px; }
:root[data-sidebar="collapsed"] .sidebar-trigger { margin-left: 0; }

/* Rail: thin hit strip on the sidebar's edge, click-to-toggle (shadcn SidebarRail). */
.sidebar-rail {
  position: absolute; top: 0; z-index: 20;
  left: max(0px, calc(var(--sidebar-w) - 8px));
  width: 16px; height: 100%;
  background: none;
  cursor: var(--cur-move);
  transition: left var(--sidebar-ease);
}
/* Collapsed and expanded both use Move; Air has no directional resize pair. */
.sidebar-rail::after {
  content: ""; position: absolute; top: 0; left: 50%;
  width: 2px; height: 100%;
  transform: translateX(-50%);
  background: var(--accent);
  opacity: 0;
  transition: opacity 150ms;
}
.sidebar-rail:hover::after, .sidebar-rail:focus-visible::after { opacity: .45; }
/* Nothing else hints at the sidebar once it is fully offcanvas, so show the rail. */
:root[data-sidebar="collapsed"][data-collapsible="offcanvas"] .sidebar-rail::after { opacity: .18; }

.traffic { display: flex; gap: 8px; }
:root[data-sidebar="collapsed"] .traffic { display: none; }
.traffic span { width: 12px; height: 12px; border-radius: 50%; }
/* Deliberately NOT tokenised. These are macOS window controls - OS chrome the
   mock is sitting inside, not Air surface. Recolouring them to brand would make
   the window read as a rendering of a window rather than a window. */
.traffic .r { background: #ff5f57; border: 0.5px solid rgba(0,0,0,.15); }
.traffic .y { background: #febc2e; border: 0.5px solid rgba(0,0,0,.15); }
.traffic .g { background: #28c840; border: 0.5px solid rgba(0,0,0,.15); }

.side-search {
  display: flex; align-items: center; gap: 6px;
  /* Clicking anywhere in the box focuses the input, so the whole box gets the
     I-beam - not just the input, which would leave the padding showing an
     arrow over a click target that does accept text. The collapsed rule below
     overrides this with the pointer, where the box is a button instead. */
  cursor: var(--cur-text);
  background: rgba(0,0,0,.055);
  border: 0.5px solid rgba(0,0,0,.06);
  border-radius: 7px;
  padding: 5px 8px;
  margin-bottom: 18px;
}
.side-search svg { width: 12px; height: 12px; opacity: .45; flex: none; }
.side-search input {
  border: 0; background: none; outline: none; width: 100%;
  font-size: 13px; color: var(--text);
}
.side-search input::placeholder { color: var(--text-3); }
/* Collapsed: the field becomes an icon button that reopens the sidebar. */
:root[data-sidebar="collapsed"] .side-search {
  width: 34px; height: 28px;
  margin: 0 auto 14px;
  padding: 0;
  justify-content: center;
  cursor: var(--cur-pointer);
}
:root[data-sidebar="collapsed"] .side-search input { display: none; }
:root[data-sidebar="collapsed"] .side-search:hover { background: rgba(0,0,0,.085); }

.nav { display: flex; flex-direction: column; gap: 1px; }
/* Air Layout SideMenu item: 40px row, radius md, 18px icon + 8px gap.
   Selected is a solid near-black pill with inverse text - the shell's single
   loudest affordance, and the clearest signal that this is Air and not the
   App Store, whose selection is a faint grey wash. */
.nav-item {
  position: relative;
  display: flex; align-items: center; gap: var(--space-8);
  height: 40px;
  padding: 0 var(--space-8);
  border-radius: var(--radius-md);
  font-size: 13px;
  color: var(--text);
  text-align: left;
  transition: background-color 120ms ease, color 120ms ease;
}
.nav-item:hover { background: var(--color-action-secondary-hover); }
.nav-item.sel { background: var(--color-action-primary); color: var(--color-text-inverse); }
.nav-item.sel:hover { background: var(--color-action-primary-hover); }
.nav-item:focus-visible { outline: 2px solid var(--color-border-focus); outline-offset: -1px; }
.nav-item .glyph { width: 18px; height: 18px; flex: none; display: grid; place-items: center; }
.nav-item .glyph svg { width: 18px; height: 18px; }
.nav-item .badge {
  margin-left: auto; font-size: 12px; color: var(--text-2);
}
/* Inside the selected pill the count has to lift off near-black. */
.nav-item.sel .badge { color: var(--color-neutral-400); }
.nav-item .badge-dot { display: none; }
:root[data-sidebar="collapsed"] .nav-item {
  padding: 6px; gap: 0;
  justify-content: center;
}
:root[data-sidebar="collapsed"] .nav-label,
:root[data-sidebar="collapsed"] .nav-item .badge { display: none; }
/* Badge count has no room at icon width - keep the signal as a dot. */
:root[data-sidebar="collapsed"] .nav-item .badge-dot {
  display: block; position: absolute; top: 4px; right: 5px;
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 0 1.5px var(--sidebar-bg);
}

/* Tooltips are the Air_DS Tooltip (components.css). The sidebar opts in only
   while collapsed, where the tip stands in for the hidden label; expanded, the
   label is right there and a tip would just be noise. */
:root[data-sidebar="collapsed"] [data-tip]:hover::after,
:root[data-sidebar="collapsed"] [data-tip]:focus-visible::after {
  opacity: 1; visibility: visible; transition-delay: 250ms;
}
:root[data-sidebar="collapsed"] .side-search,
:root[data-sidebar="collapsed"] .side-footer { position: relative; }

.side-footer {
  margin-top: auto;
  display: flex; align-items: center; gap: 8px;
  padding: 6px 4px;
  font-size: 13px;
}
:root[data-sidebar="collapsed"] .side-footer { justify-content: center; padding: 6px 0; }
:root[data-sidebar="collapsed"] .side-footer .who { display: none; }

/* The account row, in both states.

   The footer renders from the session (V8-BUILD-PLAN.md 0.A.1), and the first
   version of that made signed-out a bare text button - so the sidebar lost the
   avatar anchor v5/v6 composed it around, for every visitor who has not signed
   in. Both states are the same control now: avatar, then a line, so the
   collapsed rail narrows to the same 32px circle either way. */
.side-footer .side-account {
  display: flex; align-items: center; gap: 8px;
  flex: 1; min-width: 0;
  border: 0;
  background: none;
  padding: 2px;
  border-radius: var(--radius-md, 8px);
  font: inherit;
  color: var(--color-text-primary);
  text-decoration: none;
  cursor: pointer;
  text-align: left;
}
.side-footer .side-account:hover { background: var(--color-surface-sunken); }
.side-footer .side-account:focus-visible { outline: 2px solid var(--color-border-focus); outline-offset: 2px; }
.side-footer .side-account .who {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* A visitor with no account has no initials, so the fallback is a glyph rather
   than a letter picked out of an email they have not given us. */
.side-footer .avatar--empty { color: var(--color-text-tertiary); }
.side-footer .avatar--empty svg { width: 18px; height: 18px; }
.side-footer .side-signout {
  border: 0;
  background: none;
  padding: 2px 4px;
  border-radius: var(--radius-sm, 4px);
  font: inherit;
  font-size: 12px;
  color: var(--color-text-secondary);
  cursor: pointer;
  flex: none;
}
.side-footer .side-signout:hover { color: var(--color-text-primary); text-decoration: underline; }
.side-footer .side-signout:focus-visible { outline: 2px solid var(--color-border-focus); outline-offset: 2px; }
/* Collapsed: the circle only, in both states. */
:root[data-sidebar="collapsed"] .side-footer .side-signout { display: none; }
:root[data-sidebar="collapsed"] .side-footer .side-account { justify-content: center; flex: none; }
:root[data-sidebar="collapsed"] .side-footer .side-signout { display: none; }

@media (prefers-reduced-motion: reduce) {
  .sidebar, .sidebar-rail { transition: none; }
}
/* Geometry, radius and type now come from the Air_DS Avatar component in
   components.css. Only the identity tint is store-local: a flat surface/sunken
   fallback disappears against the sidebar, which is the same colour. */
.side-footer .avatar {
  background: linear-gradient(135deg, var(--color-data-3), var(--color-data-4));
  color: var(--color-text-inverse);
  font-weight: 600;
}

/* ---------------- Content area ---------------- */

.content {
  position: relative;
  flex: 1;
  overflow-y: auto;
  overflow-x: hidden;
  background: var(--content-bg);
  scroll-behavior: smooth;
}

.page {
  max-width: 1100px;
  margin: 0 auto;
  padding: 28px 36px 60px;
}

.page-title {
  font-size: 28px;
  font-weight: 700;
  letter-spacing: 0.2px;
  margin-bottom: 18px;
}

/* Floating chrome buttons (detail/story pages) */
.chrome-bar {
  position: sticky;
  top: 0;
  z-index: 40;
  height: 52px;
  display: flex; align-items: center;
  padding: 0 16px;
  pointer-events: none;
}
.chrome-bar.scrolled {
  background: rgba(255,255,255,.82);
  backdrop-filter: saturate(180%) blur(20px);
  border-bottom: 1px solid var(--hair-2);
}
.chrome-btn {
  pointer-events: auto;
  width: 30px; height: 30px;
  border-radius: 50%;
  background: rgba(255,255,255,.9);
  box-shadow: 0 1px 4px rgba(0,0,0,.18);
  display: grid; place-items: center;
  color: var(--text-2);
}
.chrome-bar.scrolled .chrome-btn { box-shadow: none; background: none; color: var(--link); }
.chrome-btn svg { width: 14px; height: 14px; }
.chrome-title {
  flex: 1; text-align: center;
  font-size: 13px; font-weight: 600;
  display: flex; align-items: center; justify-content: center; gap: 7px;
  opacity: 0;
  transition: opacity .15s;
}
.chrome-bar.scrolled .chrome-title { opacity: 1; }
.chrome-title .mini-icon { width: 20px; height: 20px; border-radius: 5px; }
.chrome-cta { pointer-events: auto; opacity: 0; transition: opacity .15s; }
.chrome-bar.scrolled .chrome-cta { opacity: 1; }

/* ---------------- Section headers ---------------- */

.section { margin-top: 34px; }
.section-head {
  display: flex; align-items: baseline; justify-content: space-between;
  border-top: 1px solid var(--hair);
  padding-top: 14px;
  margin-bottom: 14px;
}
.section-head.no-rule { border-top: 0; padding-top: 0; }
.section-head h2 { font-size: 21px; font-weight: 700; letter-spacing: .1px; }
.section-head .see-all { font-size: 14px; }

.eyebrow {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .6px;
  text-transform: uppercase;
  color: var(--text-3);
}
.eyebrow.blue { color: var(--link); }

/* ---------------- Hero carousel (Discover) ---------------- */

.hero-wrap { position: relative; }
.hero-carousel {
  display: flex;
  gap: 14px;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
  margin: 0 -36px;
  padding: 0 36px;
}
.hero-carousel::-webkit-scrollbar { display: none; }

.hero-slide {
  position: relative;
  flex: 0 0 100%;
  scroll-snap-align: center;
  height: 264px;
  border-radius: var(--card-r);
  overflow: hidden;
  display: flex;
  color: var(--color-text-inverse);
  cursor: var(--cur-pointer);
}
.hero-copy {
  position: relative; z-index: 2;
  width: 46%;
  padding: 26px 30px;
  display: flex; flex-direction: column;
  background: linear-gradient(90deg, rgba(0,0,0,.38), rgba(0,0,0,0));
}
.hero-copy .eyebrow { color: rgba(255,255,255,.75); margin-bottom: 6px; }
.hero-copy h3 { font-size: 27px; font-weight: 700; line-height: 1.15; }
.hero-copy p { font-size: 13px; margin-top: 8px; color: rgba(255,255,255,.85); max-width: 300px; }
.hero-copy .foot { margin-top: auto; font-size: 12px; color: rgba(255,255,255,.72); }
.hero-art { position: absolute; inset: 0; }

.hero-nav {
  position: absolute; top: 50%; transform: translateY(-50%);
  width: 30px; height: 56px;
  display: grid; place-items: center;
  color: rgba(0,0,0,.35);
  z-index: 5;
}
.hero-nav svg { width: 22px; height: 22px; }
.hero-nav.prev { left: -34px; }
.hero-nav.next { right: -34px; }

/* ---------------- Promo triplet (circle art cards) ---------------- */

.promo3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; margin-top: 14px; }
.promo-card {
  border: 1px solid var(--hair-2);
  background: var(--color-surface-sunken);
  border-radius: var(--card-r);
  padding: 20px 22px;
  display: flex; align-items: center; gap: 16px;
  min-height: 148px;
  cursor: var(--cur-pointer);
}
.promo-card:hover { background: var(--color-action-secondary-hover); }
.promo-copy { flex: 1; }
.promo-copy .eyebrow { margin-bottom: 4px; }
.promo-copy h3 { font-size: 19px; font-weight: 700; line-height: 1.2; }
.promo-copy p { font-size: 12.5px; color: var(--text-2); margin-top: 5px; }
.promo-circle {
  width: 108px; height: 108px; flex: none;
  border-radius: 50%;
  overflow: hidden;
}

/* ---------------- App rows (5-col grids) ---------------- */

.grid-5 { display: grid; grid-template-columns: repeat(5, 1fr); column-gap: 22px; }
.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); column-gap: 22px; }

.app-row {
  display: flex; align-items: center; gap: 8px;
  padding: 11px 0;
  border-bottom: 1px solid var(--hair-2);
  min-width: 0;
  cursor: var(--cur-pointer);
}
.app-row.no-line { border-bottom: 0; }
.app-row .rank {
  font-size: 16px; color: var(--text);
  min-width: 13px; text-align: left;
}
.app-icon {
  border-radius: var(--icon-r);
  flex: none;
  box-shadow: inset 0 0 0 0.5px rgba(0,0,0,.08);
  overflow: hidden;
}
.app-row .app-icon { width: 44px; height: 44px; }
.app-row .meta { flex: 1; min-width: 0; }
.app-row .meta .over {
  font-size: 10px; color: var(--text-3); letter-spacing: .2px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.app-row .meta .name {
  font-size: 13px; font-weight: 500; line-height: 1.25;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}
.app-row .meta .sub {
  font-size: 11.5px; color: var(--text-2);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  margin-top: 1px;
}
.get-wrap { text-align: center; flex: none; }
/* Air_DS Button, Size=Small: h24, px8, radius sm (6px), 13px Manrope Medium.
   The App Store's fully-round 20px pill is gone - Air buttons are squared off.
   Free listings use Secondary (grey), priced ones Primary (near-black); Air has
   no blue, and the black/grey split carries the same free-vs-paid distinction
   the blue/grey split used to. */
.get-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 24px;
  padding: 0 var(--space-8);
  min-width: 54px;
  border-radius: var(--radius-sm);
  font-family: var(--font);
  font-size: 13px;
  font-weight: 500;
  color: var(--text);
  background: var(--color-action-secondary);
  white-space: nowrap;
  transition: background-color 120ms ease;
}
.get-btn:hover { background: var(--color-action-secondary-hover); }
.get-btn:active { background: var(--color-action-secondary-active); }
.get-btn:focus-visible { outline: none; box-shadow: inset 0 0 0 1px var(--color-border-focus); }
/* Filled variant. Named for the emphasis, not for a price - it was `.price` back
   when a priced listing was the only thing that earned a filled pill. */
.get-btn.primary { background: var(--color-action-primary); color: var(--color-text-inverse); }
.get-btn.primary:hover { background: var(--color-action-primary-hover); }
.get-btn.primary:active { background: var(--color-action-primary-active); }
.iap {
  font-size: 11px; color: var(--text-3);
  margin-top: 3px; line-height: 1.2;
}

/* Large list page (See All) */
.biglist .app-row { padding: 16px 0; }
.biglist .app-row .app-icon { width: 56px; height: 56px; }

/* ---------------- Editorial cards ---------------- */

.ed-grid-5 { display: grid; grid-template-columns: repeat(5, 1fr); gap: 20px; }
.ed-grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; }

.ed-card { cursor: var(--cur-pointer); min-width: 0; }
.ed-card .art {
  border-radius: var(--card-r);
  overflow: hidden;
  aspect-ratio: 16 / 10;
  margin-bottom: 10px;
  position: relative;
}
.ed-card:hover .art { filter: brightness(.97); }
.ed-card .eyebrow { margin-bottom: 2px; }
.ed-card h4 { font-size: 15px; font-weight: 600; line-height: 1.25; }
.ed-card p {
  font-size: 12.5px; color: var(--text-2); margin-top: 3px;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}

/* 3-up page-top editorial heroes (Create/Work/Develop style) */
.hero3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; }
.hero3 .cell { cursor: var(--cur-pointer); }
.hero3 .eyebrow { margin-bottom: 2px; }
.hero3 h3 { font-size: 17px; font-weight: 600; line-height: 1.25; }
.hero3 p { font-size: 12.5px; color: var(--text-2); margin: 3px 0 10px; }
.hero3 .art {
  border-radius: var(--card-r); overflow: hidden; aspect-ratio: 16/10.5; position: relative;
}
.hero3 .cell:hover .art { filter: brightness(.97); }

/* Video card */
.play-overlay {
  position: absolute; inset: 0;
  display: grid; place-items: center;
}
.play-overlay .pbtn {
  width: 54px; height: 54px; border-radius: 50%;
  background: rgba(120,120,128,.55);
  backdrop-filter: blur(4px);
  display: grid; place-items: center;
}
.play-overlay .pbtn svg { width: 20px; height: 20px; fill: var(--color-text-inverse); margin-left: 3px; }

/* ---------------- Dark hero (Agents page, Arcade-style) ---------------- */

.dark-hero {
  position: relative;
  height: 480px;
  overflow: hidden;
  color: var(--color-text-inverse);
  display: flex;
  align-items: flex-end;
  justify-content: center;
}
.dark-hero .inner {
  position: relative; z-index: 2;
  text-align: center;
  padding-bottom: 34px;
  display: flex; flex-direction: column; align-items: center; gap: 12px;
}
.dark-hero .logo {
  display: flex; align-items: center; gap: 7px;
  font-size: 15px; font-weight: 700;
}
.dark-hero .logo svg { width: 16px; height: 16px; }
.dark-hero h2 { font-size: 30px; font-weight: 700; letter-spacing: .2px; }
.dark-hero .cta {
  background: var(--accent); color: var(--color-text-inverse);
  font-size: 13px; font-weight: 600;
  padding: 8px 18px; border-radius: 20px;
}
.dark-hero .cta:hover { background: var(--color-action-primary-hover); }
.dark-hero .fine { font-size: 10.5px; color: rgba(255,255,255,.6); }

/* Chips row */
/* The category filter row is an Air_DS Tabs (Style=Default) - see components.css.
   Only the horizontal-scroll behaviour is store-local; Air's Tabs has no scroll
   affordance because its own screens never overflow. */
.chips {
  margin: 16px 0 2px;
  overflow-x: auto; scrollbar-width: none;
  max-width: 100%;
}
.chips::-webkit-scrollbar { display: none; }
/* The decorative per-category emoji survives the swap as a pseudo-element fed
   by data-dot, rather than an extra span the Tabs component has no slot for. */
.chips .tab[data-dot]::before { content: attr(data-dot); font-size: 13px; }

/* Giant editorial showcase (Welcome to Arcade style) */
.showcase {
  position: relative;
  border-radius: var(--card-r);
  overflow: hidden;
  height: 520px;
  margin-top: 22px;
  color: var(--color-text-inverse);
  cursor: var(--cur-pointer);
}
.showcase .big-title {
  position: absolute; top: 34px; left: 34px;
  font-size: 34px; font-weight: 700; line-height: 1.1;
  text-shadow: 0 2px 18px rgba(0,0,0,.25);
  z-index: 2;
}
.showcase .glass {
  position: absolute; left: 34px; bottom: 34px; z-index: 2;
  background: rgba(40,40,40,.55);
  backdrop-filter: blur(14px);
  border-radius: 10px;
  padding: 14px 16px;
  max-width: 300px;
}
.showcase .glass .eyebrow { color: rgba(255,255,255,.7); margin-bottom: 3px; }
.showcase .glass h3 { font-size: 17px; font-weight: 700; }
.showcase .glass p { font-size: 12.5px; color: rgba(255,255,255,.85); margin-top: 4px; }
.showcase .glass .view { display: inline-block; margin-top: 8px; font-size: 12.5px; color: var(--color-text-inverse); font-weight: 500; }

/* Icon cloud footer card (Arcade bottom) */
.icon-cloud {
  border-radius: var(--card-r);
  background: var(--card-surface);
  margin-top: 22px;
  padding: 40px 20px 30px;
  text-align: center;
  position: relative;
  overflow: hidden;
}
.icon-cloud .cloud { display: flex; justify-content: center; flex-wrap: wrap; gap: 26px; margin-bottom: 28px; }
.icon-cloud .cloud .app-icon { width: 44px; height: 44px; }
.icon-cloud .cloud .app-icon:nth-child(odd) { transform: translateY(14px); opacity: .9; }
.icon-cloud .brand { font-size: 13px; font-weight: 700; margin-bottom: 12px; }
.icon-cloud .see {
  display: inline-block;
  background: var(--color-text-inverse); color: var(--link);
  border-radius: 18px; padding: 7px 16px;
  font-size: 13px; font-weight: 500;
  box-shadow: 0 1px 3px rgba(0,0,0,.08);
}

/* ---------------- Categories page ---------------- */

.cat-grid {
  display: grid; grid-template-columns: repeat(5, 1fr);
  column-gap: 22px;
}
.cat-tile {
  display: flex; align-items: center; gap: 10px;
  padding: 13px 2px;
  border-bottom: 1px solid var(--hair-2);
  font-size: 13.5px; font-weight: 500;
  color: var(--text);
  cursor: var(--cur-pointer);
  min-width: 0;
}
.cat-tile:hover { color: var(--link); }
.cat-tile .ci {
  width: 26px; height: 26px; border-radius: 6px;
  display: grid; place-items: center;
  font-size: 14px; flex: none;
  color: var(--color-text-inverse);
}

/* ---------------- Detail page ---------------- */

.detail-head {
  display: flex; gap: 22px; align-items: flex-start;
  padding: 6px 0 22px;
}
.detail-head .app-icon { width: 116px; height: 116px; border-radius: 24px; }
.detail-head h1 { font-size: 26px; font-weight: 700; line-height: 1.15; }
.detail-head .dev { font-size: 15px; color: var(--text-2); margin-top: 3px; }
.detail-head .buy-row { display: flex; align-items: center; gap: 10px; margin-top: 16px; }
.detail-head .get-btn { font-size: 13px; padding: 5px 18px; }
.detail-head .iap { font-size: 9px; text-align: left; margin: 0 0 0 2px; }

.stats-bar {
  display: flex;
  border-top: 1px solid var(--hair);
  border-bottom: 1px solid var(--hair);
  padding: 14px 0;
  margin-bottom: 24px;
}
.stat {
  flex: 1;
  text-align: center;
  padding: 2px 10px;
  border-right: 1px solid var(--hair-2);
  min-width: 0;
}
.stat:last-child { border-right: 0; }
.stat .lbl {
  font-size: 10px; font-weight: 600; letter-spacing: .6px;
  text-transform: uppercase; color: var(--text-3);
  margin-bottom: 5px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* The value row is height-locked to its tallest occupant so that every tile's
   third line lands on one baseline. Left to itself the row measures whatever
   it happens to contain - 30px for the AIR shield, ~23px for 19px text, 18px
   for a bare glyph - and the bar, whose whole claim is that six cells are
   readings off the same instrument, puts its subs on three different heights.
   The row is already a centred flex line, so short values float mid-band and
   nothing but the sub moves. Locking the row (rather than shrinking the
   shield, or bottom-anchoring the subs) keeps the shield's deliberate size
   below and leaves the MIDDLE line steady too. */
.stat .val {
  font-size: 19px; font-weight: 600; color: var(--text-3);
  display: flex; align-items: center; justify-content: center; gap: 5px;
  min-height: 30px;
  white-space: nowrap;
}
/* A glyph value is the whole reading, not an icon next to one - the Creator
   and plain-shield cells put nothing else on the line. At the 18px used for
   icons that sit BESIDE text it read as a footnote beside its 19px semibold
   neighbours; 24px gives it their weight and still sits inside the 30px band
   below, so no bar grows. The AIR shield keeps its own larger size - it is a
   badge, and the rule under this one is more specific. */
.stat .val svg { width: 24px; height: 24px; }
/* The AIR-scanned Security tile's shield carries the AIR mark inside it, so
   it needs to read at a glance rather than at icon size - bigger than the
   plain glyph row it replaces. */
.stat .val .air-shield-mark { width: 30px; height: 30px; }
.stat .sub { font-size: 11px; color: var(--text-3); margin-top: 4px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* The stars row IS a sub - it carries the class now, so it inherits the size,
   colour, 4px offset and ellipsis rules every other third line has instead of
   restating two of them and missing the rest. All that is left star-only is
   the letter-spacing, which the glyphs need and prose does not. */
.stat .stars { letter-spacing: 1px; }

.shots-row {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px;
  margin-bottom: 12px;
}
.shot {
  border-radius: 10px; overflow: hidden;
  border: 1px solid var(--hair-2);
  aspect-ratio: 16/10;
  cursor: var(--cur-pointer);
  position: relative;
}
/* The tiles are role="button" - a keyboard user needs to see which one is
   about to open. */
.shot:focus-visible { outline: 2px solid var(--color-brand-800); outline-offset: 2px; }

/* Publisher gallery (REDESIGN §14) - the block heading, the demo tile's
   duration chip, and the "+N" count for tiles the 3-up row can't show. A
   hidden asset must be visibly hidden. */
.media-block { margin-bottom: 6px; }
.media-head { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; margin-bottom: 12px; }
.media-head h3 { font-size: 18px; font-weight: 700; letter-spacing: .1px; }
.shot-dur, .shot-more, .shot-vids {
  position: absolute; z-index: 1;
  font-size: 11px; font-weight: 600; letter-spacing: .2px;
  color: var(--color-text-inverse);
  background: rgba(0,0,0,.72);
  padding: 2px 7px; border-radius: 6px;
  font-variant-numeric: tabular-nums;
}
.shot-dur { right: 8px; bottom: 8px; }
.shot-more { right: 8px; top: 8px; border-radius: 999px; padding: 3px 9px; }
.shot-vids { left: 8px; bottom: 8px; }
.platform-note {
  display: flex; align-items: center; gap: 7px;
  font-size: 11.5px; color: var(--text-3);
  padding: 6px 0 18px;
  border-bottom: 1px solid var(--hair);
  margin-bottom: 18px;
}

.desc-grid { display: grid; grid-template-columns: 1fr 200px; gap: 30px; padding-bottom: 22px; }
.desc-grid .desc { font-size: 13px; line-height: 1.5; color: var(--text); }
.desc-grid .desc p + p { margin-top: 10px; }
/* The collapsed description. Six lines is enough to tell whether the rest is
   worth opening and short enough that the page below it stays reachable. The
   class is added by clampDesc() (assets/js/app.js) only when the text actually
   overflows, so a short description never renders clamped and never renders a
   toggle. */
.desc-grid .desc-body.clamped {
  display: -webkit-box;
  -webkit-line-clamp: 6;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.desc-grid .desc .desc-toggle { margin-top: 6px; }
/* A button, because it does something. Styled as the link it used to pretend
   to be, with the focus ring the <a href="#"> version never had. */
.desc-grid .desc .more {
  color: var(--link); font-size: 12.5px;
  background: none; border: 0; padding: 0;
  font-family: inherit; cursor: pointer;
}
.desc-grid .desc .more:hover { text-decoration: underline; }
.desc-grid .desc .more:focus-visible { outline: 2px solid var(--link); outline-offset: 2px; border-radius: 2px; }
.dev-links { font-size: 13px; text-align: right; display: flex; flex-direction: column; gap: 8px; }
.dev-links a { display: inline-flex; align-items: center; gap: 5px; justify-content: flex-end; }
.dev-links .who { color: var(--link); }

/* Ratings */
.ratings-wrap { display: flex; gap: 40px; align-items: flex-start; padding: 6px 0 20px; }
.big-score { text-align: left; }
.big-score .n { font-size: 56px; font-weight: 700; color: var(--color-text-secondary); line-height: 1; }
.big-score .of { font-size: 14px; font-weight: 600; color: var(--text-3); }
.histo { flex: 1; max-width: 400px; padding-top: 6px; }
.histo .hrow { display: flex; align-items: center; gap: 8px; margin-bottom: 3px; }
.histo .hstars { font-size: 8px; letter-spacing: 1px; color: var(--text-3); width: 56px; text-align: right; }
.histo .bar { flex: 1; height: 3px; background: var(--color-neutral-200); border-radius: 2px; overflow: hidden; }
.histo .bar i { display: block; height: 100%; background: var(--text-3); border-radius: 2px; }
.histo .count { font-size: 12px; color: var(--text-3); margin-top: 8px; text-align: right; }

.review-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }
.review-card {
  background: var(--card-surface);
  border-radius: var(--card-r);
  padding: 15px 17px;
  font-size: 12.5px;
}
.review-card .rhead { display: flex; justify-content: space-between; gap: 8px; }
/* Takes the width the date does not. Without this the headline sizes to its
   own content and then shrinks, so a long one - and an imported review's
   headline is the post's title, which nobody wrote to fit here - wraps into a
   narrow column beside empty space. */
.review-card .rtitle { font-weight: 600; flex: 1 1 auto; min-width: 0; }
.review-card .rdate { color: var(--text-3); flex: none; }
.review-card .rmeta { display: flex; justify-content: space-between; margin: 4px 0 8px; }
.review-card .rstars { color: var(--color-warning-700); font-size: 11px; letter-spacing: 1px; }
.review-card .rby { color: var(--text-3); }
.review-card .rbody { color: var(--text); line-height: 1.45; }
/* The creator's reply, attached to the review it answers rather than floating
   as a separate card, because a reply that reads as its own review is a second
   voice pretending to be a customer. */
.review-card .review-reply {
  margin-top: 10px; padding-top: 10px;
  border-top: 1px solid var(--hair-2);
  display: grid; gap: 3px;
}
.review-card .review-reply b { font-size: 11.5px; color: var(--text-2); }
.review-card .review-reply span { color: var(--text-2); line-height: 1.45; }

/* The way to the reviews that did not fit on the first page. Centred under the
   grid rather than tucked beside the average, because it belongs to the list
   it extends. */
.reviews-more { display: flex; justify-content: center; margin-top: 16px; }
.reviews-more .get-btn[disabled] { opacity: .6; cursor: default; }
/* The count line is focusable only as a target for focus when the button it
   sits with is removed (assets/js/app.js refreshMeta), so it must not draw a
   ring in ordinary reading. */
.ratings-plain .reviews-count:focus { outline: none; }
.ratings-plain .reviews-count:focus-visible { outline: 2px solid var(--link); outline-offset: 2px; }

/* Reviews arrive in a second request (assets/js/app.js paintReviews). This is
   what the slot says while that is in flight, and what it says if the request
   fails - never a star and never a count, because either would be a claim the
   page has not confirmed. */
.reviews-loading { font-size: 12.5px; color: var(--text-3); margin: 6px 0 0; }

/* An unknown value in the Information grid. Styled as its own state rather than
   left to look like a value, so a reader can tell at a glance which cells are
   facts and which are gaps - and so removing the cell never becomes the tidier
   looking option again. */
.info-cell .v--unknown { color: var(--text-3); font-style: italic; }

/* The label on a description we copied rather than wrote. Quiet: it explains the
   paragraph under it, it is not a warning about the listing. */
.desc .desc-src {
  margin: 0 0 12px;
  padding: 10px 12px;
  border-left: 2px solid var(--hair-2);
  background: var(--card-surface);
  border-radius: 0 8px 8px 0;
}
.desc .desc-src h4 {
  margin: 0 0 4px;
  font-size: 11.5px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-3);
}
.desc .desc-src p { margin: 0; font-size: 12.5px; line-height: 1.5; color: var(--text-2); }

/* The disclosure moved out of the hero and down here, beside the creator and the
   repository (assets/js/app.js), and is fine print rather than a panel: the
   component is `.v6-claim-fine` (assets/css/store-v6-creator.css). It sits
   directly under the description grid, tight against it, because it is a
   footnote to the creator line above it and not a section of its own. */
/* Scoped to the v6 flag so it outranks the component's own margins in
   store-v6-creator.css, which load after this file. */
:root[data-v6="on"] .desc-grid + .v6-claim-fine { margin: -6px 0 12px; }

/* The review form. The two text fields are UI.field, so only the star row and
   the error slot need rules here - the rest is the component's. */
.rev-form { display: flex; flex-direction: column; gap: 10px; text-align: left; }
.rev-stars { display: flex; align-items: center; gap: 4px; border: 0; padding: 0; margin: 0; }
.rev-star {
  background: none; border: 0; cursor: pointer; padding: 2px;
  font-size: 24px; line-height: 1; color: var(--color-warning-700);
}
.rev-star:focus-visible { outline: 2px solid var(--color-info-600); outline-offset: 2px; border-radius: 4px; }
.rev-stars-say { font-size: 12px; color: var(--text-3); margin-left: 8px; }
.rev-err:not(:empty) {
  font-size: 12.5px;
  color: var(--color-critical-700, #b42318);
  background: var(--color-critical-50, #fef3f2);
  border-radius: 6px; padding: 8px 10px;
}

/* The tier note used to have its own block here, then a `.v6-claim-note` box.
   It is `.v6-claim-fine` now - the fine-print line the unclaimed creator page
   uses to say the same sentence for the same reason
   (assets/css/store-v6-creator.css). */

/* Editors' notes card */
.note-card {
  background: var(--card-surface); border-radius: var(--card-r);
  padding: 16px 18px; font-size: 12.5px; line-height: 1.5;
  max-width: 460px;
}
.note-card h5 { font-size: 12.5px; margin-bottom: 6px; }

/* Privacy / permissions */
.privacy-card {
  background: var(--card-surface);
  border-radius: var(--card-r);
  padding: 22px;
  text-align: center;
  max-width: 460px;
}
.privacy-card .shield { width: 34px; height: 34px; margin: 0 auto 10px; color: var(--accent); }
.privacy-card .shield svg { width: 34px; height: 34px; }
.privacy-card h5 { font-size: 13.5px; margin-bottom: 5px; }
.privacy-card p { font-size: 12px; color: var(--text-2); line-height: 1.45; }
.privacy-note { font-size: 12px; color: var(--text-2); margin-top: 12px; }
.perm-chips { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 14px; }
.perm-chip {
  display: flex; align-items: center; gap: 7px;
  background: var(--card-surface); border-radius: 9px;
  font-size: 12px; padding: 8px 12px; color: var(--text-2);
}
.perm-chip b { color: var(--text); font-weight: 600; }

/* Information grid */
.info-grid {
  display: grid; grid-template-columns: repeat(4, 1fr);
  gap: 0 30px;
}
.info-cell {
  padding: 10px 0;
  border-bottom: 1px solid var(--hair-2);
  font-size: 12.5px;
  min-width: 0;
}
.info-cell .k { color: var(--text-3); margin-bottom: 2px; font-size: 12px; }
.info-cell .v { color: var(--text); overflow: hidden; text-overflow: ellipsis; }

/* Supports rows */
.supports { display: grid; grid-template-columns: 1fr 1fr; gap: 18px 40px; }
.support-row { display: flex; gap: 12px; font-size: 12.5px; }
.support-row .si {
  width: 34px; height: 34px; flex: none; border-radius: 8px;
  display: grid; place-items: center; font-size: 16px; color: var(--color-text-inverse);
}
.support-row h6 { font-size: 13px; margin-bottom: 2px; }
.support-row p { color: var(--text-2); line-height: 1.4; }

/* ---------------- Story page ---------------- */

.story-layout {
  display: grid;
  grid-template-columns: 420px 1fr;
  gap: 40px;
  max-width: 1080px;
  margin: 0 auto;
  padding: 0 36px 60px;
}
.story-hero {
  position: sticky; top: 60px;
  height: calc(100vh - 130px);
  min-height: 480px;
  border-radius: var(--card-r);
  overflow: hidden;
  color: var(--color-text-inverse);
  display: flex; flex-direction: column;
  padding: 26px;
}
.story-hero .eyebrow { color: rgba(255,255,255,.75); position: relative; z-index: 2; }
.story-hero h1 { font-size: 27px; font-weight: 700; line-height: 1.2; margin-top: 4px; position: relative; z-index: 2; }
.story-hero .banner {
  position: relative; z-index: 2;
  margin-top: auto;
  background: rgba(30,30,35,.5);
  backdrop-filter: blur(14px);
  border-radius: 10px;
  padding: 10px 12px;
  display: flex; align-items: center; gap: 10px;
}
.story-hero .banner .app-icon { width: 40px; height: 40px; }
.story-hero .banner .bmeta { flex: 1; min-width: 0; }
.story-hero .banner .bname { font-size: 13px; font-weight: 600; }
.story-hero .banner .bsub { font-size: 11px; color: rgba(255,255,255,.7); }
.story-hero .banner .get-btn { background: rgba(255,255,255,.92); }

.story-body { padding-top: 8px; font-size: 14.5px; line-height: 1.6; }
.story-body > * + * { margin-top: 14px; }
.story-body h3 { font-size: 15px; font-weight: 700; margin-top: 22px; }
.story-body .lead b { font-weight: 700; }
.story-fig { background: var(--card-surface); border-radius: var(--card-r); overflow: hidden; }
.story-fig .art { aspect-ratio: 16/9; position: relative; }
.story-fig figcaption { font-size: 12px; color: var(--text-2); padding: 12px 16px; line-height: 1.45; }
.story-approw {
  border: 1px solid var(--hair-2); border-radius: 10px; padding: 4px 14px;
}
.story-appcard {
  background: var(--card-surface); border-radius: var(--card-r);
  text-align: center; padding: 30px;
}
.story-appcard .app-icon { width: 64px; height: 64px; margin: 0 auto 12px; }
.story-appcard .n { color: var(--link); font-size: 16px; font-weight: 600; }
.story-appcard .s { font-size: 12px; color: var(--text-2); margin: 3px 0 14px; }
.share-story { text-align: center; margin-top: 30px; }
.share-story button {
  display: inline-flex; align-items: center; gap: 7px;
  background: var(--color-action-secondary); color: var(--link);
  font-size: 13px; font-weight: 500;
  padding: 8px 18px; border-radius: 20px;
}
.share-story svg { width: 13px; height: 13px; }

/* ---------------- Search ---------------- */

.scope-chips { margin-bottom: 18px; }
.search-tools { display: flex; justify-content: flex-end; margin: -34px 0 10px; }
.filters-toggle { color: var(--link); font-size: 13.5px; display: flex; gap: 5px; align-items: center; }
.filters-panel {
  display: grid; grid-template-columns: repeat(3, 220px);
  gap: 0 40px;
  border-bottom: 1px solid var(--hair);
  padding: 6px 0 16px; margin-bottom: 20px;
}
.filters-panel .fcol .fk { font-size: 12px; color: var(--text-3); padding: 6px 0; }
.filters-panel .frow {
  display: flex; justify-content: space-between; align-items: center;
  font-size: 13.5px; color: var(--link);
  padding: 7px 0; border-top: 1px solid var(--hair-2);
  cursor: var(--cur-pointer);
}
.filters-panel .frow .chk { color: var(--link); font-weight: 600; }

.result-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 26px 22px; }
.result-cell .app-row { border-bottom: 0; padding-top: 0; }
.result-shots { display: grid; grid-template-columns: repeat(3, 1fr); gap: 7px; }
.result-shots .shot { aspect-ratio: 10/13; border-radius: 8px; }
.result-shots .shot.wide { grid-column: 1 / -1; aspect-ratio: 16/10; }
.result-cell .stars-line { display: flex; gap: 5px; align-items: center; font-size: 10.5px; color: var(--text-3); }
.result-cell .stars-line .s { color: var(--color-warning-700); letter-spacing: .5px; }

/* ---------------- Updates ---------------- */

.update-row {
  display: flex; gap: 14px; align-items: flex-start;
  padding: 16px 0; border-bottom: 1px solid var(--hair-2);
}
.update-row .app-icon { width: 56px; height: 56px; }
.update-row .umeta { flex: 1; }
.update-row .uname { font-size: 14px; font-weight: 600; }
.update-row .uver { font-size: 11.5px; color: var(--text-3); margin: 2px 0 6px; }
.update-row .unotes { font-size: 12.5px; color: var(--text-2); line-height: 1.45; }

/* ---------------- Quick links footer ---------------- */

.quick-links { margin-top: 40px; }
.quick-links h3 { font-size: 17px; font-weight: 700; border-top: 1px solid var(--hair); padding-top: 14px; margin-bottom: 8px; }
.ql-grid { display: grid; grid-template-columns: repeat(3, 1fr); column-gap: 30px; }
.ql-grid a {
  font-size: 13px;
  padding: 9px 0;
  border-bottom: 1px solid var(--hair-2);
}
.ql-btns { display: flex; justify-content: center; gap: 14px; margin: 26px 0 10px; }
.ql-btns button {
  background: var(--color-action-secondary); color: var(--link);
  font-size: 13px; font-weight: 500;
  border-radius: 20px; padding: 8px 22px;
}

/* Global footer - appended to every route, carries the AIR marks & certifications */

.store-footer { background: var(--content-bg); }
.store-footer .foot-inner {
  max-width: 1100px;
  margin: 0 auto;
  padding: 0 36px 40px;
}
.store-footer .quick-links { margin-top: 34px; }

.foot-bottom {
  display: flex; align-items: center; justify-content: space-between;
  gap: 24px 48px; flex-wrap: wrap;
  margin-top: 36px;
  padding-top: 26px;
  border-top: 1px solid var(--hair-2);
}
.foot-bottom .cert-row { --cert-h: 30px; }
/* In the footer the badges are set in text black rather than AIR teal: down
   here they sit beside the legal line as quiet credentials, not brand marks,
   and the teal read as a second accent competing with the links above them. */
.foot-bottom .cert { background-color: var(--color-text-primary); }
/* The mark, opposite the certification row. This used to be a 54ch paragraph
   of prose with the wordmark set inline at 1.05em; it is now the wordmark
   alone, so the measure, the type size and the right-alignment of text all
   went with the sentence. `margin-left:auto` is what still pushes it to the
   far end of .foot-bottom, and it no longer claims flex-basis it does not
   need - at narrow widths .foot-bottom wraps and the mark drops under the
   badges rather than squeezing them. */
.foot-copy {
  flex: 0 0 auto;
  margin-left: auto;
  line-height: 0;             /* no descender gap under an inline image */
}
/* The mark links out to air.security. inline-block so the anchor is exactly
   the artwork and not a full-width strip of the flex item; the only hover
   affordance is a slight fade, because a wordmark is not underlined. */
.foot-copy a {
  display: inline-block;
  cursor: pointer;
  transition: opacity 0.14s ease;
  /* The wordmark is 61x22, and 22 is under the 24px WCAG 2.5.8 floor. Pad the
     block edges and cancel the padding with a matching negative margin, the
     same move store-v9-taps.css makes for the footer's other links - the hit
     area grows to 34px and not a pixel of layout moves. */
  padding-block: 6px;
  margin-block: -6px;
}
.foot-copy a:hover { opacity: 0.7; }
.foot-copy a:focus-visible {
  outline: 2px solid var(--color-border-focus);
  outline-offset: 4px;
  border-radius: var(--radius-xs);
}
/* Set against the --cert-h: 30px badges to its left, a touch shorter: the
   wordmark is solid letterforms where the badges are outlined artwork, so
   matching their height outweighs them. */
.foot-copy .air-logo { height: 22px; }
.foot-legal {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 8px 40px; flex-wrap: wrap;
  margin-top: 22px;
  font-size: 11.5px; color: var(--text-3);
}
.foot-legal a { color: var(--text-2); }

/* ---------------- Art helpers (generated placeholder artwork) ---------------- */

.art-fill { position: absolute; inset: 0; width: 100%; height: 100%; }
.art-fill svg { width: 100%; height: 100%; }
/* Generated listing media (media.js). Images are produced at 3:2 - the closest
   ratio Vertex offers to our 16:10 slots - so cover-fit does the final crop. */
img.art-fill { object-fit: cover; display: block; }
.glyph-center {
  position: absolute; inset: 0;
  display: grid; place-items: center;
  font-size: 52px;
  text-shadow: 0 6px 24px rgba(0,0,0,.25);
}

/* Generic fade-in per view */
.view { animation: fade .18s ease; }
@keyframes fade { from { opacity: 0; } to { opacity: 1; } }

/* Lightbox */
/* The overlay, panel, close button and dismissal behaviour are now the Air_DS
   Dialog (components.css + UI.openDialog). What stays here is only the media
   frame itself, which the DS has no component for. */
.lb-shot {
  width: 100%;
  border-radius: var(--radius-md);
  overflow: hidden;
  background: var(--color-surface-sunken);
}
/* Gallery viewer (REDESIGN §14): one collection, stepped by the buttons or the
   arrow keys. The frame keeps a fixed aspect so stepping between tiles doesn't
   resize the dialog under the pointer - 16:9 only when a clip is on stage. */
.lb-stage { position: relative; aspect-ratio: 16/10; }
/* Clips are de-pillarboxed to 16:10 by tools/gen-video.mjs, so the stage keeps
   one ratio for stills and video: stepping between them never resizes the
   dialog, and there is no letterbox to paint around. */
.lb-shot--video .lb-stage { background: #000; }
.lb-stage img, .lb-stage video, .lb-stage svg { width: 100%; height: 100%; object-fit: cover; display: block; }
.lb-bar {
  display: flex; align-items: center; justify-content: center; gap: 16px;
  padding: 10px 0 2px;
}
.lb-nav {
  width: 30px; height: 30px; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--pill); color: var(--text-2);
  cursor: var(--cur-pointer);
}
.lb-nav:hover { background: var(--color-neutral-200); }
.lb-nav svg { width: 15px; height: 15px; }
.lb-count { font-size: 12px; color: var(--text-3); font-variant-numeric: tabular-nums; }

/* Video modal - inline YouTube playback, reuses .lightbox chrome */
/* A video letterbox is genuinely black - not surface/inverse, which is
   neutral/900 and would show as a dark grey band around the frame. */
.video-embed { background: #000; }
.video-embed iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }

/* Scrollbar cosmetics */
.content::-webkit-scrollbar { width: 14px; }
.content::-webkit-scrollbar-thumb {
  background: rgba(0,0,0,.22); border-radius: 8px;
  border: 4px solid transparent; background-clip: content-box;
}
.content::-webkit-scrollbar-thumb:hover { background-color: rgba(0,0,0,.35); background-clip: content-box; border: 4px solid transparent; }

/* ==========================================================================
   Skill detail page - trust components (quality, security, GitHub, videos)
   ========================================================================== */

/* Official / verified micro-badges next to creator name */
.dev-badges { display: inline-flex; gap: 6px; margin-left: 8px; vertical-align: 1px; }
.mini-badge {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: 10.5px; font-weight: 600; padding: 2px 8px; border-radius: 20px;
  background: var(--pill); color: var(--text-2);
}
.mini-badge.blue { background: var(--color-brand-50); color: var(--accent); }
.mini-badge svg { width: 10px; height: 10px; }

/* "What this does" lede - still the first thing read on a skill page, but
   Apple-loud rather than web-loud: no box, no icon, no rail. Editorial type
   set straight on the page, the way App Store product pages open. Header
   matches the "See it in action" heading below it (.vids-head h3); body copy
   matches the standard description text (.desc-grid .desc). A hairline
   divider under the Get line closes the section off like every section below. */
.plain-lede {
  margin: 0 0 24px;
  padding-bottom: 24px;
  border-bottom: 1px solid var(--hair-2);
}
.plain-lede h3 { font-size: 18px; font-weight: 700; letter-spacing: .1px; margin-bottom: 8px; }
.plain-lede p {
  font-size: 13px; font-weight: 400; line-height: 1.5;
  color: var(--text);
}
.plain-lede .lede-sub {
  display: flex; align-items: center; flex-wrap: wrap; gap: 10px;
  margin-top: 12px; font-size: 13px; color: var(--text-2);
}
.plain-lede .get-btn { font-size: 12px; padding: 4px 16px; }

/* GitHub repo strip */
/* Where the code lives, as one quiet row (2026-08-23). It was a full card - a
   34px black avatar, a 15px/700 star count, 13px padding - competing with the
   listing's own hero for the same glance. It is a link to somewhere else, so it
   reads as a line of meta with a hairline under it: no box, small mono-ish
   type, the star count demoted to the same size as everything beside it. */
.repo-strip {
  display: flex; align-items: baseline; gap: 10px;
  border-bottom: 1px solid var(--hair); padding: 0 0 10px;
  margin: 2px 0 14px; cursor: var(--cur-pointer);
}
.repo-strip:hover .rlink { text-decoration: underline; }
.repo-strip .gh {
  width: 15px; height: 15px; color: var(--text-3);
  display: grid; place-items: center; flex: none;
  position: relative; top: 2px;
}
.repo-strip .gh svg { width: 15px; height: 15px; fill: currentColor; }
.repo-strip .rmeta { flex: 1; min-width: 0; display: flex; flex-wrap: wrap; align-items: baseline; gap: 3px 8px; }
.repo-strip .rname { font-size: 12.5px; font-weight: 600; }
.repo-strip .rsub { font-size: 12px; color: var(--text-3); }
.repo-strip .stars-big { font-size: 12px; font-weight: 600; color: var(--text-2); white-space: nowrap; }
.repo-strip .stars-big .star { color: var(--text-3); }
.repo-strip .rlink { font-size: 12px; color: var(--link); white-space: nowrap; }

/* Quality score ring */
.ring { --p: 84; --ring-c: var(--accent); width: 74px; height: 74px; border-radius: 50%; position: relative; flex: none;
  background: conic-gradient(var(--ring-c) calc(var(--p) * 1%), var(--color-neutral-200) 0); }
.ring::before { content: ""; position: absolute; inset: 7px; border-radius: 50%; background: var(--color-text-inverse); }
.ring .rn { position: absolute; inset: 0; display: grid; place-items: center; font-size: 22px; font-weight: 700; color: var(--text); }
.ring.big { width: 110px; height: 110px; }
.ring.big::before { inset: 10px; }
.ring.big .rn { font-size: 32px; }

/* Quality card on the listing */
.qual-card {
  display: grid; grid-template-columns: auto 1fr auto; gap: 24px; align-items: center;
  background: var(--card-surface); border-radius: var(--card-r); padding: 20px 22px;
}
.qual-card .q-ring { text-align: center; }
.qual-card .q-grade { font-size: 11px; font-weight: 600; color: var(--text-2); margin-top: 7px; }
.qual-card .q-head { font-size: 14px; line-height: 1.45; font-weight: 500; }
.qual-card .q-sub { font-size: 12px; color: var(--text-2); margin-top: 4px; }
.qual-card .q-side { text-align: right; font-size: 12.5px; display: flex; flex-direction: column; gap: 6px; align-items: flex-end; }
.qual-card .q-checks { color: var(--text-2); }
.cmp { margin-top: 12px; display: flex; flex-direction: column; gap: 7px; max-width: 430px; }
.cmp .crow { display: grid; grid-template-columns: 108px 1fr 38px; gap: 10px; align-items: center; font-size: 11.5px; color: var(--text-2); }
.cmp .cbar { height: 7px; border-radius: 4px; background: var(--color-neutral-200); overflow: hidden; }
.cmp .cbar i { display: block; height: 100%; border-radius: 4px; background: var(--color-neutral-400); }
.cmp .crow.with .cbar i { background: linear-gradient(90deg, var(--color-brand-600), var(--color-brand-400)); }
.cmp .cpct { font-weight: 700; color: var(--text); text-align: right; }

/* Video cards (real YouTube thumbnails) */
.vids-block { margin-bottom: 4px; }
.vids-head {
  display: flex; align-items: baseline; justify-content: space-between; gap: 16px;
}
.vids-head h3 { font-size: 18px; font-weight: 700; letter-spacing: .1px; }
.vids-head .see-all { font-size: 13px; white-space: nowrap; }
.vids-sub {
  font-size: 12.5px; line-height: 1.4; color: var(--text-3);
  margin-top: 4px; max-width: 780px;
}
/* The empty state is a line of prose, not an empty card grid: the block stays
   so the reader learns the store has community video, and says plainly that
   this listing has none yet. */
.vids-block--empty { margin-top: 16px; }
.vids-empty {
  font-size: 12.5px; line-height: 1.5; color: var(--text-3);
  margin-top: 4px; max-width: 780px;
}
.vids-empty .see-all { font-size: 12.5px; white-space: nowrap; }
.vids-row {
  display: flex; gap: 16px; overflow-x: auto; padding: 14px 0 6px;
  scrollbar-width: none;
}
.vids-row::-webkit-scrollbar { display: none; }
.vid-card { width: 300px; flex: none; cursor: var(--cur-pointer); }
.vid-thumb {
  aspect-ratio: 16/9; border-radius: 12px; overflow: hidden; position: relative;
  background: var(--color-text-primary); box-shadow: 0 1px 3px rgba(0,0,0,.14);
}
.vid-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.vid-card:hover .vid-thumb img { opacity: .92; }
.vid-dur {
  position: absolute; right: 8px; bottom: 8px; background: rgba(0,0,0,.75); color: var(--color-text-inverse);
  font-size: 10.5px; font-weight: 600; padding: 2px 6px; border-radius: 4px;
}
.vid-title {
  font-size: 13px; font-weight: 600; line-height: 1.35; margin-top: 9px;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.vid-sub { font-size: 11.5px; color: var(--text-3); margin-top: 3px; }

/* Security summary on the listing */
.sec-head-card {
  display: flex; align-items: center; gap: 16px;
  border: 1px solid var(--hair); border-radius: var(--card-r); padding: 16px 18px;
}
.sec-head-card .sshield { width: 42px; height: 42px; color: var(--color-status-success); flex: none; }
.sec-head-card .sshield svg { width: 42px; height: 42px; }
.sec-head-card .st { flex: 1; }
.sec-head-card .st b { font-size: 14.5px; display: block; }
.sec-head-card .st span { font-size: 12px; color: var(--text-2); }
.sec-head-card .rlink { font-size: 12.5px; color: var(--link); white-space: nowrap; cursor: var(--cur-pointer); }
.sec-cats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin-top: 14px; }
.sec-cat {
  display: flex; align-items: center; gap: 8px; font-size: 12px; color: var(--text);
  background: var(--card-surface); border-radius: 10px; padding: 9px 12px;
}
.sec-cat .ok { color: var(--color-status-success); font-weight: 700; flex: none; }

/* ---------------- Report pages (quality & security) ---------------- */

.rep-wrap { max-width: 780px; margin: 0 auto; }
.rep-head { display: flex; align-items: center; gap: 18px; padding: 22px 0 6px; }
.rep-head .app-icon { width: 72px; height: 72px; }
.rep-head .rh-meta { flex: 1; }
.rep-head .rh-over { font-size: 11px; font-weight: 700; letter-spacing: .6px; text-transform: uppercase; color: var(--text-3); }
.rep-head h1 { font-size: 24px; font-weight: 700; line-height: 1.15; margin-top: 2px; }
.rep-head .rh-sub { font-size: 13px; color: var(--text-2); margin-top: 4px; }
.rep-lead { font-size: 14.5px; line-height: 1.55; margin-top: 14px; }
.rep-note { font-size: 12px; color: var(--text-3); line-height: 1.5; margin-top: 10px; }

.badge-verified {
  display: inline-flex; align-items: center; gap: 6px;
  background: var(--color-status-success-bg); color: var(--color-status-success-fg-strong); font-size: 12px; font-weight: 700;
  border-radius: 20px; padding: 4px 12px;
}
.badge-verified svg { width: 13px; height: 13px; }

/* scenario cards (quality) */
.scn-card {
  border: 1px solid var(--hair); border-radius: var(--card-r);
  padding: 16px 18px; margin-top: 12px;
}
.scn-card h4 { font-size: 14px; font-weight: 600; }
.scn-card .snote { font-size: 12px; color: var(--text-2); margin-top: 8px; }

/* check groups (quality) */
.grp-card { border: 1px solid var(--hair); border-radius: var(--card-r); padding: 16px 18px; margin-top: 12px; }
.grp-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.grp-head h4 { font-size: 14px; font-weight: 600; }
.grp-head .gscore { font-size: 12.5px; font-weight: 700; color: var(--text-2); white-space: nowrap; }
.grp-plain { font-size: 12px; color: var(--text-2); margin-top: 3px; }
.grp-items { margin-top: 10px; display: grid; grid-template-columns: 1fr 1fr; gap: 6px 20px; }
.grp-item { display: flex; gap: 7px; font-size: 12.5px; align-items: baseline; }
.grp-item .gi { flex: none; font-weight: 700; }
.grp-item .gi.pass { color: var(--color-status-success); }
.grp-item .gi.warn { color: var(--color-status-warning); }

.rep-meta-strip {
  display: flex; flex-wrap: wrap; gap: 8px 22px; font-size: 12px; color: var(--text-2);
  background: var(--card-surface); border-radius: 10px; padding: 12px 16px; margin-top: 18px;
}
.rep-meta-strip b { color: var(--text); font-weight: 600; }

/* security category rows */
.sec-row { display: flex; gap: 14px; padding: 16px 0; border-bottom: 1px solid var(--hair-2); }
.sec-row:last-child { border-bottom: 0; }
.sec-row .si { font-size: 24px; flex: none; width: 34px; text-align: center; }
.sec-row .sm { flex: 1; }
.sec-row h4 { font-size: 14px; font-weight: 600; }
.sec-row p { font-size: 12.5px; color: var(--text-2); line-height: 1.45; margin-top: 3px; }
.sec-row .sres {
  flex: none; align-self: center; font-size: 12px; font-weight: 700; color: var(--color-status-success-fg-strong);
  background: var(--color-status-success-bg); border-radius: 20px; padding: 4px 12px; white-space: nowrap;
}

.never-card { background: var(--card-surface); border-radius: var(--card-r); padding: 16px 18px; }
.never-card ul { list-style: none; display: grid; grid-template-columns: 1fr 1fr; gap: 8px 20px; }
.never-card li { font-size: 12.5px; display: flex; gap: 8px; align-items: baseline; }
.never-card li::before { content: "✕"; color: var(--color-status-critical); font-weight: 700; font-size: 11px; }

.hist-row { display: flex; gap: 14px; font-size: 12.5px; padding: 10px 0; border-bottom: 1px solid var(--hair-2); align-items: baseline; }
.hist-row:last-child { border-bottom: 0; }
.hist-row .hv { font-weight: 600; width: 64px; flex: none; }
/* `.hist-row .hd`, the date column, went with the date itself (app.js). The
   frozen v0 prototype still renders one, off its own sheet (assets/v0). */
.hist-row .hres { color: var(--text-2); }
.hist-row .hres b { color: var(--color-status-success-fg-strong); font-weight: 600; }

.verifier-card {
  display: flex; align-items: center; gap: 14px; margin-top: 26px;
  border: 1px solid var(--hair); border-radius: var(--card-r); padding: 16px 18px;
}
.verifier-card .vlogo {
  width: 40px; height: 40px; border-radius: 10px; flex: none;
  background: linear-gradient(135deg, var(--color-neutral-600), var(--color-neutral-900)); color: var(--color-text-inverse);
  display: grid; place-items: center;
}
.verifier-card .vlogo svg { width: 22px; height: 22px; }
.verifier-card .vlogo { background: var(--color-neutral-900); }
.verifier-card .vlogo .air-logo { height: 14px; }
.verifier-card .vm { flex: 1; font-size: 12.5px; color: var(--text-2); line-height: 1.45; }
.verifier-card .vm b { color: var(--text); font-size: 13px; display: block; }

/* ---------------- AIR brand marks & certifications ---------------- */

/* Sized in em so the wordmark tracks whatever text it is set in. */
.air-logo {
  height: 1em; width: auto;
  display: inline-block;
  vertical-align: -0.13em;
}

/* The badge artwork ships as white-on-transparent, so its alpha channel *is* the
   mark. Painting it through a CSS mask recolours it exactly to AIR's brand teal
   and works identically for the SVG badges and the PNG one. */
.cert-row {
  --cert-h: 34px;
  display: flex; align-items: center; flex-wrap: wrap;
  gap: 10px 24px;
}
.cert {
  display: block;
  flex: none;
  height: var(--cert-h);
  width: calc(var(--cert-h) * var(--r, 1));
  background-color: var(--air-teal);
  -webkit-mask: var(--m) center / contain no-repeat;
  mask: var(--m) center / contain no-repeat;
}
/* --r is the badge's intrinsic width/height, so one height var sizes them all. */
.cert-soc2 { --m: url("../img/air/cert-soc2.svg");      --r: 1; }
.cert-iso  { --m: url("../img/air/cert-iso-27001.svg"); --r: 0.757; }
.cert-gdpr { --m: url("../img/air/cert-gdpr.svg");      --r: 1.117; }
.cert-euai { --m: url("../img/air/cert-eu-ai-act.png"); --r: 3.937; }

/* prevent scroll anchoring from restoring old positions across hash routes */
#content { overflow-anchor: none; }

/* Re-verified line on the Updates page. Was an inline #059669 (Tailwind
   emerald-600, 3.77:1) - success/700 reaches 5.4:1 and is an Air token. */
.uver--ok { color: var(--color-success-700); }

/* Empty state for a tab filter that matches nothing. Spans the results grid so
   it reads as the grid's content rather than a stray line beside it. */
.tab-empty {
  grid-column: 1 / -1;
  margin: var(--space-16) 0;
  font-size: 13.5px;
  color: var(--text-2);
}

/* ---------------- Mono ----------------
   Air_DS declares JetBrains Mono as Mono/AirFont but does not say where it
   belongs, so the call is ours: literal machine identifiers - version strings,
   repo slugs, per-run cost figures. These are values a reader compares
   character by character ("2.4.1" vs "2.4.11", "$0.02" vs "$0.20"), which is
   exactly what tabular, fixed-width glyphs are for. Prose stays Manrope.
   Applied narrowly on purpose; mono creeping into labels or descriptions makes
   a marketplace read like a terminal. */
.repo-strip .rname,
.hist-row .hv,
.uver .uver-v,
.mono {
  font-family: var(--font-mono-stack);
  font-variant-numeric: tabular-nums;
}
/* Sizes step down a touch: JetBrains Mono runs optically larger than Manrope
   at the same px, so matching the number keeps the line rhythm. */
.repo-strip .rname { font-size: 12.5px; }
.hist-row .hv { font-size: 12px; }

/* ==========================================================================
   v4 - the skill page answers the Aug 2 review (REDESIGN.md §12)
   --------------------------------------------------------------------------
   All shared, so it lands in store.css rather than a per-design sheet: the
   detail page is the one screen the three designs do NOT fork, and a save
   button that only existed in v3 would make the design switch a feature
   switch. House rule from 2026-07-30 still holds here - emphasis comes from
   type scale and whitespace, not coloured rails, borders or shadows.
   ========================================================================== */

/* ---- Top 3 things it does for you (§12.2.2) ----
   The loudest list on the page: 15px/600 sits between the lede (19px) and body
   copy (13px), so it reads as a promise rather than a feature table. */
.lede-tasks { margin-top: 16px; }
.lede-tasks h4 {
  font-size: 11px; font-weight: 600; letter-spacing: .6px;
  text-transform: uppercase; color: var(--text-3);
  margin-bottom: 8px;
}
.lede-tasks ul { list-style: none; display: grid; gap: 7px; }
.lede-tasks li {
  display: flex; align-items: flex-start; gap: 9px;
  font-size: 15px; font-weight: 600; line-height: 1.35;
  color: var(--text);
}
.lede-tasks .tk {
  flex: none; margin-top: 1px;
  font-size: 13px; font-weight: 700;
  color: var(--color-success-700);
}

/* ---- What's inside (§12.2.3) ----
   Hairline rows, not cards: a bill of materials is a list of facts, and boxing
   each file would give six files the visual weight of six sections. */
.bom-sub { font-size: 12.5px; color: var(--text-2); margin: 2px 0 10px; }
.bom { border-top: 1px solid var(--hair-2); }
.bom-row {
  display: grid;
  grid-template-columns: 22px minmax(120px, 240px) 1fr;
  align-items: baseline; gap: 12px;
  padding: 10px 2px;
  border-bottom: 1px solid var(--hair-2);
}
.bom-kind { font-size: 13px; color: var(--text-3); text-align: center; }
.bom-name { font-size: 12.5px; color: var(--text); word-break: break-all; }
/* A file name that links to the file. It stays the colour of a filename rather
   than becoming a blue link in a list of filenames: the arrow says it opens, and
   the hover says it is clickable. */
a.bom-name { cursor: pointer; text-decoration: none; }
a.bom-name:hover { text-decoration: underline; }
.bom-open { color: var(--text-3); font-size: 11px; }
.bom-note { font-size: 12.5px; color: var(--text-2); }
.bom-foot { margin-top: 10px; font-size: 12.5px; }
.bom-foot a { color: var(--link); }

/* A long list gets folded (see bomLevel() in app.js): one row per folder, one
   row for the tail of an over-long level, both <details>. A folder row is the
   same hairline row as a file row - it must read as part of the same list, not
   as a control bolted on top of it - so it borrows .bom-row and only swaps the
   glyph column for a caret. */
.bom-tally { color: var(--text-3); white-space: nowrap; }
.bom-dir, .bom-more { border-bottom: 1px solid var(--hair-2); }
.bom-dirrow, .bom-morerow {
  border-bottom: 0;
  cursor: var(--cur-pointer);
  list-style: none;
}
.bom-dirrow::-webkit-details-marker,
.bom-morerow::-webkit-details-marker { display: none; }
.bom-dirrow .bom-name { font-weight: 600; }
.bom-morerow .bom-name { color: var(--link); font-size: 12.5px; }
.bom-dirrow:hover .bom-name, .bom-morerow:hover .bom-name { text-decoration: underline; }
.bom-caret {
  font-size: 15px; color: var(--text-3); text-align: center;
  transition: transform .12s ease;
}
[open] > .bom-dirrow .bom-caret,
[open] > .bom-morerow .bom-caret { transform: rotate(90deg); }
/* The nested rows are indented to the name column so a child file lines up
   under its folder's name rather than under its caret. The last child's
   hairline is dropped: the <details> already carries one. */
.bom-kids { padding-left: 22px; }
/* The tail of a long level is the same level, not a child of anything, so its
   rows keep the indent of their siblings. */
.bom-more > .bom-kids { padding-left: 0; }
.bom-kids > .bom-row:last-child,
.bom-kids > .bom-dir:last-child,
.bom-kids > .bom-more:last-child { border-bottom: 0; }
@media (max-width: 720px) { .bom-kids { padding-left: 12px; } }

/* ---- Save for later (§12.2.6) ----
   Air_DS has no filled-bookmark glyph, so the saved state is carried by the
   label and the filled treatment, not by the icon alone. */
.save-btn {
  display: inline-flex; align-items: center; gap: 6px;
  height: 24px; padding: 0 var(--space-8);
  border-radius: var(--radius-sm);
  border: 1px solid var(--hair);
  background: none; color: var(--text);
  font-family: var(--font); font-size: 13px; font-weight: 500;
  white-space: nowrap;
  transition: background-color 120ms ease, border-color 120ms ease;
}
.save-btn:hover { background: var(--pill); }
.save-btn:focus-visible { outline: none; box-shadow: inset 0 0 0 1px var(--color-border-focus); }
.save-btn .ic { --ic-size: 15px; }
/* Inline glyphs (heart / phone / cloud, app.js SAVE_GLYPH) - not sprite icons,
   so they size here rather than through .ic. */
.save-btn .sv-glyph { display: inline-flex; }
.save-btn .sv-glyph svg { width: 15px; height: 15px; }
/* The where-glyph: phone = this device, cloud = the account. Trails the label
   and stays quieter than it - it is a fact, not a second label. */
.save-btn .save-where { display: inline-flex; margin-left: 2px; opacity: .75; }
.save-btn .save-where svg { width: 14px; height: 14px; }
.save-btn.is-saved {
  background: var(--color-action-secondary-hover);
  border-color: transparent;
}
.save-btn.is-saved .ic { color: var(--color-success-700); }

/* The sign-in-to-sync strip: page state, not a toast. Its box is reserved from
   first render (min-height matching the one-line strip, below), saved or not,
   so the first save changes pixels inside an existing box instead of growing
   the CTA column and shifting "Open in Claude" above it. Empty (not saved, or
   signed in - nothing to pitch either way) it stays in the flow but invisible,
   via :empty below, rather than leaving the layout via display:none. Below
   ~420px the strip wraps to two lines (next rule) and the extra line still
   shifts the layout - accepted on phones only, per the flag that asked for
   this fix. */
.save-sync {
  display: flex; align-items: center; gap: 3px 5px;
  flex-wrap: wrap;
  width: fit-content;
  margin-top: var(--space-8); padding: 4px 10px;
  border-radius: var(--radius-sm);
  background: var(--pill);
  font-size: 12.5px; line-height: 1; color: var(--text-2);
  min-height: 21px;
}
/* Narrow screens break BETWEEN phrases, never inside one. */
.save-sync > * { white-space: nowrap; }
.save-sync:empty { visibility: hidden; }
.save-sync .sv-glyph { display: inline-flex; }
.save-sync .sv-glyph svg { width: 13px; height: 13px; }
.save-sync-link {
  border: 0; background: none; padding: 0;
  font: inherit; color: var(--link); cursor: pointer;
}
.save-sync-link:hover { text-decoration: underline; }
/* The chrome-bar copy: same button, condensed, and it appears with the bar. */
.chrome-bar .save-btn {
  pointer-events: auto;
  margin-left: 8px;
  opacity: 0; transition: opacity .15s;
  background: rgba(255,255,255,.9);
}
.chrome-bar.scrolled .save-btn { opacity: 1; background: none; }

/* A text-weight action for the buy row and list tools. Not a .get-btn: two
   pills side by side would give "Send to me" the same weight as Get. */
.link-btn {
  font-family: var(--font); font-size: 13px; color: var(--link);
  background: none; padding: 0 2px;
}
.link-btn:hover { text-decoration: underline; }

/* ---- Honest empties (§12.2.5) ---- */
.stat--empty { cursor: var(--cur-pointer); }
.stat--empty .val { color: var(--text-3); font-weight: 500; }
.stat--empty .sub { color: var(--link); }
.shots-note { font-size: 11.5px; color: var(--text-3); margin: -4px 0 4px; }
.stars-line .s-none { font-size: 11.5px; color: var(--text-3); }

.be-first {
  display: flex; align-items: center; gap: 18px;
  background: var(--card-surface);
  border-radius: var(--card-r);
  padding: 18px 20px;
  margin-top: 6px;
}
.be-first .bf-stars { font-size: 20px; letter-spacing: 2px; color: var(--text-3); flex: none; }
.be-first .bf-copy { flex: 1; display: grid; gap: 3px; }
.be-first .bf-copy b { font-size: 14px; }
.be-first .bf-copy span { font-size: 12.5px; color: var(--text-2); }
.be-first .get-btn { flex: none; }

/* "Not checked yet": teal means verified and nothing else (v2.1's rule), so
   the badge's inner Air mark drops to the same neutral as its shield. */
.sec-head-card--none .sshield { color: var(--text-3); }
.sec-head-card--none .air-shield-mark g { fill: currentColor; }

/* ---- Efficiency report (§12.2.4) ---- */
.rep-illus {
  font-size: 12px; color: var(--text-3);
  margin-top: 10px; padding-top: 10px;
  border-top: 1px solid var(--hair-2);
}
.eff-grid {
  display: grid; grid-template-columns: repeat(4, 1fr);
  gap: 1px; background: var(--hair-2);
  border: 1px solid var(--hair-2); border-radius: var(--card-r);
  overflow: hidden;
  margin-top: 4px;
}
.eff-cell { background: var(--content-bg); padding: 14px 15px; }
.eff-cell .k {
  font-size: 10px; font-weight: 600; letter-spacing: .6px;
  text-transform: uppercase; color: var(--text-3);
}
.eff-cell .v { font-size: 20px; font-weight: 700; margin: 5px 0 4px; font-variant-numeric: tabular-nums; }
.eff-cell .v .u { font-size: 11px; font-weight: 600; color: var(--text-3); letter-spacing: .2px; }
.eff-cell .n { font-size: 11.5px; color: var(--text-2); line-height: 1.4; }

/* At-a-glance decision panel: the grade, the derived verdict, the headline cost
   comparison and the savings/consistency reads in one first-class card. Built
   from primitives the report already owns (q-ring, cmp/crow) and the same card
   surface as .qual-card so it reads as one design with the Quality/Security
   reports, not a bolted-on banner. */
.eff-summary {
  display: flex; align-items: center; gap: 22px;
  background: var(--card-surface); border-radius: var(--card-r);
  padding: 20px 22px; margin-top: 16px;
}
.eff-sum-ring { flex: none; text-align: center; }
.eff-sum-ring .q-grade { font-size: 11px; font-weight: 600; color: var(--text-2); margin-top: 7px; }
.eff-sum-main { flex: 1; min-width: 0; }
.eff-verdict { font-size: 15px; line-height: 1.5; font-weight: 600; color: var(--text); }
.eff-sum-cmp { margin-top: 12px; }
.eff-sum-reads { display: flex; flex-wrap: wrap; gap: 8px 32px; margin-top: 14px; }
.eff-read { display: flex; flex-direction: column; gap: 3px; }
.eff-read .er-k {
  font-size: 10px; font-weight: 600; letter-spacing: .6px;
  text-transform: uppercase; color: var(--text-3);
}
.eff-read .er-v { font-size: 15px; font-weight: 700; color: var(--text); font-variant-numeric: tabular-nums; }

/* The Cost report's comparison rows carry approximate dollar figures ("≈ $0.05")
   in the value slot, not the 38px percentage the Quality report shows there, so
   that column sizes to its content and the figure never wraps. */
.eff-sum-cmp .crow,
.section[data-section="cost-per-outcome"] .crow { grid-template-columns: 108px 1fr auto; }
.eff-sum-cmp .cpct,
.section[data-section="cost-per-outcome"] .cpct { white-space: nowrap; }

/* The methodology box the meeting asked for: closed by default so the page
   stays readable, and openable in place so nobody has to take our word for it.
   <details> rather than a scripted panel - it works with JS off and keyboards
   already know it. */
.meth {
  border: 1px solid var(--hair);
  border-radius: var(--card-r);
  margin-top: 4px;
}
.meth > summary {
  display: flex; align-items: baseline; gap: 10px;
  padding: 14px 16px;
  cursor: var(--cur-pointer);
  list-style: none;
}
.meth > summary::-webkit-details-marker { display: none; }
.meth > summary::after {
  content: "＋"; margin-left: auto;
  font-size: 14px; color: var(--text-3);
}
.meth[open] > summary::after { content: "－"; }
.meth[open] > summary { border-bottom: 1px solid var(--hair-2); }
.meth .meth-t { font-size: 14px; font-weight: 600; color: var(--link); }
.meth .meth-h { font-size: 12px; color: var(--text-3); }
.meth-body { padding: 14px 16px 16px; font-size: 13px; line-height: 1.55; color: var(--text); }
.meth-body p + p, .meth-body ol + p { margin-top: 10px; }
.meth-body ol { margin: 10px 0 0 18px; display: grid; gap: 7px; }
.meth-body li { font-size: 12.5px; color: var(--text-2); }
.meth-body li b { color: var(--text); }
.meth-body a { color: var(--link); }
.meth-illus { color: var(--text-3); font-size: 12px; }
/* The summary is a title and a hint on one baseline, which needs room the
   phone does not have: at 375px the two share the row until the TITLE wraps
   ("How this / report works") while the hint sits beside it, which reads as a
   broken heading rather than a label with a subtitle. Stacked below the app's
   collapse seam (720px, store-v9-mobile.css BREAKPOINTS) - the hint keeps its
   own line and the title keeps being a title. */
@media (max-width: 720px) {
  .meth > summary { flex-direction: column; align-items: flex-start; gap: 2px; }
  .meth > summary::after { position: absolute; right: 16px; }
  .meth > summary { position: relative; padding-right: 40px; }
}

/* ---- Saved view (§12.2.6) ---- */
.saved-count { font-size: 13px; color: var(--text-2); margin-top: -6px; }
.saved-tools { margin-top: 18px; text-align: right; }
.saved-empty {
  text-align: center;
  padding: 56px 20px 60px;
  background: var(--card-surface);
  border-radius: var(--card-r);
  margin-top: 8px;
}
.saved-empty .se-ic { color: var(--text-3); margin-bottom: 10px; }
.saved-empty .se-ic .ic { --ic-size: 32px; }
.saved-empty h3 { font-size: 17px; font-weight: 700; }
.saved-empty p {
  font-size: 13.5px; color: var(--text-2);
  max-width: 380px; margin: 6px auto 16px;
  line-height: 1.5;
}

/* The sign-in tease. It sits ON TOP of a list that already works - that is the
   whole argument (§12.2.6), so it is dismissible and never modal. */
.signin-card {
  position: relative;
  display: flex; align-items: center; gap: 16px;
  border: 1px solid var(--hair);
  border-radius: var(--card-r);
  padding: 16px 44px 16px 18px;
  margin: 4px 0 22px;
}
.signin-card .sc-ic { color: var(--text-3); flex: none; }
.signin-card .sc-copy { flex: 1; display: grid; gap: 3px; }
.signin-card .sc-copy b { font-size: 14px; }
.signin-card .sc-copy span { font-size: 12.5px; color: var(--text-2); }
.signin-card .get-btn { flex: none; }
.signin-card .sc-x {
  position: absolute; top: 10px; right: 10px;
  width: 24px; height: 24px; border-radius: 50%;
  display: grid; place-items: center;
  color: var(--text-3); background: none;
}
.signin-card .sc-x:hover { background: var(--pill); }

/* ---- Send to me sheet + magic link (§12.2.6, both mocked) ---- */
.sheet { display: grid; gap: 2px; }
.sheet-row {
  display: flex; align-items: center; gap: 14px;
  width: 100%; text-align: left;
  padding: 12px 4px;
  background: none;
  border-bottom: 1px solid var(--hair-2);
}
.sheet-row:last-of-type { border-bottom: 0; }
button.sheet-row:hover { background: var(--card-surface); }
.sheet-ic {
  width: 34px; height: 34px; border-radius: 50%;
  background: var(--card-surface); color: var(--text-2);
  display: grid; place-items: center; flex: none;
}
.sheet-txt { display: grid; gap: 2px; }
.sheet-txt b { font-size: 13.5px; }
.sheet-txt span { font-size: 12px; color: var(--text-2); line-height: 1.45; }
.sheet-txt em { font-style: normal; color: var(--text-3); }
.sheet-qr { width: 62px; height: 62px; flex: none; color: var(--text); }
.sheet-qr .qr { width: 100%; height: 100%; display: block; }
.sheet-link {
  font-size: 11px; color: var(--text-3);
  padding-top: 12px; margin-top: 4px;
  border-top: 1px solid var(--hair-2);
  word-break: break-all;
}

.magic { display: grid; gap: 6px; }
.magic-l { font-size: 12px; font-weight: 600; color: var(--text-2); }
.magic-i {
  font-family: var(--font); font-size: 14px;
  padding: 9px 12px;
  border: 1px solid var(--hair);
  border-radius: var(--radius-sm);
  background: var(--content-bg); color: var(--text);
}
.magic-i:focus-visible { outline: none; border-color: var(--color-border-focus); }
.magic-n { font-size: 12px; color: var(--text-2); }
.magic-mock { font-size: 11.5px; color: var(--text-3); }

/* ---- Toast ----
   A deliberate stand-in: Air_DS ships a Toast component we have not extracted
   yet (its spec has to come from the bound variables, not a guess), so this is
   one element with one class, sized and coloured from tokens, easy to delete
   when the real component lands.

   TOP OF THE STACK (110), above .dialog-overlay (components.css, 100) and the
   anchored menu (95). It used to sit at 90, under the dialog scrim - and the
   one flow that raises a toast from inside an open dialog is the Stack's
   "Install all": the fallback toast, whose "Open Claude on the web" button is
   the entire escape hatch when no handler answers the claude:// link, was
   painted BEHIND the Stack drawer's scrim and dimmed by it. A click aimed at
   the button hit the scrim instead, so the multi-item install had a visible
   dead end where the single CTA (raised from a page, with no dialog over it)
   had none. A toast is transient and bottom-anchored; nothing it covers is
   covered for long, and a message that cannot be reached is not a message. */
.v4-toast {
  position: fixed;
  left: 50%; bottom: 26px;
  transform: translate(-50%, 12px);
  z-index: 110;
  display: flex; align-items: flex-start; gap: 12px;
  max-width: 520px;
  padding: 13px 12px 13px 16px;
  border-radius: var(--card-r);
  background: var(--color-text-primary);
  color: var(--color-text-inverse);
  box-shadow: 0 8px 28px rgba(0,0,0,.22);
  opacity: 0;
  transition: opacity 160ms ease, transform 160ms ease;
}
.v4-toast.in { opacity: 1; transform: translate(-50%, 0); }
/* The body takes the slack so the dismiss X (and the action pill) sit against
   the right edge - on phones the toast is stretched to the viewport, and
   without this they would float in the middle, right after the last word. */
.v4-toast-body { flex: 1 1 auto; min-width: 0; font-size: 13px; line-height: 1.45; }
.v4-toast-body b { font-weight: 600; }
.v4-toast .toast-link {
  font-family: var(--font); font-size: 13px; font-weight: 600;
  color: var(--color-text-inverse);
  background: none; padding: 0;
  text-decoration: underline;
}
.v4-toast-x {
  flex: none; background: none; color: var(--color-text-inverse);
  opacity: .7; padding: 0; margin-top: 1px;
}
.v4-toast-x:hover { opacity: 1; }
/* Action button (v5 fallback: "Open Claude on the web"). A bordered pill inside
   the dark toast, so it reads as the recommended next step, not a link. */
.v4-toast-action {
  flex: none; align-self: center;
  font-family: var(--font); font-size: 12.5px; font-weight: 600;
  color: var(--color-text-inverse);
  padding: 6px 12px;
  border: 1px solid rgba(255,255,255,.28);
  border-radius: var(--radius-sm);
  background: rgba(255,255,255,.06);
  white-space: nowrap;
  transition: background-color 120ms ease;
}
.v4-toast-action:hover { background: rgba(255,255,255,.16); }
.v4-toast-action:focus-visible { outline: none; border-color: var(--color-text-inverse); }

/* Ratings with no written reviews (§12.2.5). Not an error state - most people
   rate and move on - so it reads as an invitation, not a gap. */
.ratings-plain { display: grid; gap: 4px; justify-items: start; align-content: center; }
.ratings-plain b { font-size: 15px; }
.ratings-plain span { font-size: 12.5px; color: var(--text-2); }
.ratings-plain .get-btn { margin-top: 6px; }
