/* Shared marketing components — the single source for site chrome (nav,
   footer, skip-link) and the spec-sheet primitives (.btn, .tag, .card,
   .section, .wrap, .cap, .exhibit, .stamp, .code-block). Loaded globally from
   base.njk and by the scanner Pages Function (functions/scan/[domain].ts).
   Page stylesheets (index.css, learn.css, legal.css, scan.css) hold
   page-specific styles ONLY — never redefine these components there.
   Design tokens come from the shared /tokens.css; the logo @keyframes from
   /animations.css. Single source: packages/design-tokens — see adr/0016,
   and DESIGN.md for the system these primitives render. */

/* ---------- base ---------- */
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
html, body { background: hsl(var(--paper)); color: hsl(var(--ink)); }
/* The nav is sticky, so an in-page anchor (#pricing, #how-it-works) would land
   its target under the header. Clear the nav plus a little air, so a jumped-to
   section opens with the same breathing room as a scrolled-to one. */
html { scroll-padding-top: 100px; }
body { font-family: var(--font-sans); font-size: 17px; line-height: 1.55; }
::selection { background: hsl(var(--pop-soft)); color: hsl(var(--ink)); }

/* The measure: the width of the content column, gutters excluded.
   Sections stay full-bleed so a ground colour reaches both edges of the window;
   the column is produced by the section's own inline padding, not by centring
   each child. Centring the children looked right for a full-width block and
   wrong for a narrow one — a lede capped at 58ch ended up floating in the
   middle of the page instead of sitting flush with the headline above it.
   The percentage (not 100vw) keeps the scrollbar out of the sum. */
:root { --wrap: 1072px; --gutter: max(24px, calc((100% - var(--wrap)) / 2)); }
.wrap { max-width: var(--wrap); margin-inline: auto; }
a { color: inherit; text-decoration: none; transition: text-decoration-color var(--motion-quick) ease; }
/* Hover keeps the text ink and moves Hazard Orange onto the underline: orange
   text on paper is 3.0:1 and fails AA at every size, while an orange rule under
   ink text is legal. Same idiom as .nav-links a:hover (an orange bottom border). */
a:not(.btn):not(.tier-cta):not(.brand):hover {
  text-decoration: underline;
  text-decoration-color: hsl(var(--pop));
  text-decoration-thickness: 1.5px;
  text-underline-offset: 3px;
}
/* Focus-visible, not :focus — a mouse click on a button should not leave the
   Hazard Orange ring behind. Keyboard and AT navigation still get it. */
button:focus-visible, a:focus-visible, input:focus-visible { outline: 2px solid hsl(var(--pop)); outline-offset: 3px; }
/* Headings are mono at 500. A heading is structure, and structure speaks in
   the machine's voice — see DESIGN.md, The Two Voices Rule. */
h1, h2, h3 { font-family: var(--font-mono); font-weight: 500; letter-spacing: -0.02em; text-wrap: balance; }
code, pre, .mono { font-family: var(--font-mono); }

/* Keyboard-only skip link — visually hidden until focused. */
.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  background: hsl(var(--ink));
  color: hsl(var(--paper));
  padding: 10px 16px;
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.04em;
  z-index: 100;
}
.skip-link:focus { left: 0; }

/* ---------- primitives ---------- */
.tag {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: hsl(var(--ink));
  display: inline-block;
  white-space: nowrap;
}
.tag--bordered {
  border: var(--rule-width) solid hsl(var(--ink));
  border-radius: var(--radius);
  padding: 4px 8px;
}
.tag--filled {
  background: hsl(var(--ink));
  color: hsl(var(--paper));
  padding: 4px 10px;
}
/* INK FIELDS ONLY. Hazard Orange text is 3.0:1 on paper (fails AA at every
   size) and 4.9:1 on ink — never put this modifier on a paper surface.
   Currently unused in markup. */
.tag--pop {
  color: hsl(var(--pop));
}

.card {
  background: hsl(var(--paper));
  color: hsl(var(--ink));
  border: var(--rule-width) solid hsl(var(--ink));
  border-radius: var(--radius);
  padding: 24px;
}
.card--accent {
  background: hsl(var(--ink));
  color: hsl(var(--paper));
}

.btn {
  font-family: var(--font-mono);
  font-size: 14px;
  font-weight: 500;
  padding: 11px 18px;
  border-radius: var(--radius);
  background: transparent;
  color: hsl(var(--ink));
  border: var(--rule-width) solid hsl(var(--ink));
  cursor: pointer;
  letter-spacing: 0.04em;
  white-space: nowrap;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  line-height: 1;
  text-decoration: none;
}
.btn--primary {
  background: hsl(var(--ink));
  color: hsl(var(--paper));
}
.btn--big {
  font-size: 15px;
  padding: 15px 24px;
}
/* The nav size. Small enough that two of them sit in a 60px bar. */
.btn--sm { font-size: 12.5px; padding: 7px 12px; }
/* A button that is really a link — the quiet half of a CTA pair. */
.btn--text {
  border-color: transparent;
  color: hsl(var(--ink-muted));
  padding-inline: 8px;
}
.btn--pop {
  background: hsl(var(--pop));
  color: hsl(var(--ink));
  border-color: hsl(var(--pop));
  padding: 15px 24px;
  font-size: 15px;
}
/* Pre-launch gate: registration is closed until launch — see _includes/base.njk */
.is-soon {
  opacity: 0.5;
  cursor: not-allowed;
  pointer-events: none;
  box-shadow: none;
}

/* Hover is a background shift now. The plate that used to grow under a lifted
   box is gone, so a control that does not change colour has no hover at all —
   these four rules are the whole affordance. Focus is the :focus-visible ring
   further down; nothing may cancel it. */
.btn, .tier-cta {
  transition: background-color var(--motion-quick) ease,
              border-color var(--motion-quick) ease,
              color var(--motion-quick) ease;
}
@media (hover: hover) {
  .btn:hover, .tier-cta:hover { background: hsl(var(--paper-3)); }
  .btn--text:hover { background: transparent; color: hsl(var(--ink)); }
  .btn--primary:hover { background: hsl(var(--ink) / 0.9); }
  .btn--pop:hover { background: hsl(var(--pop-deep)); border-color: hsl(var(--pop-deep)); }
}
/* No reduced-motion override here: a 150ms background fade is not movement,
   and cancelling it would leave the control with no hover feedback at all.
   What reduced motion switches off is collected at the end of this file. */

/* Sections stay full-bleed so a ground colour reaches both edges of the window;
   every direct child is centred in the 1120px measure. That is what lets a dark
   section span the viewport without a wrapper div in every template.
   The separator is a hairline, not an ink rule: the rules here divide readings,
   they are not shouting. */
.section, .section--hero, .section--explain {
  padding-block: clamp(72px, 10vw, 128px);
  padding-inline: var(--gutter);
}
.section { border-bottom: 1px solid hsl(var(--hairline)); }
.section--hero    { padding-block: clamp(44px, 6vw, 80px) clamp(56px, 8vw, 96px); }
/* There is no `--dark` variant any more. Every page runs on paper; the only ink
   fields left are specimens (.code-block), which is what DESIGN.md means by a
   surface asserting itself. An ink *band* was the old system's rhythm. */
.section--explain { }
.section--paper2  { background: hsl(var(--paper-2)); }
/* Bottom rule for sections that don't carry the base .section border. */
.section--ruled   { border-bottom: 1px solid hsl(var(--hairline)); }

.section-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 16px;
  margin-bottom: clamp(36px, 5vw, 56px);
}
.section-head h2 {
  font-size: clamp(26px, 3.2vw, 36px);
  line-height: 1.15;
  margin: 0;
  max-width: 28ch;
}
.section-head--compact { margin-bottom: 32px; }

.footnote {
  font-family: var(--font-mono);
  font-size: 12.5px;
  color: hsl(var(--ink-muted));
}

/* The lede under a headline. Wider than body copy is allowed to run, quieter
   than the headline, and capped at a readable measure. */
.lede {
  font-size: 19px;
  max-width: 58ch;
  color: hsl(var(--ink-muted));
}

/* The paper-safe orange, as a class. Three headlines use it to lift their
   second line; --pop itself is 3.0:1 on paper and fails AA as type. */
.pop-text { color: hsl(var(--pop-text)); }

/* An eyebrow with the one orange dot the hero is allowed. */
.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: hsl(var(--ink-muted));
}
.eyebrow .dot { width: 8px; height: 8px; border-radius: 50%; background: hsl(var(--pop)); }

/* An exhibit is a specimen the reader inspects: a header, a table of origins, a
   scorecard. Light ground with a hairline, so it reads as a page in the spec
   sheet rather than as a terminal dropped into the page. */
.exhibit {
  border: 1px solid hsl(var(--hairline));
  border-radius: var(--radius);
  background: hsl(var(--paper-2));
  font-family: var(--font-mono);
  font-size: 13.5px;
  line-height: 1.6;
  overflow-x: auto;
}
.exhibit pre { margin: 0; padding: 18px 20px; }
.exhibit .status {
  display: flex;
  gap: 8px 18px;
  flex-wrap: wrap;
  align-items: center;
  padding: 10px 20px;
  border-top: 1px solid hsl(var(--hairline));
  color: hsl(var(--ink-muted));
  font-size: 12.5px;
}
.exhibit .dim { color: hsl(var(--ink-muted)); }
.exhibit .kw-line { color: hsl(var(--ink-muted)); }

/* The caption strip on top of an exhibit: what this specimen is, and one fact
   about it on the right. */
.cap {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 10px;
  min-height: 38px;
  padding: 6px 18px;
  border-bottom: 1px solid hsl(var(--hairline));
  font-family: var(--font-mono);
  font-size: 11.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: hsl(var(--ink-muted));
}
.cap .meta {
  text-transform: none;
  letter-spacing: 0;
  color: hsl(var(--ink-muted));
  font-variant-numeric: tabular-nums;
}

/* The scanner's tier badge. Shared by /scan, the scorecard Pages Function and
   the homepage's example — the product grades Strong / Good / Basic and has no
   letter grades, so these three are the whole vocabulary. */
.grade-badge {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 5px 12px;
  border-radius: var(--radius);
}
.grade-strong { background: hsl(var(--ok)); color: hsl(var(--paper)); }
.grade-good   { background: hsl(var(--ink)); color: hsl(var(--paper)); }
.grade-basic  { background: hsl(var(--pop)); color: hsl(var(--ink)); }

/* A stamp records a verified state. Orange outline with --pop-text inside it —
   the fill would make it the screen's one orange mark, and it is not. */
.stamp {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 2px 7px;
  border: 1px solid hsl(var(--pop));
  border-radius: var(--radius);
  color: hsl(var(--pop-text));
}

/* Specimen tables: an origin list, a scorecard, a tier comparison. */
/* A specimen table holds machine strings that must not wrap to stay readable,
   so it keeps its own width and the exhibit scrolls. The min-width is what makes
   that scroll real rather than a clipped last column. */
.exhibit table { border-collapse: collapse; width: 100%; min-width: 560px; font-variant-numeric: tabular-nums; }
.exhibit th, .exhibit td {
  text-align: left;
  padding: 9px 14px;
  border-bottom: 1px solid hsl(var(--hairline));
  white-space: nowrap;
}
.exhibit th {
  font-weight: 500;
  font-size: 11.5px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: hsl(var(--ink-muted));
}
.exhibit td.n, .exhibit th.n { text-align: right; }
.exhibit tr:last-child td { border-bottom: 0; }

/* The homepage's copy of the dashboard's Triage table. Keep it matching
   csp-dashboard Projects/Show.vue when that table changes. */
.triage td { vertical-align: top; }
.triage thead { background: hsl(var(--paper)); }
.triage .dir, .triage .what, .triage .chip, .triage .conf {
  font-size: 10.5px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.triage .dir { display: block; color: hsl(var(--ink-muted)); }
.triage .origin { display: block; font-weight: 700; }
.triage .meta { display: block; font-size: 11px; color: hsl(var(--ink-muted)); }
.triage .what { color: hsl(var(--ink-muted)); }
.triage .chip { display: inline-block; padding: 1px 6px; border-radius: var(--radius); }
.triage .chip--allow  { background: hsl(var(--ok) / 0.14); color: hsl(var(--ok)); }
.triage .chip--ignore { background: hsl(var(--ink) / 0.07); color: hsl(var(--ink-muted)); }
.triage .conf {
  padding: 1px 4px;
  border: 1px solid hsl(var(--ink) / 0.2);
  background: hsl(var(--ink) / 0.05);
  color: hsl(var(--ink-muted));
}
.triage .dash { color: hsl(var(--ink-muted)); }

.inline-code {
  font-family: var(--font-mono);
  font-size: 0.92em;
  background: hsl(var(--paper-2));
  border-radius: var(--radius);
  padding: 1px 6px;
}

/* ---------- code block ---------- */
.code-block {
  background: hsl(var(--ink));
  color: hsl(var(--paper));
  font-family: var(--font-mono);
  font-size: 13px;
  line-height: 1.65;
  padding: 24px;
  border: var(--rule-width) solid hsl(var(--ink));
  border-radius: var(--radius);
  overflow-x: auto;
}
.code-block .dim { color: hsl(var(--muted-on-ink)); }
/* Orange is legal here because .code-block is an ink field (4.9:1). The two
   light variants — .hero .code-block (index.css) and .code-block--light
   (learn.css) — override .pop, because on paper it drops to 2.96:1. */
.code-block .pop { color: hsl(var(--pop)); }
.code-block .cb-label { color: hsl(var(--muted-on-ink)); margin-bottom: 8px; }
.code-block .cb-comment { color: hsl(var(--muted-on-ink)); font-style: italic; margin-top: 4px; }
.code-block .cb-indent { padding-left: 16px; }
.code-block .cb-spacer { height: 16px; }
.code-block .cb-directive { color: hsl(var(--code-keyword)); font-weight: 600; }
.code-block .cb-ok { color: hsl(var(--code-ok)); }
.code-block .cb-danger { color: hsl(var(--code-danger)); font-weight: 600; }

/* ---------- nav ---------- */
.nav {
  position: sticky;
  top: 0;
  z-index: 50;
  border-bottom: 1px solid hsl(var(--hairline));
  padding: 10px 24px;
  min-height: 60px;
  display: flex;
  align-items: center;
  gap: 24px;
  background: hsl(var(--paper));
}
/* The bar is full-bleed so its rule reaches both edges; its contents line up
   with the section measure underneath. */
.nav, .site-footer { padding-inline: var(--gutter); }
.brand {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: 18px;
  font-weight: 600;
  letter-spacing: -0.02em;
  text-decoration: none;
}
/* There is no mark beside the wordmark. Both approved mockups render the brand
   as text and nothing else; the underscore is the device. */
/* The wordmark sits on paper in both the nav and the footer, where --pop is
   3.0:1 and fails AA as text. --pop-text is the paper-safe orange (5.1:1). */
.brand .dot { color: hsl(var(--pop-text)); }
.nav-links {
  display: flex;
  gap: 22px;
  font-family: var(--font-mono);
  font-size: 13.5px;
}
.nav-links a {
  color: hsl(var(--ink-muted));
  text-decoration: none;
  border-bottom: var(--rule-width) solid transparent;
  padding-bottom: 2px;
  transition: border-color var(--motion-quick) ease;
}
/* The nav already carries the orange-on-hover marker as a bottom border, so
   suppress the global underline hover to avoid doubling the rule. */
.nav-links a:hover { color: hsl(var(--ink)); border-bottom-color: hsl(var(--pop)); text-decoration: none; }
.nav-ctas { display: flex; align-items: center; gap: 10px; margin-left: auto; font-family: var(--font-mono); font-size: 13.5px; }
/* The secondary action is a plain link, not a second outlined box. Two bordered
   controls in a 60px bar read as a toolbar. */
.nav-ctas .plain { color: hsl(var(--ink-muted)); margin-right: 6px; }
.nav-ctas .plain:hover { color: hsl(var(--ink)); }
.nav-hamburger {
  display: none;
  flex-direction: column;
  justify-content: space-between;
  width: 28px;
  height: 22px;
  padding: 0;
  background: transparent;
  border: 0;
  cursor: pointer;
}
.nav-hamburger span {
  display: block;
  height: 2.5px;
  width: 100%;
  background: hsl(var(--ink));
  border-radius: 1px;
  transition: transform var(--motion-panel) var(--motion-ease),
              opacity var(--motion-panel) var(--motion-ease);
}
body.nav-open .nav-hamburger span:nth-child(1) { transform: translateY(9.5px) rotate(45deg); }
body.nav-open .nav-hamburger span:nth-child(2) { opacity: 0; }
body.nav-open .nav-hamburger span:nth-child(3) { transform: translateY(-9.5px) rotate(-45deg); }
.nav-menu {
  display: none;
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 48; /* below the header (z-index: 50) so the X stays clickable */
  flex-direction: column;
  gap: 16px;
  padding: 80px 24px 24px; /* top padding clears the sticky header; panel sizes to its content */
  background: hsl(var(--paper));
  border-bottom: var(--rule-width) solid hsl(var(--ink));
  font-family: var(--font-mono);
  font-size: 18px;
  /* The panel is fixed to the top edge and carries an ink rule along its
     bottom, so it is a drawer: it comes out from under the header and that
     drawn rule travels with it. 8px is the whole distance — enough to read as
     a drawer, short of anything that could read as floating (ADR-0052 deleted
     the lift). `display` is a discrete property, so it needs allow-discrete
     to hold the panel in flow until the fade has finished; without it the
     close would be instant. Closing runs at --motion-quick. */
  opacity: 0;
  transform: translateY(-8px);
  transition: opacity var(--motion-quick) var(--motion-ease),
              transform var(--motion-quick) var(--motion-ease),
              display var(--motion-quick);
  transition-behavior: allow-discrete;
}
.nav-menu a:not(.btn) {
  color: hsl(var(--ink));
  text-decoration: none;
}
body.nav-open .nav-menu {
  display: flex;
  opacity: 1;
  transform: translateY(0);
  transition-duration: var(--motion-panel);
}
/* A panel that was display:none has no previous style to transition from, so
   without this the opening would simply appear. This names what it opens from,
   and must stay after the rule above to win the cascade. */
@starting-style {
  body.nav-open .nav-menu { opacity: 0; transform: translateY(-8px); }
}
body.nav-open { overflow: hidden; }

/* ---------- footer ---------- */
/* The footer carries its own top hairline now, so pages no longer add one in
   their page CSS. A section that already ends on a rule simply meets it. */
.site-footer {
  padding-block: 36px 48px;
  border-top: 1px solid hsl(var(--hairline));
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 24px;
  flex-wrap: wrap;
  background: hsl(var(--paper));
  font-family: var(--font-mono);
  font-size: 12.5px;
  color: hsl(var(--ink-muted));
}
.site-footer a { color: hsl(var(--ink-muted)); text-decoration: none; }
.site-footer a:hover { color: hsl(var(--ink)); }
.footer-brand { font-weight: 600; letter-spacing: -0.02em; color: hsl(var(--ink-muted)); }
.footer-brand .dot { color: hsl(var(--pop-text)); }
.footer-links { display: flex; gap: 24px; }
.footer-account { display: flex; gap: 18px; }

/* ---------- responsive chrome ---------- */
@media (max-width: 719px) {
  .nav { gap: 12px; }
  :root { --gutter: 20px; }
  .site-footer { padding-block: 28px 36px; }
  /* The nav measures 70px here against 75px at wide widths. */
  html { scroll-padding-top: 80px; }

  .section-head h2 { font-size: clamp(28px, 8vw, 40px); }
  .section-head {
    flex-direction: column;
    align-items: flex-start;
    gap: 12px;
    margin-bottom: 32px;
  }
  .section-head .tag { order: -1; }

  .nav-links { display: none; }
  /* The conversion action stays in the sticky bar on phones — this is an
     acquisition surface, and a CTA behind a hamburger is a CTA nobody presses.
     Only the secondary action (log_in) folds into the panel. */
  /* Log in folds into the hamburger panel; Scan stays in the bar. */
  .nav-ctas .plain { display: none; }
  .nav-ctas .btn--primary { padding: 9px 12px; font-size: 13px; }
  .nav-hamburger { display: flex; }
  .brand { font-size: 16px; }

  .btn--pop, .btn--big { padding: 14px 20px; }
  .btn, .tier-cta { min-height: 44px; }

  .site-footer {
    flex-direction: column;
    align-items: flex-start;
    gap: 18px;
  }
  .footer-brand { order: 1; }
  .footer-account { order: 2; flex-wrap: wrap; }
  .footer-links { order: 3; flex-wrap: wrap; }
}

@media (max-width: 479px) {

  :root { --gutter: 16px; }

}

/* ---------- scan: the pending rule ---------- */
/* Both the homepage and /scan carry this form, and each styles it in its own
   sheet — so the one thing they share lives here.

   Submitting navigates to /scan/<domain>, which fetches a third-party site
   before it can render. The hero advertises that as ~5 seconds. Until this
   rule existed the page simply sat there, and a disabled button with a changed
   label still cannot distinguish "working" from "hung".

   The bar is not clipped by anything: it is 40% of the form and travels 150% of
   its own width, which lands its right edge exactly on the form's. That is why
   it sweeps back and forth rather than looping in one direction — there is no
   track to hide a re-entry in. Constant speed, because this is an instrument
   reading, not an interface easing. */
.scan-form { position: relative; }
.scan-form::after {
  content: "";
  position: absolute;
  inset-inline-start: 0;
  inset-block-end: -9px;
  inline-size: 40%;
  block-size: var(--rule-width);
  background: hsl(var(--pop));
  opacity: 0;
  transition: opacity var(--motion-quick) ease;
}
.scan-form.is-scanning::after {
  opacity: 1;
  animation: scanSweep 1.4s linear infinite alternate;
}
/* The label swaps to "Scanning…", which is 45px wider than "Scan". Reserve the
   room up front rather than letting the button grow on click: /scan's form is
   the one that wraps, and there the growth threw the button onto a line of its
   own at the exact moment the user was watching it. 16ch is the pending label
   plus the wider of the two paddings (/scan runs 28px where the homepage runs
   20px), measured rather than guessed.
   The cost is that the resting button is wider than its natural size on both
   pages, and that below ~420px /scan now wraps to two lines from the start
   instead of at the click. Stable beats snug in the second the page hangs. */
.scan-form button { min-width: 16ch; }

/* ---------- reduced motion ---------- */
/* Colour stays. A 150ms background fade is not what makes people ill, and
   cancelling it would leave every control without feedback. What goes is
   movement — and never the signal the movement was carrying: the scan rule
   stops sweeping and stands still under the form, so "working" still reads.
   See DESIGN.md -> Motion. */
@media (prefers-reduced-motion: reduce) {
  .nav-menu { transform: none; transition-property: opacity, display; }
  @starting-style {
    body.nav-open .nav-menu { transform: none; }
  }
  body.nav-open .nav-menu { transform: none; }
  .nav-hamburger span { transition-property: opacity; }
  .scan-form.is-scanning::after { animation: none; inline-size: 100%; }
}
