/* ============================================================
   components.css — shared UI chrome, ONE source of truth.
   Imported by both style.css (report pages) and app.css (threat-
   model view pages) so every page renders the same header, footer,
   etc. Page-type layout (fixed-canvas vs sticky-doc vs in-flow
   report) stays in the page CSS that owns that mode.
   ============================================================ */

/* ── Fonts (PP Neue Montreal) — ONE declaration set for the whole site.
   components.css is linked first on every page, so every page inherits these.
   (Previously redeclared in style.css, app.css, and the visual-summary page.) */
@font-face {
  font-family: "PP";
  src: url("fonts/PPNeueMontreal-Book.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "PP";
  /* Real italic face (not the upright Book file — else every <em> renders upright). */
  src: url("fonts/PPNeueMontreal-BookItalic.woff2") format("woff2");
  font-weight: 400; font-style: italic; font-display: swap;
}
@font-face {
  font-family: "PP";
  src: url("fonts/PPNeueMontreal-Medium.woff2") format("woff2");
  font-weight: 500; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "PP";
  src: url("fonts/PPNeueMontreal-Thin.woff2") format("woff2");
  font-weight: 100; font-style: normal; font-display: swap;
}

/* Reserve the scrollbar gutter on EVERY page so the fixed header (logo +
   Download button) and the TOC sit at the same x in every section. Without
   this, macOS overlay scrollbars leave report pages full-width while the threat
   pages reserve a gutter — shifting the navbar between sections. */
html { scrollbar-gutter: stable; }
/* Mobile scrollbars are overlay (zero-width), so a reserved gutter is just an
   empty strip down the right edge — the "gap where the scrollbar would be".
   Drop it on phones (the desktop TOC-alignment reason doesn't apply there). */
@media (max-width: 768px) { html { scrollbar-gutter: auto; } }

/* ── Canonical design tokens (Figma variables) ─────────────── */
:root {
  /* ink */
  --ink:        #0d0d0d;
  --ink-body:   #333333;
  --ink-2:      #444444;
  --ink-muted:  #666666;
  --ink-3:      #888888;   /* the "#888" secondary-UI grey tier (icons, hints) */
  --ink-faint:  #aaaaaa;
  --ink-inverse: #ffffff;  /* text/icon ON an --ink-coloured surface (e.g. filled pills) */
  /* surface */
  --surface-white:   #ffffff;
  --surface-bg:      #fafafa;
  --surface-panel:   #f8f8f8;
  --surface-page:    #f3f5f4;
  --surface-overlay: #ffffff;   /* drawers / popovers / menus */
  --border:          #e6e6e6;
  --dot-color:       #9c9e9d;   /* the dotted-grid page-background dot */
  /* brand */
  --mint:        #17cfb9;
  --mint-deep:   #0fa995;
  --teal-deep:   #0a7d6c;   /* deeper-than-mint ink for links, citations, outcome text */
  /* radius */
  --r-sm:   8px;
  --r-pill: 999px;
  /* layout */
  --header-h: 64px;
  --subnav-h: 48px;   /* report-page section sub-nav, fixed below the header */
  /* Section sub-nav band (Figma 130:2117, light theme redesign): a LIGHT sage-grey
     strip — surface-page (#f3f5f4) at 90% over sage #7d8c84 — with DARK links
     (active = --ink, inactive = --ink-2 @ 50%) + a 2px --ink-2 active underline.
     (Was a dark sage strip with white links; the AA story flips with the text.) */
  --subnav-bg: linear-gradient(rgba(243, 245, 244, 0.9), rgba(243, 245, 244, 0.9)), #7d8c84;
  /* elevation — shared by chapter cards + action buttons (resting + hover lift) */
  --shadow-card:       0 4px 8px -2px rgba(16,24,40,0.10), 0 2px 4px -2px rgba(16,24,40,0.06);
  --shadow-card-hover: 0 8px 18px -8px rgba(16,24,40,0.12), 0 3px 8px -4px rgba(16,24,40,0.07);
  /* softer, flatter elevation for the pill/button family (report buttons, diagram
     UI pills, legend, search) — one shared, restrained shadow so they all match. */
  --shadow-pill:       0 2px 6px -2px rgba(16,24,40,0.08), 0 1px 2px -1px rgba(16,24,40,0.05);
  /* ── Interactive card system ──────────────────────────────────────────────
     One vocabulary for every clickable card across the diagram surfaces (Visual
     Summary .ml-card, standard-view .mtm-node, detail-view .map-node) so they read
     as one premium UI system, harmonised with the report's chapter cards / prev-next
     buttons. Resting = stronger-than-default border + a soft shadow; hover lifts with
     the Visual Summary shadow; active (a card that has opened its drawer / is selected)
     firms the border to near-black with no lift. Colour-coded "special" cards keep this
     same white base and carry their category colour as a left bar (--card-bar-w). */
  --card-border:        #d4d4d4;   /* darker/stronger than --border for a crisp edge */
  --card-radius:        10px;
  --card-shadow:        0 1px 1.5px rgba(16,24,40,0.10), 0 1px 1px rgba(16,24,40,0.06);  /* = VS Figma Dropshadow/S — canonical white-card resting elevation */
  --card-shadow-hover:  0 6px 16px rgba(16,24,40,0.10);
  --card-lift:          -3px;
  --card-active-border: #1a1a1a;   /* = --diag-selection; the "this one is active" edge */
  --card-active-shadow: 0 4px 12px rgba(16,24,40,0.12);
  --card-bar-w:         4px;        /* colour bar width for special/category cards */
  /* motion (Emil) */
  --ease-out:    cubic-bezier(0.23, 1, 0.32, 1);
  --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);
  /* micro-interaction tokens — one vocabulary for drawer/content reveals so every
     surface cascades with the same rhythm. */
  --stagger:     40ms;   /* per-item delay in a cascade (drawer rows, reveals) */
  --blur-mask:   2px;    /* blur that bridges a content crossfade (Emil's "one object") */
  --dur-reveal:  340ms;  /* drawer row / content reveal duration */
  /* transitions.dev tokens — sub-nav sliding active-indicator (#16). */
  --tabs-dur:         250ms;
  --tabs-ease:        cubic-bezier(0.22, 1, 0.36, 1);
  /* focus */
  --focus-ring: #1a1a1a;
}

/* Native form controls / scrollbars are light-only. */
:root { color-scheme: light; }

/* ── Eyebrow / kicker — the ONE small-caps tracked label used across the whole
   site. Every eyebrow had its own copy of these four props; they now live here
   once. Colour, line-height, margin and layout stay on each selector (they vary
   by context), so this only owns the shared typography. `.eyebrow` is the class
   for new markup; the rest fold the existing report + diagram one-offs in.
   ── 11px family ── */
.eyebrow,
.hero .meta, .section-label, .card-label,
.article-page-header .breadcrumb, .chapter-nav-item .nav-dir,
.pandoc-content section.footnotes::before,
.pandoc-content details.references summary, details.example-foldout > summary,
.app-eyebrow, .app-box-eyebrow, .tm-mit-eyebrow {
  font-size: 11px; font-weight: 500; letter-spacing: 0.18em; text-transform: uppercase;
}
/* ── 9.5px family (denser sub-heads / badges) ── */
.eyebrow--sm,
.legend__section-head, .tm-search-badge {
  font-size: 9.5px; font-weight: 600; letter-spacing: 0.2em; text-transform: uppercase;
}

/* ── Global keyboard focus indicator ───────────────────────────
   One high-contrast ring for every interactive element, keyboard-only
   (:focus-visible) so mouse users are unaffected. outline-offset places the
   ring on the surrounding (light) background, so it stays visible even around
   dark controls like the Next pill; outline (not box-shadow) is never clipped
   by overflow:hidden ancestors. */
:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
  border-radius: 2px;
}

/* Skip-to-content link — injected as the first focusable element by
   site-header.js; hidden until focused, then slides in over the fixed header. */
.app-skip-link {
  position: fixed; top: 8px; left: 8px; z-index: 200;
  padding: 9px 16px; border-radius: var(--r-sm);
  background: var(--ink); color: var(--ink-inverse);
  font: 500 13px/1 "PP", -apple-system, sans-serif; letter-spacing: 0.015em;
  text-decoration: none;
  transform: translateY(-160%);
  transition: transform 0.16s var(--ease-out);
}
.app-skip-link:focus { transform: translateY(0); }

/* ============================================================
   Footer (Figma 12:19)
   ------------------------------------------------------------
   Opaque page-bg fill masks the dotted grid; flat, with a single
   2px mint line across the top. Base position is static (report
   pages ride the normal flow); threat pages override to fixed
   (canvas) / sticky (doc) in app.css.
   ============================================================ */
.app-footer { background: var(--surface-page); }
.app-footer-inner {
  position: relative; max-width: 1020px; margin: 0 auto;
  background: var(--surface-page);
  padding: 22px 40px 18px; display: flex; align-items: flex-end; justify-content: space-between;
}
.app-footer-inner::before {
  content: ""; position: absolute; inset: 0 0 auto 0; height: 2px; background: var(--mint);
}
.app-footer-col { display: flex; flex-direction: column; gap: 4px; }
.app-footer-col--right { align-items: flex-end; text-align: right; }
.app-footer-title { margin: 0; font-size: 12.5px; font-weight: 500; line-height: 1.3; letter-spacing: 0.02em; color: var(--ink); }
.app-footer-meta { margin: 0; font-size: 13px; font-weight: 400; line-height: 1.45; letter-spacing: 0.015em; color: var(--ink-body); }
.app-footer-contact { margin: 0; font-size: 12px; font-weight: 400; line-height: 1.4; letter-spacing: 0.02em; color: var(--ink-body); }
.app-footer-link { font-size: 12px; font-weight: 400; line-height: 1.4; letter-spacing: 0.02em; color: var(--mint-deep); text-decoration: none; }
.app-footer-link:hover { text-decoration: underline; }

/* ============================================================
   Header — fixed top bar (same on every page type).
   ============================================================ */
.app-header {
  position: fixed; inset: 0 0 auto 0; height: var(--header-h); z-index: 100;
  background: var(--surface-white);
  border-bottom: 1px solid var(--border);
}
.app-header-inner {
  max-width: 1100px; height: 100%; margin: 0 auto; padding: 0 32px;
  display: flex; align-items: center; justify-content: space-between;
}
.app-logo { flex: 0 0 auto; display: flex; }
/* display:block kills the inline baseline gap so the logo box is exactly the
   image height (30px) and sits identically on EVERY page. Without it, pages that
   don't load app.css (Home / Q&A) render the img inline → a taller box + vertical
   shift vs the threat-model pages (which get app.css's global img{display:block}).
   Setting it here in the shared header stylesheet makes the header pixel-identical. */
.app-logo img { width: 109px; height: 30px; object-fit: contain; display: block; }
.app-nav { display: flex; align-items: center; gap: 32px; height: 100%; }
.app-nav-link {
  /* Figma "Nav Link" = 08 · UI type: 12.5px / Medium 500 / 0.02em, ink/muted. */
  display: flex; align-items: center; gap: 6px; height: 100%; padding: 10px 0;
  text-decoration: none; font-size: 12.5px; font-weight: 500; letter-spacing: 0.02em;
  line-height: 1.3;
  color: var(--ink-muted); border-bottom: 2px solid transparent;
  transition: color .18s var(--ease-out);
}
.app-nav-link:hover { color: var(--ink); }
.app-nav-link.is-active { color: var(--ink); border-bottom-color: var(--mint-deep); }
/* Inline icon (e.g. Threat Modelling git-pull-request) — 14px, follows label colour. */
.app-nav-link svg { width: 14px; height: 14px; display: block; flex: 0 0 auto; }
/* Download PDF — Figma "Button / Outline": white, hairline border (surface/border),
   ink/muted #666 label, 32px tall, 12.5/500/0.02em. */
.app-nav-pdf {
  display: inline-flex; align-items: center; gap: 6px; height: 32px; padding: 6px 14px;
  border-radius: var(--r-pill);
  background: var(--surface-white); color: var(--ink-muted); border: 1px solid var(--border);
  font-size: 12.5px; font-weight: 500; line-height: 1.3; letter-spacing: 0.02em;
  text-decoration: none;
  transition: background .18s var(--ease-out), border-color .18s var(--ease-out), color .18s var(--ease-out), transform .16s var(--ease-out);
}
.app-nav-pdf:hover { background: var(--surface-bg); border-color: var(--card-border); color: var(--ink); }
.app-nav-pdf:active { transform: scale(0.97); }

/* Logo as a CSS-masked shape (figures/logo.svg) so it inherits --ink (one source
   of truth for the mark colour) rather than shipping a baked-colour raster. */
.app-logo-mark {
  display: block; width: 108px; height: 30px;
  background-color: var(--ink);
  -webkit-mask: url("figures/logo.svg") left center / contain no-repeat;
          mask: url("figures/logo.svg") left center / contain no-repeat;
}

/* Right-side header cluster: nav + (mobile) hamburger. */
.app-header-right { display: flex; align-items: center; gap: 22px; height: 100%; }

/* ── Sub-navigation ─────────────────────────────────────────────
   The 3 thematic section links (What is LoC / How to Prevent / The Future),
   injected by site-header.js on report pages only. Fixed directly below the
   64px header so it stays put while the article scrolls. Report-page content
   clears header-h + subnav-h (see .hero / .chapter-page padding in style.css). */
.app-subnav {
  position: fixed; top: var(--header-h); left: 0; right: 0; z-index: 99;
  height: var(--subnav-h);
  background: var(--surface-white);                 /* white, matching the header (was the sage --subnav-bg) */
  border-bottom: 1px solid var(--border);           /* divider under the strip, like threat-model 5's row-2 */
}
.app-subnav-inner {
  position: relative;   /* anchors the sliding active-indicator inside the track */
  max-width: 1100px; height: 100%; margin: 0 auto; padding: 0 32px;
  display: flex; align-items: center; gap: 32px;
  /* Draggable at every width: the section list can exceed the row, so it scrolls.
     `safe center` centres it when it fits but aligns to the start when it
     overflows (plain `center` would clip the first item out of reach). The drag
     handler in site-header.js adds click-and-drag for mouse/pointer. */
  justify-content: safe center;
  overflow-x: auto; -webkit-overflow-scrolling: touch;
  touch-action: pan-x; cursor: grab; scrollbar-width: none;
}
.app-subnav-inner.is-grabbing { cursor: grabbing; }
.app-subnav-inner::-webkit-scrollbar { display: none; }
.app-subnav-link { flex: 0 0 auto; white-space: nowrap; }
/* Figma "Nav Link" on the sub-nav band (130:2119): 11.5/500/0.02em.
   Inactive = --ink-2 @ 50%, active = --ink @ full + a 2px --ink-2 underline. The
   transparent border on every link reserves the underline's 2px so active/inactive
   stay the same height. */
.app-subnav-link {
  display: inline-flex; align-items: center; height: 100%;
  /* Font matches the main nav link (12.5px / Medium 500 / 0.02em / 1.3). */
  font-size: 12.5px; font-weight: 500; letter-spacing: 0.02em; line-height: 1.3;
  color: var(--ink-2); opacity: 0.5; text-decoration: none;
  border-bottom: 2px solid transparent;
  transition: opacity .18s var(--ease-out), color .18s var(--ease-out), border-color .18s var(--ease-out);
}
.app-subnav-link:hover { opacity: 0.8; }
.app-subnav-link.is-active { color: var(--ink); opacity: 1; border-bottom-color: var(--ink-2); }

/* Sliding active-indicator (transitions.dev #16, tabs-sliding). Each sub-nav link
   navigates to a separate page rather than switching in place, so the *visible*
   slide is the hover/focus follow — the underline chases the pointer across the
   strip and settles under the current page's active link on mouse-out. A 2px
   white underline (not the skill's filled pill) matches the sage strip's existing
   active treatment and keeps the 11.5px labels legible. JS writes width +
   translateX; CSS owns the tween. Lives inside the scrolling track so it stays
   aligned when the strip scrolls/drag-scrolls on mobile. */
.app-subnav-indicator {
  /* Disabled: the sliding underline chasing the pointer read as the active bar
     "jumping around". The label opacity (hover 0.8 / active 1) carries the state
     on its own. Kept in the DOM (JS still measures it) but not painted. */
  display: none;
  position: absolute; bottom: 0; left: 0;
  height: 2px; width: 0;
  background: #fff; border-radius: 2px 2px 0 0;
  transform: translateX(0); opacity: 0;
  transition:
    transform var(--tabs-dur) var(--tabs-ease),
    width     var(--tabs-dur) var(--tabs-ease),
    opacity   var(--tabs-dur) var(--tabs-ease);
  will-change: transform, width;
  pointer-events: none;
}

/* On phones the 3 long section labels can't sit centred side by side, so the bar
   stays visible but scrolls horizontally (left-aligned), keeping the section
   navigation reachable rather than dropping it. */
@media (max-width: 768px) {
  /* Scroll/drag behaviour is now in the base rule; phones just tighten spacing. */
  .app-subnav-inner { gap: 24px; padding: 0 20px; }
}

/* ── Mobile hamburger ───────────────────────────────────────────
   On narrow viewports the primary nav collapses into a hamburger that opens the
   links + Download PDF as a sheet below the header. */
.app-nav-toggle {
  display: none; width: 40px; height: 40px; padding: 0; margin-right: -8px;
  border: 0; background: transparent; color: var(--ink); cursor: pointer;
  align-items: center; justify-content: center;
}
.app-nav-burger, .app-nav-burger::before, .app-nav-burger::after {
  content: ""; display: block; width: 20px; height: 2px; border-radius: 2px;
  background: currentColor; transition: transform .18s var(--ease-out), opacity .12s var(--ease-out);
}
.app-nav-burger { position: relative; }
.app-nav-burger::before { position: absolute; left: 0; top: -6px; }
.app-nav-burger::after  { position: absolute; left: 0; top:  6px; }
/* open = X */
.app-header.is-menu-open .app-nav-burger { background: transparent; }
.app-header.is-menu-open .app-nav-burger::before { transform: translateY(6px) rotate(45deg); }
.app-header.is-menu-open .app-nav-burger::after  { transform: translateY(-6px) rotate(-45deg); }

@media (max-width: 768px) {
  /* Shorter, lighter header on phones (Figma mobile). Driving it through
     --header-h cascades to body padding, the sub-nav offset, and the
     report-page top padding (all keyed off the token) in one place. */
  :root { --header-h: 56px; }
  .app-logo img { width: 90px; height: 25px; }
  .app-nav-toggle { display: inline-flex; }
  .app-nav {
    position: absolute; top: 100%; left: 0; right: 0;
    flex-direction: column; align-items: stretch; gap: 0; height: auto;
    padding: 6px 0;
    background: var(--surface-white); border-bottom: 1px solid var(--border);
    box-shadow: 0 10px 24px rgba(16,24,40,0.10);
    display: none;
  }
  .app-header.is-menu-open .app-nav { display: flex; }
  .app-nav-link { height: auto; padding: 13px 24px; border-bottom: 0; font-size: 15px; color: var(--ink); }
  .app-nav-link.is-active { border-bottom: 0; color: var(--mint-deep); }
  .app-nav-pdf { display: inline-flex; justify-content: center; margin: 8px 24px 8px; }
}

/* ============================================================
   Drawer — the ONE right-side slide-in panel used everywhere
   ------------------------------------------------------------
   Card-detail (Visual Summary) and mitigations/search (Detail).
   Shared mechanics + look: white panel, slides in from the right,
   layers above the TOC (z70), and casts a shadow ONLY when open
   (so a closed, off-screen panel doesn't bleed onto the edge).
   Each page sets its own position/top/width (fixed-below-header on
   Detail, absolute-in-stage on Visual Summary).
   Shared behaviour: site-drawer.js dismisses any open .app-drawer on an
   outside click (it triggers the drawer's own .app-drawer-close), so every
   drawer closes the same way — page just needs the .app-drawer-close button.
   ============================================================ */
.app-drawer {
  background: var(--surface-overlay);
  border-left: 1px solid var(--border);
  z-index: 70;
  transform: translateX(100%);
  /* box-shadow on the base (not just .is-open) so the elevation fades out WITH the
     slide on close instead of popping off the instant the class is removed. */
  transition: transform 0.26s var(--ease-out), box-shadow 0.26s var(--ease-out);
}
.app-drawer.is-open { transform: translateX(0); box-shadow: -8px 0 28px rgba(0,0,0,0.08); }

/* Shared drawer close button — the ONE circular ✕ used by every sidebar drawer
   (Visual Summary card detail, Detail map mitigations + search). Look only; each
   drawer positions it top-right. (Bubbles/popovers keep their own plain ✕.) */
.app-drawer-close {
  width: 28px; height: 28px; border-radius: var(--r-pill);
  border: 1px solid var(--border); background: var(--surface-white);
  color: var(--ink-3); font-size: 12px; cursor: pointer; line-height: 1;
  display: flex; align-items: center; justify-content: center;
  transition: background .13s var(--ease-out), color .13s var(--ease-out),
              border-color .13s var(--ease-out), transform .16s var(--ease-out);
}
/* The ✕ rotates a quarter-turn toward "close" on hover — a small acknowledgement.
   Composed with the press-scale so the two don't overwrite each other. */
.app-drawer-close:hover { background: var(--surface-panel); color: var(--ink); transform: rotate(90deg); }
.app-drawer-close:active { transform: rotate(90deg) scale(0.94); }

/* Drawer content reveal — shared keyframe so every sidebar drawer cascades its content
   (eyebrow → title → rows) with one rhythm when it opens or swaps to a new node. Each
   drawer's CSS sets per-item animation-delay via --i. Base opacity is 1, so the global
   reduced-motion reset (which zeroes the duration) leaves content fully visible. */
@keyframes drawer-content-in {
  from { opacity: 0; transform: translateY(8px); filter: blur(var(--blur-mask)); }
  to   { opacity: 1; transform: translateY(0);    filter: blur(0); }
}

/* Tooltip on icon-only controls (zoom ±, etc.). Pure CSS: label from [data-tooltip],
   appears above the control after a short delay, leaves instantly (no delay) so a
   toolbar sweep doesn't trail tooltips. Scales from its bottom edge (origin-aware). */
[data-tooltip] { position: relative; }
[data-tooltip]::after {
  content: attr(data-tooltip);
  position: absolute; left: 50%; bottom: calc(100% + 9px);
  transform: translateX(-50%) translateY(3px) scale(0.96);
  transform-origin: 50% 100%;
  padding: 5px 8px; border-radius: 6px;
  background: var(--ink); color: var(--ink-inverse);
  font: 500 11px/1 "PP", -apple-system, sans-serif; letter-spacing: 0.01em;
  white-space: nowrap; pointer-events: none; opacity: 0;
  transition: opacity .12s var(--ease-out), transform .12s var(--ease-out);
  z-index: 80;
}
[data-tooltip]:hover::after, [data-tooltip]:focus-visible::after {
  opacity: 1; transform: translateX(-50%) translateY(0) scale(1);
  transition-delay: .4s;   /* delay-in; leaving has no delay → instant-out */
}

/* ============================================================
   Reduced motion — shared across every page (report + threat).
   ============================================================ */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation-duration: 0.001ms !important; transition-duration: 0.001ms !important; }
  /* skip the cross-fade — still no white flash, just an instant swap */
  ::view-transition-old(root), ::view-transition-new(root) { animation: none; }
}
