:root {
  /* --- Brand tokens ------------------------------------------------- */
  --color-bg: #FAF9F4; /* ivory */
  --color-surface: #FFFFFF; /* card surfaces, lifted off the ivory bg */
  --color-ink: #0F1115; /* primary text */
  --color-slate: #60635C; /* secondary / muted text — a hair darker than the original #63665F so it clears 4.5:1 even where hero text crosses a ring-texture line (4.70:1 on --color-border; 5.80:1 on ivory). See scripts/contrast-audit.mjs. */
  --color-border: #E4E2D8; /* hairline borders on ivory */
  --color-border-strong: #D6D3C6;
  --color-input-border: #8F9086; /* text-input outline only — a form field's boundary needs 3:1 (WCAG 1.4.11): 3.23:1 on the white card, 3.06:1 on the input's own ivory fill. --color-border-strong (1.50:1) stays for decorative hairlines. */
  --color-accent: #0B84F3; /* signal blue — brand mark, buttons, links, focus states. Was #03FA73 (green) before the logo refresh. */
  --color-accent-secondary: #00E0FF; /* the gradient's second stop (cyan) */
  --color-accent-gradient: linear-gradient(135deg, var(--color-accent), var(--color-accent-secondary));
  --color-accent-text: #0A66B8; /* deepened, contrast-safe blue for small text/dots (e.g. "Connected") — raw --color-accent measures under 4.5:1 as text, same reasoning as the --tone-*-text pairs below */
  --color-accent-ink: #04140B; /* near-black text for use ON the bright accent (e.g. primary button) — still 5:1+ on the new blue */
  --color-amber: #D98F2B;
  --color-rose: #E5484D;
  --radius: 12px;
  --radius-sm: 8px;
  --spacing: 1.25rem;

  /* Faint concentric-ring texture, page-wide — echoes the logo's ring
     motif at a scale/contrast that reads as paper texture, not
     decoration. --color-border is already tuned to sit barely-there on
     ivory, so reusing it directly (no extra opacity layer) keeps this
     from ever competing with foreground text. Painted on .bg-rings (see
     Background layer below), not on individual sections — it used to be
     hero-only, applied directly to .site-header/.intro; now it's one
     continuous layer behind the whole page instead. */
  --ring-texture: repeating-radial-gradient(
    circle at 90% 8%,
    transparent 0,
    transparent 25px,
    var(--color-border) 26px,
    var(--color-border) 27px
  );

  /* --- Type ------------------------------------------------------- */
  --font-heading: "Space Grotesk", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --font-body: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  /* --- Semantic tones -----------------------------------------------
     Each tone pairs a pale background with a deepened, contrast-safe
     text color from the same hue (the raw brand colors above are far
     too light to use as text on ivory/white). "Passed" deliberately
     does NOT use the green tone — it's neutral, so a clean scan reads
     as calm and resolved rather than a wall of celebratory green. */
  --tone-green-bg: #E3FBEC;
  --tone-green-text: #0B6B3A;
  --tone-green-border: #03D468;

  --tone-amber-bg: #FBF1DE;
  --tone-amber-text: #8A5A17;
  --tone-amber-border: var(--color-amber);

  --tone-rose-bg: #FBE7E8;
  --tone-rose-text: #A21C25;
  --tone-rose-border: var(--color-rose);

  --tone-neutral-bg: #F2F1EA;
  --tone-neutral-text: var(--color-ink);
  --tone-neutral-border: #B9BBAE;

  --tone-unknown-bg: #EFEFEA;
  --tone-unknown-text: var(--color-slate);
  --tone-unknown-border: #C4C6BA;
}

* {
  box-sizing: border-box;
}

html, body {
  margin: 0;
  padding: 0;
}

/* Safety net, not the fix: the actual cause of the horizontal-scroll bug
   this guards against (the scroll-sweep mark's translateX escaping its
   container's unclipped bounds — see .scroll-sweep) is fixed at the
   source. This just guarantees nothing else can ever make the page
   swipeable sideways, without masking a real bug were one to reappear.
   Deliberately on html only, not body too: html's overflow is what the
   spec propagates to the viewport's own scrolling box, so this clips
   horizontally without creating a scrolling context of its own. Setting
   it on body as well (as a previous version of this rule did) gives body
   its own explicit (and, per the overflow-x/-y coupling rule, forced-
   -auto) overflow instead of staying a plain, non-scrolling block —
   which breaks position: sticky on .site-header, since sticky resolves
   against the nearest non-visible-overflow ancestor and that becomes
   body's own nested scroll box instead of the real viewport scroll. */
html {
  overflow-x: hidden;
}

body {
  background-color: var(--color-bg);
  color: var(--color-ink);
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.55;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  -webkit-font-smoothing: antialiased;
}

/* --- Background layer --------------------------------------------------
   The ring texture as one continuous layer behind the whole page (see
   --ring-texture above), not painted per-section. Fixed + oversized only
   vertically (60vh top/bottom) so scroll-fx.js's parallax below — which
   clamps its own translateY to stay within that same 60vh, with a
   margin — can never outrun the layer and reveal an edge, however far
   the page scrolls. Exactly viewport-width (left/right: 0, no vw-based
   horizontal overhang): the parallax only ever moves this layer
   vertically, so there's nothing for horizontal slack to buy here, and
   `vw` units can measure wider than the visible viewport on some mobile
   browsers — not worth the risk of a fixed element contributing
   horizontal scroll for a buffer this layer never uses. z-index: -1 sits
   it behind every card's opaque surface, so it only ever shows in the
   ivory gaps between them, never under body text. scroll-fx.js nudges
   its own transform (translateY only — never a layout property) at a
   fraction of scroll speed for a subtle sense of depth; without that
   script (or under prefers-reduced-motion, which it checks itself) this
   simply renders as a static backdrop. */
.bg-rings {
  position: fixed;
  top: -60vh;
  right: 0;
  bottom: -60vh;
  left: 0;
  z-index: -1;
  background-image: var(--ring-texture);
  pointer-events: none;
}

h1, h2, h3, h4 {
  font-family: var(--font-heading);
  color: var(--color-ink);
}

/* --- Focus states ---------------------------------------------------
   One consistent, clearly visible focus ring everywhere, in the brand
   accent — unlike the old signal green (a 1.4:1 non-text contrast on
   white, well under the 3:1 a focus ring needs), the new signal blue
   measures 3.5–3.7:1 on ivory/white, comfortably clearing that bar. */
a:focus-visible,
button:focus-visible,
input:focus-visible,
summary:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
  border-radius: 4px;
}

/* --- Header ----------------------------------------------------------- */

/* Sticky, compact, and opaque: pinned to the top of the viewport while
   the page scrolls underneath (position: sticky needs an explicit top
   and doesn't need a positioned ancestor, unlike fixed). z-index sits it
   above ordinary content but below the hamburger's own overlay/panel
   (40/41 — see below), so opening the menu still visually covers it, not
   the other way round. background-color is explicit and opaque (not
   inherited transparency) for two reasons at once: it's what keeps
   scrolled-under content from showing through a persistently-visible
   header, and it's what keeps the page-wide .bg-rings texture layer
   (z-index: -1, behind everything) from showing through here — the
   header is the one place on the page that's deliberately plain, since
   the ring texture is a hero/intro-and-below treatment, not a chrome
   treatment. The hairline border-bottom is the "subtle separation" this
   needs now that content passes under it continuously; matches the
   site's no-drop-shadow design language rather than adding one. */
.site-header {
  position: sticky;
  top: 0;
  z-index: 20;
  padding: 0.55rem var(--spacing);
  background-color: var(--color-bg);
  border-bottom: 1px solid var(--color-border);
}

.header-inner {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  max-width: 640px;
  margin: 0 auto;
}

.logo {
  display: block;
  flex-shrink: 0;
  width: auto;
}

.brand h1 {
  margin: 0;
  font-size: 1.375rem;
  font-weight: 600;
  line-height: 1.15;
  letter-spacing: -0.01em;
}

.tagline {
  margin: 0.05rem 0 0;
  font-size: 0.6875rem;
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: 0.1em;
  color: var(--color-slate);
  text-transform: uppercase;
}

/* The small square accent mark from the new brand mark/social-preview
   art, echoed next to the tagline — one of the few places the two-stop
   gradient is used (everywhere else the brand accent is the flat,
   solid blue, per the logo refresh). Decorative only. */
.tagline-dot {
  display: inline-block;
  width: 0.5em;
  height: 0.5em;
  margin-right: 0.45em;
  border-radius: 2px;
  background: var(--color-accent-gradient);
  vertical-align: middle;
}

/* --- Nav toggle / close buttons -----------------------------------------
   Square icon buttons of matching visual weight: .nav-toggle sits on the
   right of the header, next to the logo/wordmark, and opens the panel
   below; .nav-close is its counterpart inside the panel's own header
   row. Both share the same bar-built icon markup (.nav-toggle-icon) so
   the hamburger, its open (×) state, and the panel's static × are all
   drawn with the same two CSS bars rather than three different icons. */

.nav-toggle,
.nav-close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.25rem;
  height: 2.25rem;
  padding: 0;
  background-color: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  cursor: pointer;
}

.nav-toggle {
  flex-shrink: 0;
  margin-left: auto;
}

.nav-toggle:hover,
.nav-close:hover {
  border-color: var(--color-border-strong);
}

.nav-toggle-icon,
.nav-toggle-icon::before,
.nav-toggle-icon::after {
  display: block;
  width: 1.1rem;
  height: 2px;
  border-radius: 1px;
  background-color: var(--color-ink);
  transition: transform 0.18s ease, opacity 0.18s ease, background-color 0.18s ease;
}

.nav-toggle-icon {
  position: relative;
}

.nav-toggle-icon::before,
.nav-toggle-icon::after {
  content: "";
  position: absolute;
  left: 0;
}

.nav-toggle-icon::before {
  top: -6px;
}

.nav-toggle-icon::after {
  top: 6px;
}

.nav-toggle[aria-expanded="true"] .nav-toggle-icon,
.nav-close .nav-toggle-icon {
  background-color: transparent;
}

.nav-toggle[aria-expanded="true"] .nav-toggle-icon::before,
.nav-close .nav-toggle-icon::before {
  top: 0;
  transform: rotate(45deg);
}

.nav-toggle[aria-expanded="true"] .nav-toggle-icon::after,
.nav-close .nav-toggle-icon::after {
  top: 0;
  transform: rotate(-45deg);
}

/* --- Nav overlay + panel -------------------------------------------------
   A full-height panel that slides in from the right over a dimmed
   overlay, in place of a compact dropdown — same ivory/ink/accent
   tokens as the rest of the site, not a generic drawer component. */

.nav-overlay {
  position: fixed;
  inset: 0;
  z-index: 40;
  background-color: rgba(15, 17, 21, 0.45);
  opacity: 0;
  transition: opacity 0.22s ease;
}

.nav-overlay.is-open {
  opacity: 1;
}

.nav-overlay[hidden] {
  display: none;
}

.nav-panel {
  position: fixed;
  top: 0;
  right: 0;
  z-index: 41;
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 360px;
  height: 100vh;
  height: 100dvh;
  background-color: var(--color-bg);
  border-left: 1px solid var(--color-border);
  overflow-y: auto;
  transform: translateX(100%);
  transition: transform 0.22s ease;
}

.nav-panel.is-open {
  transform: translateX(0);
}

.nav-panel[hidden] {
  display: none;
}

.nav-panel-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-shrink: 0;
  padding: 1.25rem var(--spacing) 1rem;
  border-bottom: 1px solid var(--color-border);
}

.nav-panel-title {
  margin: 0;
  font-family: var(--font-heading);
  font-size: 1.125rem;
  font-weight: 600;
  color: var(--color-ink);
}

.nav-panel .site-nav {
  display: flex;
  flex-direction: column;
  padding: 0 var(--spacing);
}

.nav-panel .site-nav a {
  padding: 1.1rem 0.1rem;
  border-bottom: 1px solid var(--color-border);
  font-size: 1.05rem;
  font-weight: 500;
  color: var(--color-ink);
  text-decoration: none;
}

.nav-panel .site-nav a:last-child {
  border-bottom: none;
}

.nav-panel .site-nav a:hover {
  color: var(--color-accent-text);
}

/* Deepened accent shade, not the raw #0B84F3 brand blue — same reasoning
   as --color-accent-text everywhere else it's used for small text: the
   raw accent falls under 4.5:1 as text on ivory/white. */
.nav-panel .site-nav a[aria-current="page"] {
  color: var(--color-accent-text);
  font-weight: 600;
}

body.nav-open {
  overflow: hidden;
}

@media (prefers-reduced-motion: reduce) {
  .nav-toggle-icon,
  .nav-toggle-icon::before,
  .nav-toggle-icon::after,
  .nav-overlay,
  .nav-panel {
    transition: none;
  }
}

/* --- Layout ------------------------------------------------------------ */

main {
  flex: 1;
  max-width: 640px;
  width: 100%;
  margin: 0 auto;
  padding: var(--spacing);
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
}

/* --- Scroll sweep (index.html only) -------------------------------------
   The one deliberate scroll-triggered moment on the page (see the
   brief): a dot-and-dash mark — the exact marker used for a signal-list
   finding (see .finding-marker) — travels once across a hairline track
   as the scan section first enters view, then stays gone. Not a repeat
   effect and not applied to any other section; scroll-fx.js fires this
   via IntersectionObserver the first time only, then disconnects.
   --sweep-distance is set by scroll-fx.js to the track's actual measured
   width right before the animation starts (a plain CSS % of the mark's
   OWN small width wouldn't reach across it). */
.scroll-sweep {
  position: relative;
  height: 14px;
  /* The mark below travels past this element's own right edge by design
     (see --sweep-distance) — without clipping that, the translateX
     stays applied after the animation's `forwards` fill-mode holds its
     end state, permanently widening the whole page's scrollable area
     and leaving it swipeable sideways. This is the actual fix for that;
     overflow-x: hidden on html/body below is only a backstop. */
  overflow: hidden;
}

.scroll-sweep::before {
  content: "";
  position: absolute;
  top: 50%;
  left: 0;
  right: 0;
  height: 1px;
  background-color: var(--color-border);
  transform: translateY(-50%);
}

.scroll-sweep-mark {
  position: absolute;
  top: 0;
  left: 0;
  width: 64px;
  height: 14px;
  fill: var(--color-accent);
  stroke: var(--color-accent);
  stroke-width: 2;
  stroke-linecap: round;
  opacity: 0;
  transform: translateX(-64px);
}

.scroll-sweep.is-active .scroll-sweep-mark {
  animation: scroll-sweep-move 1.2s ease-out forwards;
}

@keyframes scroll-sweep-move {
  0% { transform: translateX(-64px); opacity: 0; }
  12% { opacity: 1; }
  88% { opacity: 1; }
  100% { transform: translateX(var(--sweep-distance, 100%)); opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .scroll-sweep.is-active .scroll-sweep-mark {
    animation: none;
    opacity: 0;
  }
}

.card {
  background-color: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  padding: 1.125rem 1.25rem;
}

.card h2,
.card h3 {
  margin: 0 0 0.75rem;
  font-size: 0.95rem;
  font-weight: 600;
}

/* --- Plain content pages (About, Disclaimer): paragraphs/lists/sub-
   -headings with consistent rhythm, reusing the same card/type tokens. */

.prose p {
  margin: 0.9rem 0 0;
}

.prose p:first-child {
  margin-top: 0;
}

.prose ul {
  margin: 0.9rem 0 0;
  padding-left: 1.2rem;
}

.prose li + li {
  margin-top: 0.4rem;
}

.prose h3 {
  margin: 1.5rem 0 0.5rem;
  font-size: 1rem;
}

.prose h3:first-child {
  margin-top: 0;
}

.prose a {
  color: var(--color-ink);
}

.prose code,
.check-item code,
.faq-item code {
  font-family: var(--font-mono);
  font-size: 0.85em;
  background-color: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: 4px;
  padding: 0.1em 0.35em;
}

/* --- How it works: the risk-check list — a calm, neutral rhythm (not
   severity-colored, since this is documentation, not a live result). */

.check-list {
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
  margin: 0.75rem 0 0;
}

.check-item {
  border-left: 3px solid var(--color-border-strong);
  padding: 0.05rem 0 0.05rem 0.9rem;
}

.check-item h4 {
  margin: 0;
  font-family: var(--font-body);
  font-size: 0.9rem;
  font-weight: 600;
}

.check-item p {
  margin: 0.3rem 0 0;
  font-size: 0.85rem;
  color: var(--color-slate);
}

/* --- FAQ ------------------------------------------------------------- */

.faq-list {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  margin: 0.75rem 0 0;
}

.faq-item {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  padding: 0.8rem 0.95rem;
}

.faq-item summary {
  cursor: pointer;
  font-weight: 600;
  font-size: 0.925rem;
}

.faq-item summary:hover {
  color: var(--color-slate);
}

.faq-item p {
  margin: 0.6rem 0 0;
  font-size: 0.875rem;
  color: var(--color-slate);
}

.faq-item p a {
  color: var(--color-ink);
}

/* --- Intro block --------------------------------------------------- */

.intro {
  padding: 0.25rem 0.1rem 0.25rem;
}

.intro-headline {
  margin: 0;
  font-family: var(--font-heading);
  font-size: 1.375rem;
  font-weight: 600;
  line-height: 1.3;
  letter-spacing: -0.01em;
}

.intro-sub {
  margin: 0.5rem 0 0;
  font-size: 0.925rem;
  color: var(--color-slate);
  max-width: 46ch;
}

/* --- Section labels (small, calm — never tracked-out caps) --------- */

.section-label {
  margin: 0 0 0.5rem;
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--color-slate);
}

/* --- Network status: compact strip + collapsed details -------------- */

.status-strip {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.4rem;
  font-size: 0.9rem;
}

.status-strip-sep {
  color: var(--color-border-strong);
}

.status-strip-block {
  color: var(--color-slate);
}

.status-strip-block #network-block-number {
  font-family: var(--font-mono);
  color: var(--color-ink);
}

#network-name {
  color: var(--color-slate);
}

.status-pending,
.status-ok,
.status-bad {
  font-weight: 600;
}

.status-pending {
  color: var(--color-slate);
}

.status-pending::before,
.status-ok::before,
.status-bad::before {
  content: "";
  display: inline-block;
  width: 0.4rem;
  height: 0.4rem;
  border-radius: 999px;
  margin-right: 0.4rem;
  vertical-align: middle;
}

.status-pending::before {
  background-color: var(--color-border-strong);
}

.status-ok {
  /* A "connected" status is a brand/operational signal, not a risk
     level — it moves to the new signal blue along with the rest of the
     brand accent, rather than staying on the risk-tone green. */
  color: var(--color-accent-text);
}

.status-ok::before {
  background-color: var(--color-accent-text);
}

.status-bad {
  color: var(--tone-rose-text);
}

.status-bad::before {
  background-color: var(--color-rose);
}

.status-more {
  margin-top: 0.6rem;
}

.status-more > summary {
  cursor: pointer;
  color: var(--color-slate);
  font-size: 0.8125rem;
  font-weight: 600;
}

.status-more > summary:hover {
  color: var(--color-ink);
}

.status-grid,
.result-grid {
  margin: 0.6rem 0 0;
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.5rem 1rem;
}

.status-row,
.result-row {
  display: contents;
}

dt {
  font-family: var(--font-body);
  font-weight: 500;
  color: var(--color-slate);
  font-size: 0.8125rem;
}

dd {
  margin: 0;
  word-break: break-all;
  font-family: var(--font-mono);
  font-size: 0.85rem;
}

.status-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-top: 0.75rem;
}

/* --- Forms --------------------------------------------------------- */

form {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}

.field {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

label {
  font-size: 0.8125rem;
  font-weight: 600;
}

input[type="text"] {
  padding: 0.7rem 0.85rem;
  border: 1px solid var(--color-input-border);
  border-radius: var(--radius-sm);
  font-size: 0.95rem;
  font-family: var(--font-mono);
  background-color: var(--color-bg);
  color: var(--color-ink);
}

/* Full-strength slate (5.8:1 on the input's ivory), not a faded one: at
   70% opacity it measured 2.98:1, under AA. opacity: 1 is explicit
   because Firefox otherwise applies its own default placeholder opacity. */
input[type="text"]::placeholder {
  color: var(--color-slate);
  opacity: 1;
}

.primary-button,
.secondary-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  border: none;
  border-radius: var(--radius-sm);
  padding: 0.7rem 1.1rem;
  font-family: var(--font-body);
  font-size: 0.925rem;
  font-weight: 600;
  cursor: pointer;
  transition: filter 0.1s ease, background-color 0.1s ease;
}

/* The same on-brand busy cue as a pending finding row's marker dot (see
   .risk-finding-pending), shown only while the button is disabled for a
   scan in flight — never a generic spinner. Pure CSS: nothing in app.js
   toggles this directly, it just follows :disabled. */
.button-busy-dot {
  display: none;
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 999px;
  background-color: currentColor;
  flex-shrink: 0;
}

.primary-button:disabled .button-busy-dot,
.secondary-button:disabled .button-busy-dot {
  display: block;
  animation: finding-pulse 1.1s ease-in-out infinite;
}

@media (prefers-reduced-motion: reduce) {
  .primary-button:disabled .button-busy-dot,
  .secondary-button:disabled .button-busy-dot {
    animation: none;
  }
}

.primary-button {
  background-color: var(--color-accent);
  color: var(--color-accent-ink);
}

.primary-button:hover {
  filter: brightness(0.96);
}

.primary-button:disabled {
  opacity: 0.55;
  cursor: not-allowed;
  filter: none;
}

.secondary-button {
  background-color: transparent;
  border: 1px solid var(--color-border-strong);
  color: var(--color-ink);
  align-self: flex-start;
  margin-top: 0.25rem;
}

.secondary-button:hover {
  background-color: var(--color-bg);
}

.secondary-button:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}

/* Post-scan cooldown (see startScanCooldown in app.js): still disabled,
   but showing a countdown the visitor needs to read, so it's a calm,
   full-opacity neutral "waiting" state rather than the faded disabled
   look (55% opacity would put the label near 2:1). Slate on the neutral
   tint is 5.4:1; slate on the white card (Rescan) is 6.1:1. The busy dot
   is hidden — nothing is scanning any more. */
.primary-button.is-cooling-down:disabled {
  opacity: 1;
  background-color: var(--tone-neutral-bg);
  color: var(--color-slate);
  box-shadow: inset 0 0 0 1px var(--color-border-strong);
}

.secondary-button.is-cooling-down:disabled {
  opacity: 1;
  color: var(--color-slate);
}

.primary-button.is-cooling-down:disabled .button-busy-dot,
.secondary-button.is-cooling-down:disabled .button-busy-dot {
  display: none;
}

/* Quiet, link-styled button (e.g. "Try an example") — deliberately
   secondary to the primary Scan button: no fill, no border, just accent
   text. Still a full 44px-tall tap target via min-height, and keyboard
   focus uses the site-wide button:focus-visible ring. */
.text-button {
  display: inline-flex;
  align-items: center;
  align-self: flex-start;
  min-height: 44px;
  padding: 0 0.1rem;
  margin: -0.35rem 0 -0.5rem;
  border: none;
  background: none;
  font-family: var(--font-body);
  font-size: 0.8125rem;
  font-weight: 500;
  color: var(--color-accent-text);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  cursor: pointer;
}

.text-button:hover {
  text-decoration-thickness: 2px;
}

.text-button:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}

/* Screen-reader-only text (e.g. the "Link copied" announcement). */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.error-message {
  background-color: var(--tone-rose-bg);
  color: var(--tone-rose-text);
  border-radius: var(--radius-sm);
  padding: 0.65rem 0.8rem;
  font-size: 0.85rem;
  margin: 0.5rem 0 0;
}

/* --- Technical details: inset mono code blocks ---------------------- */

.tech-details {
  margin: 0.5rem 0 0;
  font-size: 0.8125rem;
}

.tech-details summary {
  cursor: pointer;
  color: var(--color-slate);
  font-weight: 600;
}

.tech-details summary:hover {
  color: var(--color-ink);
}

.tech-details pre {
  margin: 0.5rem 0 0;
  padding: 0.75rem 0.85rem;
  background-color: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  font-family: var(--font-mono);
  font-size: 0.75rem;
  line-height: 1.5;
  white-space: pre-wrap;
  word-break: break-word;
}

/* --- Result card ----------------------------------------------------- */

.result-card {
  margin-top: 1.25rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--color-border);
}

.result-card-header {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  margin-bottom: 1rem;
}

.result-card-header h3 {
  margin: 0;
  font-size: 1.15rem;
  font-weight: 600;
}

/* A small "locked target" treatment for the symbol, restrained to a
   type/color detail — mono, accent blue, bracketed like a target ID —
   not a new badge shape. */
.token-symbol-tag {
  font-family: var(--font-mono);
  font-size: 0.95em;
  font-weight: 600;
  color: var(--color-accent-text);
}

.token-symbol-tag::before {
  content: "[";
  color: var(--color-border-strong);
  margin-right: 0.05em;
}

.token-symbol-tag::after {
  content: "]";
  color: var(--color-border-strong);
  margin-left: 0.05em;
}

/* Copy link + Rescan, kept together; wraps below a long token title on
   narrow screens rather than squeezing it. */
.result-card-actions {
  display: flex;
  gap: 0.5rem;
  flex-shrink: 0;
}

.result-card-header .secondary-button {
  margin-top: 0;
  min-height: 44px;
  padding: 0.45rem 0.85rem;
  font-size: 0.825rem;
  flex-shrink: 0;
}

.risk-summary {
  margin-bottom: 1.25rem;
  padding-bottom: 1.25rem;
  border-bottom: 1px solid var(--color-border);
}

.risk-headline {
  margin-bottom: 0.85rem;
  text-align: center;
}

/* --- Risk gauge -------------------------------------------------------
   The one bold element: a semicircular dial echoing the logo's
   concentric rings — a thin guide ring behind three colored zone arcs
   (Low/Medium/High), with a needle that pivots on a target-style hub
   dot. The SVG is purely decorative (aria-hidden): the caption below it
   is the real, always-present text equivalent of the risk level. */

.risk-gauge-wrap {
  max-width: 260px;
  margin: 0 auto;
}

.risk-gauge {
  display: block;
  width: 100%;
  height: auto;
}

.gauge-zone {
  fill: none;
  stroke-width: 20;
}

.gauge-zone-low { stroke: var(--tone-green-border); }
.gauge-zone-medium { stroke: var(--tone-amber-border); }
.gauge-zone-high { stroke: var(--tone-rose-border); }

.gauge-guide-ring {
  fill: none;
  stroke: var(--color-border);
  stroke-width: 1.5;
}

.gauge-needle-rotor {
  transform: rotate(-95deg);
  transform-origin: 0 0;
  transition: transform 0.7s cubic-bezier(0.22, 1, 0.36, 1);
}

.gauge-needle-rotor.gauge-needle-hidden {
  opacity: 0;
}

/* The gauge's own "still scanning" indicator: the needle sweeps across
   the full dial instead of pointing anywhere, since neither owner nor
   sell-simulation (both level-affecting) has resolved yet. transition
   and animation never fight — setGaugeNeedle always removes this class
   before setting a real angle, so settling takes over wherever the
   sweep left off (see app.js). */
.gauge-needle-rotor.gauge-needle-pending {
  animation: gauge-sweep 1.8s ease-in-out infinite;
}

@keyframes gauge-sweep {
  0%, 100% { transform: rotate(-60deg); }
  50% { transform: rotate(60deg); }
}

.gauge-needle {
  stroke: var(--color-accent);
  stroke-width: 3;
  stroke-linecap: round;
}

.gauge-needle-tip {
  fill: var(--color-accent);
}

.gauge-hub {
  fill: var(--color-accent);
  stroke: var(--color-surface);
  stroke-width: 3;
}

.risk-gauge-caption {
  margin-top: -0.35rem;
  text-align: center;
}

.risk-level-label {
  display: block;
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-slate);
}

.risk-level-value {
  display: block;
  margin-top: 0.15rem;
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: 1.375rem;
  line-height: 1.2;
}

.risk-level-low { color: var(--tone-green-text); }
.risk-level-medium { color: var(--tone-amber-text); }
.risk-level-high { color: var(--tone-rose-text); }
.risk-level-insufficient-data { color: var(--color-slate); }

.risk-level-pending {
  color: var(--color-slate);
  font-size: 1.05rem;
}

.risk-summary-sentence {
  margin: 0.6rem auto 0;
  font-size: 1rem;
  font-weight: 500;
  max-width: 34ch;
  text-align: center;
}

@media (prefers-reduced-motion: reduce) {
  .gauge-needle-rotor {
    transition: none;
  }

  .gauge-needle-rotor.gauge-needle-pending {
    animation: none;
    transform: rotate(0deg);
  }
}

.risk-disclaimer {
  font-size: 0.78rem;
  color: var(--color-slate);
  margin: 0 0 0.9rem;
}

.risk-findings-group h4 {
  font-family: var(--font-body);
  font-size: 0.8125rem;
  font-weight: 700;
  color: var(--color-slate);
  margin: 0.9rem 0 0.5rem;
}

.risk-findings-group:first-child h4 {
  margin-top: 0;
}

/* --- Findings as a signal list ------------------------------------------
   Each finding reads as a radar blip: a small dot + a short rounded-dash
   line (the exact two-dash treatment from the logo's own dashed line),
   colored by severity, then the text block — replacing the old colored-
   card-with-left-border treatment. A hairline divider between rows
   (not a filled card) keeps the list calm; the severity color lives only
   on the marker, never washed across the text. */

.risk-finding {
  display: flex;
  align-items: flex-start;
  gap: 0.65rem;
  padding: 0.65rem 0;
  border-bottom: 1px solid var(--color-border);
}

.risk-findings-group .risk-finding:last-child {
  border-bottom: none;
}

.finding-marker {
  flex-shrink: 0;
  width: 34px;
  height: 9px;
  margin-top: 0.42rem;
  fill: currentColor;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  color: var(--color-border-strong);
}

.risk-finding-passed .finding-marker { color: var(--tone-neutral-border); }
.risk-finding-low .finding-marker { color: var(--tone-green-border); }
.risk-finding-medium .finding-marker { color: var(--tone-amber-border); }
.risk-finding-high .finding-marker { color: var(--tone-rose-border); }
.risk-finding-unknown .finding-marker { color: var(--tone-unknown-border); }

/* An "unknown" outcome gets a hollow dot rather than a filled one —
   the same distinction the old dashed-vs-solid left border used to
   draw, carried over into the new marker instead. */
.risk-finding-unknown .finding-marker circle {
  fill: none;
}

/* A pending row (owner/sell-simulation, still in flight) reuses the same
   marker, in the neutral color it already falls back to, with its dot
   pulsing — a small, on-brand "still scanning" cue rather than a
   generic spinner. Frozen under reduced motion, leaving a plain static
   dot; the row's own "…" label already reads as in-progress either way. */
.risk-finding-pending .finding-marker circle {
  animation: finding-pulse 1.4s ease-in-out infinite;
  transform-origin: center;
  transform-box: fill-box;
}

@keyframes finding-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.3; }
}

@media (prefers-reduced-motion: reduce) {
  .risk-finding-pending .finding-marker circle {
    animation: none;
  }
}

.finding-body {
  min-width: 0;
  flex: 1;
}

.risk-finding-title {
  margin: 0;
  font-weight: 600;
  font-size: 0.9rem;
}

.risk-finding-detail {
  margin: 0.25rem 0 0;
  font-size: 0.825rem;
  color: var(--color-slate);
}

.risk-finding-evidence {
  margin: 0.6rem 0 0;
  font-size: 0.8rem;
}

.risk-finding-evidence summary {
  cursor: pointer;
  color: inherit;
  font-weight: 600;
  opacity: 0.85;
}

.risk-finding-evidence summary:hover {
  opacity: 1;
}

.risk-finding-evidence p,
.risk-finding-evidence ul {
  margin: 0.4rem 0 0;
}

.risk-finding-evidence ul {
  padding-left: 1.1rem;
}

.risk-finding-evidence a {
  color: inherit;
  font-family: var(--font-mono);
  font-size: 0.78rem;
  word-break: break-all;
}

.risk-finding-evidence pre {
  margin: 0.4rem 0 0;
  padding: 0.7rem 0.8rem;
  background-color: var(--color-surface);
  border: 1px solid rgb(0 0 0 / 8%);
  border-radius: var(--radius-sm);
  font-family: var(--font-mono);
  font-size: 0.75rem;
  line-height: 1.5;
  white-space: pre-wrap;
  word-break: break-word;
}

.evidence-holder {
  margin-top: 0.6rem;
  padding-top: 0.6rem;
  border-top: 1px dashed rgb(0 0 0 / 12%);
}

#worker-test-result {
  margin-top: 0.6rem;
}

#worker-test-summary p {
  margin: 0.35rem 0;
  font-size: 0.85rem;
}

/* --- Footer ---------------------------------------------------------- */

/* One small spacing scale, reused for every gap in the footer so the
   blocks share a single rhythm: 0.5 / 1 / 1.5 / 2.5rem. Opaque ivory
   background, like the header, so the page-wide .bg-rings texture
   (z-index: -1) never shows behind footer text. Contrast on ivory:
   ink 17.9:1, slate 5.8:1, --color-accent-text 5.5:1 — all clear AA. */
.site-footer {
  --footer-space-1: 0.5rem;
  --footer-space-2: 1rem;
  --footer-space-3: 1.5rem;
  --footer-space-4: 2.5rem;
  /* Soft slate-tinted hairline — low-contrast divider, not black. */
  --footer-rule: rgb(99 102 95 / 22%);

  margin-top: var(--footer-space-4);
  padding: var(--footer-space-4) var(--spacing) var(--footer-space-3);
  background-color: var(--color-bg);
  border-top: 1px solid var(--color-border);
  font-size: 0.875rem;
  line-height: 1.55;
}

.footer-grid {
  max-width: 640px;
  margin: 0 auto;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  column-gap: var(--footer-space-3);
  row-gap: var(--footer-space-2);
}

/* Brand block always leads, full width, with extra room beneath it
   (row-gap + this margin = the largest step on the scale). */
.footer-col-brand {
  grid-column: 1 / -1;
  margin-bottom: var(--footer-space-3);
}

.footer-brand {
  display: flex;
  align-items: center;
  gap: var(--footer-space-1);
  font-family: var(--font-heading);
  font-weight: 600;
  font-size: 1.125rem;
  line-height: 1.2;
  color: var(--color-ink);
}

.footer-desc {
  margin: var(--footer-space-2) 0 0;
  color: var(--color-slate);
  max-width: 38ch;
}

.footer-disclaimer-short {
  margin: var(--footer-space-1) 0 0;
  font-size: 0.8125rem;
  color: var(--color-slate);
  max-width: 48ch;
}

/* Quiet group label: slate, sentence case, medium weight, no tracking. */
.footer-col-heading {
  margin: 0;
  font-family: var(--font-body);
  font-size: 0.8125rem;
  font-weight: 500;
  letter-spacing: 0;
  text-transform: none;
  color: var(--color-slate);
}

.footer-links {
  list-style: none;
  margin: 0;
  padding: 0;
}

/* Each link is its own 44px-tall tap target; the vertical padding that
   builds it also spaces the list, so the items need no extra gap. */
.footer-links a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  color: var(--color-ink);
  font-weight: 500;
  text-decoration: none;
  text-decoration-thickness: 2px;
  text-underline-offset: 4px;
}

/* Hover/focus: a blue accent underline; keyboard focus also gets the
   site-wide a:focus-visible ring defined under Focus states. */
.footer-links a:hover,
.footer-links a:focus-visible {
  text-decoration-line: underline;
  text-decoration-color: var(--color-accent);
}

.footer-fine-print {
  max-width: 640px;
  margin: var(--footer-space-2) auto 0;
  padding-top: var(--footer-space-3);
  border-top: 1px solid var(--footer-rule);
  font-size: 0.8125rem;
  line-height: 1.6;
  color: var(--color-slate);
  overflow-wrap: break-word;
  text-wrap: pretty;
}

/* Keeps short proper names like "Robinhood Markets, Inc." on one line
   so the fine print never strands "Inc." at the start of a line. */
.footer-nowrap {
  white-space: nowrap;
}

/* Both of these are inline text links sitting in a sentence of slate
   fine print, not a standalone nav-style link list (like .footer-links
   above) — so, unlike those, they need a real link affordance (accent
   color + underline), not just a layout cue, to read as clickable
   rather than as plain emphasized text. */
.footer-fine-print a,
.footer-disclaimer-short a {
  color: var(--color-accent-text);
  font-weight: 500;
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* Two link groups side by side once there's comfortable room (~360px);
   stacked on anything narrower. */
@media (min-width: 360px) {
  .footer-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* --- Wider viewports --------------------------------------------------
   Mobile-first: everything above works stacked at phone width. From here
   up, the token-details table pairs two label/value columns per row
   instead of one long stack, and a couple of type sizes step up. */

@media (min-width: 480px) {
  .result-grid {
    grid-template-columns: auto 1fr auto 1fr;
    column-gap: 1.5rem;
  }
}

@media (min-width: 640px) {
  .brand h1 {
    font-size: 1.625rem;
  }

  .intro-headline {
    font-size: 1.625rem;
  }

  .risk-level-value {
    font-size: 1.625rem;
  }

  .risk-gauge-wrap {
    max-width: 300px;
  }
}

/* --- Debug timing overlay (only exists with ?debug=timing) -----------
   Deliberately NOT styled like the site: a plain dark, semi-transparent,
   monospace overlay so it reads unmistakably as a developer tool. Fixed
   to the bottom, collapsible via <details>, above every site layer. */
.debug-timing-panel {
  position: fixed;
  left: 8px;
  right: 8px;
  bottom: calc(8px + env(safe-area-inset-bottom, 0px));
  z-index: 1000;
  max-height: 45vh;
  overflow: auto;
  padding: 0 8px 6px;
  border-radius: 6px;
  background: rgb(0 0 0 / 86%);
  -webkit-backdrop-filter: blur(3px);
  backdrop-filter: blur(3px);
  color: #f2f2f2;
  font: 11px/1.4 var(--font-mono);
}

.debug-timing-panel summary {
  padding: 13px 0;
  line-height: 18px;
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.debug-timing-panel summary:focus-visible {
  outline: 2px solid #ffd54a;
  outline-offset: -2px;
}

.debug-timing-panel table {
  width: 100%;
  border-collapse: collapse;
}

.debug-timing-panel th,
.debug-timing-panel td {
  padding: 2px 4px;
  text-align: right;
  font-weight: normal;
  vertical-align: top;
}

.debug-timing-panel th:first-child,
.debug-timing-panel td:first-child {
  text-align: left;
  overflow-wrap: anywhere;
}

.debug-timing-panel th {
  color: #b8b8b8;
  border-bottom: 1px solid rgb(255 255 255 / 25%);
}
