/* ============================================================================
   main.css
   ----------------------------------------------------------------------------
   All colours are CSS custom properties set on :root. The base palette below
   is a fallback (and matches the real SAms - SITizen Ambassadors - red/
   black/white identity); the real values are written in from
   src/data/quizConfig.js at runtime (see src/app.js -> applyBrandColors()),
   so organisers only ever need to edit quizConfig.js, never this file, to
   restyle the app.

   --sams-* variables are static (not runtime-injected) and used only in the
   handful of places that need a literal value - e.g. a gradient stop written
   directly in this file. Everything else uses the --color-* variables so it
   stays in sync with quizConfig.js BRAND_COLORS.
   ============================================================================ */

:root {
  --color-bg: #f5f5f5;
  --color-surface: #ffffff;
  --color-ink: #111111;
  --color-ink-muted: #555555;
  --color-border: #d8d8d8;
  --color-success: #111111;
  --color-danger: #a80000;

  --color-tt: #d60000;
  --color-lur: #8a1420;
  --color-ct: #1f1f1f;
  --color-gc: #6e6e6e;

  --sams-red: #d60000; /* sampled directly from the real logo file (#d40005) - this is the source of truth, not a guess */
  --sams-red-bright: #ff2418; /* brighter accent for hover/glow only - never used for large flat areas */
  --sams-red-dark: #a80000; /* also sampled from the logo's secondary shade (#aa0004) */
  --sams-red-soft: #fff0ef; /* pale red for selected-answer backgrounds */
  --sams-black-red: #22090a; /* near-black with a red tint - used only for the page background gradient */
  --sams-black: #111111;
  --sams-charcoal: #18181b; /* premium dark-section background - softer than pure black */
  --sams-slate: #27272a; /* secondary dark surface, e.g. raised panels on a charcoal section */
  --sams-white: #ffffff;
  --sams-grey-light: #f5f5f5;
  --sams-grey-mid: #d8d8d8;
  --sams-grey-dark: #555555;
  --sams-grey-400: #a1a1aa;

  --radius-sm: 12px;
  --radius-md: 20px;
  --radius-lg: 32px;
  --radius-xl: 40px;
  --shadow-soft: 0 8px 24px rgba(17, 17, 17, 0.08);
  --shadow-lift: 0 16px 32px rgba(17, 17, 17, 0.16);
  --shadow-card: 0 10px 30px rgba(0, 0, 0, 0.08), 0 2px 8px rgba(0, 0, 0, 0.05);
  --shadow-dark: 0 18px 50px rgba(0, 0, 0, 0.35), 0 0 40px rgba(214, 0, 0, 0.1);
  /* Two-font system: Plus Jakarta Sans for anything read at length
     (questions, answers, body copy, buttons, labels, disclaimers), and
     Fredoka for a handful of short, large display moments (the landing
     title, the primary trait name, big section headings) - see .font-display
     usages below. Both come from the Google Fonts <link> in index.html/
     dashboard.html; the system-ui fallback chain here is what's shown
     immediately (and permanently, if the font ever fails to load) since
     font-display:swap on that link never blocks rendering. */
  --font-body: "Plus Jakarta Sans", system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --font-display: "Fredoka", "Plus Jakarta Sans", system-ui, -apple-system, sans-serif;
  --transition-fast: 180ms ease;
  --transition-med: 320ms cubic-bezier(0.22, 1, 0.36, 1);
  /* Mimics a Framer Motion spring (stiffness ~350, damping ~22): a quick
     overshoot-and-settle curve, used for button/answer-card hover and tap
     feedback so plain CSS transitions feel closer to spring physics. */
  --spring: 260ms cubic-bezier(0.34, 1.56, 0.64, 1);
  --spring-fast: 160ms cubic-bezier(0.34, 1.56, 0.64, 1);

  /* v2.14.0: intentional spacing scale - every new/updated rule in this
     polish pass uses one of these instead of a one-off px value, so
     margins/paddings/gaps read as deliberate multiples of a single system
     rather than scattered numbers. Older rules elsewhere in this file that
     already worked (and weren't part of this round's brief) were left as
     literal px values rather than being churned just to reference these -
     see "What's new in v2.14.0" in README.md. */
  --space-8: 8px;
  --space-12: 12px;
  --space-16: 16px;
  --space-24: 24px;
  --space-32: 32px;
  --space-48: 48px;
  --space-64: 64px;
}

* {
  box-sizing: border-box;
}

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

/* v2.17.0: true optical-centring fix, app-wide. Every "centred" screen in
   this app (.screen { display:flex; align-items:center } wrapping a
   max-width .screen-inner with margin:0 auto) was already mathematically
   centred within its own scrollable content box - but on any page tall
   enough to trigger a vertical scrollbar, a classic desktop-browser
   scrollbar only reserves space on the RIGHT edge of the viewport, so the
   centred content shifts a few pixels left of the visible viewport's true
   centre while short pages (no scrollbar) stay perfectly centred - an
   inconsistency that reads as "this page feels slightly off-centre" even
   though no single rule is actually wrong. scrollbar-gutter reserves that
   space on BOTH edges unconditionally, so every screen centres identically
   whether or not its own content happens to scroll. No-op (ignored, not
   broken) in browsers that don't yet support the property. */
html {
  scrollbar-gutter: stable both-edges;
}

body {
  font-family: var(--font-body);
  /* App-wide brand gradient: black-ish red at the bottom, up through the
     brand red, fading to white at the top - covers the FULL page, however
     long its content is (no size/repeat restriction), so it's always
     visible behind everything.
     Because the lower half of this gradient is genuinely dark, all real
     content sits on an opaque white "content panel" on top of it (see
     .screen-inner and .loading-panel/.transition-panel below) rather than
     directly on the gradient - that's what keeps every word legible
     regardless of how far down the page it ends up. */
  background-color: var(--color-bg);
  background-image: linear-gradient(to top, var(--sams-black-red) 0%, var(--sams-red-dark) 45%, var(--sams-white) 100%);
  color: var(--color-ink);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

#app {
  min-height: 100vh;
  min-height: 100dvh;
  position: relative;
  overflow-x: hidden;
}

img, svg {
  max-width: 100%;
  display: block;
}

button {
  font-family: inherit;
}

a {
  color: inherit;
}

.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;
}

/* ---------------------------------------------------------------------- */
/* Focus + accessibility                                                   */
/* ---------------------------------------------------------------------- */
:focus-visible {
  outline: 3px solid var(--color-tt);
  outline-offset: 3px;
  border-radius: 6px;
}

.skip-link {
  position: absolute;
  top: -60px;
  left: 12px;
  background: var(--color-ink);
  color: white;
  padding: 10px 16px;
  border-radius: var(--radius-sm);
  z-index: 100;
  transition: top var(--transition-fast);
}
.skip-link:focus {
  top: 12px;
}

/* ---------------------------------------------------------------------- */
/* Reduced motion                                                          */
/* ---------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-delay: 0s !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
}
/* Manual "Reduce Motion" override from the accessibility panel (see
   accessibilityPanel.js) - identical to the automatic prefers-reduced-motion
   query above, just keyed off a class on <html> instead of an OS/browser
   setting, for anyone who wants the effect without changing system
   preferences (e.g. on a shared event kiosk). */
html.force-reduced-motion *,
html.force-reduced-motion *::before,
html.force-reduced-motion *::after {
  animation-duration: 0.001ms !important;
  animation-delay: 0s !important;
  animation-iteration-count: 1 !important;
  transition-duration: 0.001ms !important;
  scroll-behavior: auto !important;
}

/* "Larger Text" override from the accessibility panel - scales every
   rem-sized piece of type app-wide (the large majority of the type scale).
   A handful of fixed-px micro-labels are unaffected - see
   accessibilityPanel.js's file header for the reasoning. */
html.text-size-large {
  font-size: 118%;
}

/* "High Contrast" override from the accessibility panel - swaps the ink/
   border custom properties for higher-contrast values and thickens
   interactive outlines, without touching the brand colours themselves
   (--color-tt/lur/ct/gc), so trait identity stays intact. */
html.high-contrast {
  --color-ink: #000000;
  --color-ink-muted: #1a1a1a;
  --color-border: #4d4d4d;
}
html.high-contrast .btn,
html.high-contrast .answer-card,
html.high-contrast input,
html.high-contrast textarea {
  border-width: 2px !important;
}
html.high-contrast :focus-visible {
  outline: 3px solid #000000 !important;
  outline-offset: 2px;
}

/* ---------------------------------------------------------------------- */
/* Screen layout                                                           */
/* ---------------------------------------------------------------------- */
.screen {
  min-height: 100vh;
  min-height: 100dvh;
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  /* Deliberately modest - this is what reveals the gradient as a coloured
     frame around the white .screen-inner panel, on every screen. */
  padding: 20px 14px 32px;
  position: relative;
  animation: screenIn var(--transition-med) both;
}

@keyframes screenIn {
  from { opacity: 0; transform: translateY(12px); }
  to { opacity: 1; transform: translateY(0); }
}

/* Quiz question screens slide horizontally instead of the generic fade-up
   above, direction set per-navigation by quizQuestion.js (state.quizDirection
   in app.js) - Next slides in from the right, Back slides in from the left,
   echoing which physical direction the participant just moved through the
   question list. Two-class selectors (.screen.quiz-slide-*) so these
   deliberately win over the plain .screen rule regardless of source order.
   Both fold into the single global prefers-reduced-motion override below
   (near-zero animation-duration), same as every other animation in this
   file - no separate reduced-motion rule needed here. */
.screen.quiz-slide-forward {
  animation: quizSlideInForward var(--transition-med) both;
}
.screen.quiz-slide-backward {
  animation: quizSlideInBackward var(--transition-med) both;
}
@keyframes quizSlideInForward {
  from { opacity: 0; transform: translateX(28px); }
  to { opacity: 1; transform: translateX(0); }
}
@keyframes quizSlideInBackward {
  from { opacity: 0; transform: translateX(-28px); }
  to { opacity: 1; transform: translateX(0); }
}

.screen-inner {
  width: 100%;
  max-width: 720px;
  margin: 0 auto;
  position: relative;
  z-index: 2;
  /* Opaque white content panel so every word of real content sits on a
     guaranteed-legible surface, no matter where it lands on the gradient
     behind it. The gradient still shows through as a coloured "frame"
     around the panel (see .screen's own padding, below). */
  background: var(--color-surface);
  border-radius: var(--radius-lg);
  padding: 30px 26px 38px;
  box-shadow: var(--shadow-lift);
}

.screen--narrow .screen-inner { max-width: 560px; }
.screen--wide .screen-inner { max-width: 960px; }

/* Vertically centres a short screen's content within the viewport instead
   of letting it sit near the top - used only on the instructions screen
   (see .screen--center-v below), which otherwise looks stranded high up
   on tall viewports since it has comparatively little content. */
.screen--center-v { justify-content: center; }

/* ---------------------------------------------------------------------- */
/* Instructions screen ("Step 2 of 3") - centred as one deliberate group   */
/* ----------------------------------------------------------------------
   Root cause of the earlier "not properly centred" look: the list and the
   button row each had their OWN, DIFFERENT max-width (460px / 420px)
   layered on top of .screen-inner's own 560px max-width - three different
   widths, each individually centred, produced a "stepped"/tapering column
   instead of one clean aligned block, which read as misaligned even
   though nothing was technically off-axis. Fixed by giving every element
   in this screen the SAME width (100% of .screen-inner's own content
   box - already itself centred via max-width + margin:0 auto on
   .screen-inner, see above), so the logo, heading, boxes and buttons all
   share one exact left/right edge. The instruction list items stay
   left-aligned text inside their own row (each is a flex row, so the
   parent's text-align:center never misaligns them) - centring long body
   text can hurt readability, so only the short standalone lines
   (eyebrow/heading) are centred. */
.instructions-panel .sams-header {
  justify-content: center;
}
/* v2.17.0: explicit width/max-width/margin-inline on both the panel and the
   list itself, on top of the existing .screen-inner centring - belt and
   braces so the group's horizontal centre is never dependent on exactly one
   rule elsewhere in the cascade. max-width here is comfortably inside
   .screen--narrow .screen-inner's own 560px cap, so in practice it never
   constrains anything further on this narrow screen - it's here so the
   intent (list itself has a deliberate, self-contained max-width) is
   explicit rather than implicit. */
.instructions-panel {
  width: 100%;
  max-width: 560px;
  margin-inline: auto;
}
.instructions-panel .instructions-list {
  width: 100%;
  max-width: 520px;
  margin-inline: auto;
  text-align: left;
}
.instructions-panel .instructions-list li {
  width: 100%;
  box-sizing: border-box;
}
/* Bare .instructions-nav (also reused as-is by reviewScreen.js's own,
   wider .screen-inner) stays unconstrained here; the narrower max-width
   belongs only to the instructions screen, where it needs to line up with
   the instructions-list's own 520px cap above - see the
   .instructions-panel .instructions-nav override right after this rule. */
.instructions-nav {
  width: 100%;
  justify-content: center;
  gap: 20px;
  margin-top: 28px;
}
.instructions-panel .instructions-nav {
  max-width: 520px;
  margin-inline: auto;
}
@media (max-width: 480px) {
  .instructions-nav {
    flex-wrap: wrap;
  }
  .instructions-nav .btn { flex: 1; min-width: 130px; }
}

@media (max-width: 480px) {
  .screen-inner {
    padding: 22px 18px 30px;
    border-radius: var(--radius-md);
  }
}

/* ---------------------------------------------------------------------- */
/* Result reveal - staged entrance, restrained and celebratory (v2.14.0)   */
/* ----------------------------------------------------------------------
   Only applied on the result page (.screen-inner.result-reveal), so it
   never affects any other screen. Four explicit stages instead of one
   flat stagger, matching the brief's sequence and rough timing budget
   (~2-3s total): primary trait -> supporting trait/combo/summary ->
   percentages -> everything else (Why this result?, Explore My Full DNA,
   Story-card preview, Download/Share, Connect with SAms, footer), the
   last of which staggers its own direct children on top. See
   resultPage.js for exactly what HTML lands in each stage and why the
   percentage bar-fill/count-up JS animation (traitBreakdown.js) is timed
   to start alongside stage 3 rather than instantly on render.

   .is-settled (added by resultPage.js if this component ever renders a
   SECOND time for the same already-shown result - e.g. a browser
   back/forward landing back on 'result') skips every stage's animation so
   the page just appears fully in place instead of replaying the reveal. */
.result-reveal-primary,
.result-reveal-supporting,
.result-reveal-percentages,
.result-reveal-rest > * {
  opacity: 0;
  transform: translateY(18px);
  animation: resultRevealIn cubic-bezier(0.22, 1, 0.36, 1) both;
}
.result-reveal-primary { animation-duration: 650ms; animation-delay: 0s; }
.result-reveal-supporting { animation-duration: 450ms; animation-delay: 0.5s; }
.result-reveal-percentages { animation-duration: 550ms; animation-delay: 0.85s; }
.result-reveal-rest > * { animation-duration: 500ms; }
.result-reveal-rest > *:nth-child(1) { animation-delay: 1.3s; }
.result-reveal-rest > *:nth-child(2) { animation-delay: 1.42s; }
.result-reveal-rest > *:nth-child(3) { animation-delay: 1.54s; }
.result-reveal-rest > *:nth-child(4) { animation-delay: 1.66s; }
.result-reveal-rest > *:nth-child(5) { animation-delay: 1.78s; }
.result-reveal-rest > *:nth-child(n+6) { animation-delay: 1.9s; }
@keyframes resultRevealIn {
  from { opacity: 0; transform: translateY(18px); }
  to { opacity: 1; transform: translateY(0); }
}

/* The trait name gets its own extra "pop" layered on top of the stage-1
   block's own fade/lift - per the brief, this is meant to be the single
   strongest moment of motion on the page, without tipping into a bounce/
   confetti feel (a controlled scale+fade, nothing more). */
.result-trait-name {
  animation: resultTraitNamePop 550ms cubic-bezier(0.22, 1, 0.36, 1) both;
  animation-delay: 0.15s;
}
@keyframes resultTraitNamePop {
  from { opacity: 0; transform: scale(0.92); }
  to { opacity: 1; transform: scale(1); }
}

/* Short "draw on" for the constellation lines behind the primary trait -
   see renderTraitMotifSvg() in resultPage.js. A generous fixed
   stroke-dasharray (comfortably longer than any line this SVG ever draws,
   given its viewBox) is the standard SVG line-draw technique: the line
   starts fully hidden (dashoffset = dasharray) and animates to fully
   visible (dashoffset: 0), regardless of that particular line's actual
   length. Purely decorative, so this is the one piece of motion the brief
   asked to skip outright under reduced motion - the app's existing global
   prefers-reduced-motion override (near-zero animation-duration) already
   does exactly that, snapping it to its finished state instantly. */
.result-trait-motif svg line {
  stroke-dasharray: 600;
  stroke-dashoffset: 600;
  animation: constellationDraw 900ms ease-out both;
  animation-delay: 0.2s;
}
@keyframes constellationDraw {
  to { stroke-dashoffset: 0; }
}

.result-reveal.is-settled .result-reveal-primary,
.result-reveal.is-settled .result-reveal-supporting,
.result-reveal.is-settled .result-reveal-percentages,
.result-reveal.is-settled .result-reveal-rest > *,
.result-reveal.is-settled .result-trait-name,
.result-reveal.is-settled .result-trait-motif svg line {
  animation: none;
  opacity: 1;
  transform: none;
  stroke-dashoffset: 0;
}

/* ---------------------------------------------------------------------- */
/* DNA background                                                          */
/* ---------------------------------------------------------------------- */
.dna-bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
}

.dna-bg svg {
  width: 100%;
  height: 100%;
}

.dna-bg .strand {
  stroke-dasharray: 8 10;
  animation: dnaFlow 26s linear infinite;
}
.dna-bg .strand--b {
  animation-duration: 32s;
  animation-direction: reverse;
}
@keyframes dnaFlow {
  to { stroke-dashoffset: -400; }
}

.dna-bg .node {
  animation: nodePulse 4.5s ease-in-out infinite;
  transform-origin: center;
}
@keyframes nodePulse {
  0%, 100% { opacity: 0.35; transform: scale(1); }
  50% { opacity: 0.9; transform: scale(1.25); }
}

/* Soft blurred "glow orb" ambient shapes, sitting behind the DNA strands.
   Plain CSS radial-gradient + blur + slow drift - a lightweight, dependency-
   free stand-in for the WebGL orb/aurora backgrounds in the react-bits
   reference library, kept well within the suggested 0.05-0.25 opacity range
   for large decorative forms so they never compete with real content. */
.dna-glow {
  position: absolute;
  border-radius: 50%;
  filter: blur(60px);
  opacity: 0.16;
  will-change: transform;
}
.dna-glow--red {
  width: 420px;
  height: 420px;
  top: -10%;
  right: -8%;
  background: radial-gradient(circle, var(--sams-red) 0%, transparent 70%);
  animation: glowDriftA 22s ease-in-out infinite;
}
.dna-glow--ink {
  width: 380px;
  height: 380px;
  bottom: -12%;
  left: -10%;
  background: radial-gradient(circle, var(--sams-charcoal) 0%, transparent 70%);
  opacity: 0.14;
  animation: glowDriftB 26s ease-in-out infinite;
}
.dna-glow--soft {
  width: 260px;
  height: 260px;
  top: 40%;
  left: 55%;
  background: radial-gradient(circle, var(--sams-red-bright) 0%, transparent 70%);
  opacity: 0.1;
  animation: glowDriftA 30s ease-in-out infinite reverse;
}
@keyframes glowDriftA {
  0%, 100% { transform: translate(0, 0) scale(1); }
  50% { transform: translate(-24px, 30px) scale(1.08); }
}
@keyframes glowDriftB {
  0%, 100% { transform: translate(0, 0) scale(1); }
  50% { transform: translate(28px, -24px) scale(1.05); }
}

/* Reduced decorative density on narrow/mobile viewports - fewer glow shapes
   and a smaller footprint, both for a calmer mobile composition and for
   performance on event tablets/phones. */
@media (max-width: 640px) {
  .dna-glow--soft { display: none; }
  .dna-glow--red { width: 280px; height: 280px; opacity: 0.12; }
  .dna-glow--ink { width: 240px; height: 240px; opacity: 0.1; }
}

/* ---------------------------------------------------------------------- */
/* SAms mascot - official artwork, never redrawn/recoloured (see           */
/* src/components/samsMascot.js). Purely decorative: pointer-events:none,  */
/* animation is a slow, small float+tilt (never spinning, shaking or       */
/* squashing), and is disabled entirely under prefers-reduced-motion by    */
/* the global override below rather than just sped up.                    */
/* ---------------------------------------------------------------------- */
.mascot { pointer-events: none; }
.landing-mascot {
  display: flex;
  justify-content: center;
  margin: 4px 0 18px;
}
.landing-mascot img { height: clamp(90px, 18vw, 150px) !important; }
@media (max-width: 480px) {
  .landing-mascot { margin: 2px 0 14px; }
}
/* v2.16.0 mobile refinement: logo + mascot as one row (see resultPage.js's
   .result-hero-brand wrapper). Stacked-but-tight on desktop/tablet (matches
   the pre-existing look almost exactly - just wrapped in one flex column so
   the -8px pull-up below still applies), but a genuine side-by-side row
   under 480px, where two separately-centred stacked blocks were previously
   eating a disproportionate share of a short mobile viewport's vertical
   space before the participant even reaches their result. */
.result-hero-brand {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
}
.result-hero-mascot {
  display: flex;
  justify-content: center;
  margin-bottom: -8px; /* pulls it visually close to the result title below */
}
.result-hero-mascot img { height: clamp(70px, 14vw, 110px) !important; }
@media (max-width: 480px) {
  .result-hero-brand {
    flex-direction: row;
    justify-content: center;
    gap: 14px;
    margin-bottom: 4px;
  }
  .result-hero-logo img { height: 44px !important; }
  .result-hero-mascot { margin-bottom: 0; }
  .result-hero-mascot img { height: clamp(52px, 16vw, 72px) !important; }
}
.social-cta-mascot {
  display: flex;
  justify-content: center;
  margin-bottom: 8px;
}
.social-cta-mascot img { height: clamp(56px, 10vw, 90px) !important; }
.mascot--float {
  animation: mascotFloat 4s ease-in-out infinite;
  transform-origin: center bottom;
}
@keyframes mascotFloat {
  0%, 100% { transform: translateY(0) rotate(0deg); }
  50% { transform: translateY(-6px) rotate(1.5deg); }
}
@media (prefers-reduced-motion: reduce) {
  .mascot--float { animation: none; }
}

/* ---------------------------------------------------------------------- */
/* Typography                                                              */
/* ---------------------------------------------------------------------- */
.eyebrow {
  text-transform: uppercase;
  letter-spacing: 0.14em;
  font-size: 0.78rem;
  font-weight: 700;
  color: var(--color-ink-muted);
}

h1, h2, h3 {
  line-height: 1.15;
  font-weight: 800;
  margin: 0 0 12px;
}

/* h1 is only ever the landing-page title or the result page's trait name -
   both explicitly called out for the playful display font. Fredoka tops
   out at 700 (it has no true 800/900 weight), which is still the boldest,
   most confident cut the family ships. */
h1 {
  font-size: clamp(2rem, 5vw, 3.1rem);
  font-family: var(--font-display);
  font-weight: 700;
}
h2 { font-size: clamp(1.5rem, 3.4vw, 2.1rem); }
h3 { font-size: 1.2rem; }

/* Utility for the handful of short labels/big headings (not long-form
   text) that also get the playful display font - see brief section 1's
   "large section headings" / "selected short labels". */
.font-display { font-family: var(--font-display); }

p { margin: 0 0 16px; color: var(--color-ink-muted); }

.text-center { text-align: center; }

/* ---------------------------------------------------------------------- */
/* Buttons                                                                 */
/* ---------------------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  border: none;
  border-radius: 999px;
  padding: 16px 32px;
  font-size: 1.05rem;
  font-weight: 700;
  cursor: pointer;
  /* Transform uses the spring-like curve so hover/tap feedback feels closer
     to Framer Motion's whileHover/whileTap spring physics than a linear
     ease would - see --spring in :root above. */
  transition: transform var(--spring), box-shadow var(--transition-fast), background var(--transition-fast), opacity var(--transition-fast), border-color var(--transition-fast), color var(--transition-fast);
  min-height: 52px;
  touch-action: manipulation;
}
.btn:active { transform: scale(0.97); }
.btn:disabled {
  opacity: 0.45;
  cursor: not-allowed;
  transform: none;
}

.btn-primary {
  background: var(--color-tt);
  color: var(--sams-white);
  box-shadow: var(--shadow-soft);
}
.btn-primary:hover:not(:disabled) {
  background: var(--sams-red-dark);
  box-shadow: var(--shadow-lift);
  transform: translateY(-2px) scale(1.03);
}

.btn-secondary {
  background: var(--color-surface);
  color: var(--color-ink);
  border: 2px solid var(--color-border);
}
.btn-secondary:hover:not(:disabled) {
  border-color: var(--color-tt);
  color: var(--color-tt);
  transform: scale(1.015);
}
.btn-secondary:active:not(:disabled) { transform: scale(0.985); }

.btn-ghost {
  background: transparent;
  color: var(--color-ink-muted);
  padding: 12px 20px;
}
.btn-ghost:hover:not(:disabled) {
  color: var(--color-tt);
  transform: scale(1.015);
}
.btn-ghost:active:not(:disabled) { transform: scale(0.985); }

.btn-block { width: 100%; }
.btn-sm { padding: 10px 20px; font-size: 0.92rem; min-height: 40px; }

/* ---------------------------------------------------------------------- */
/* Icons (hand-built, replace every emoji - see components/icons.js)      */
/* ---------------------------------------------------------------------- */
.icon {
  display: block;
  flex-shrink: 0;
}

/* Back navigation - same felt motion as the brief's Framer Motion
   whileHover/whileTap spec (nudge left + scale up on hover, scale down on
   press), done in plain CSS since this project has no React/Framer Motion
   available (see README). The scale-down-on-press part is already covered
   by the global `.btn:active` rule. */
.btn-back:hover:not(:disabled) {
  transform: translateX(-2px) scale(1.02);
}

/* ---------------------------------------------------------------------- */
/* QR badge - always self-centres, wherever it's placed                    */
/* ---------------------------------------------------------------------- */
.qr-section {
  display: flex;
  justify-content: center;
}
.qr-badge-wrap {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  margin: 0 auto;
  max-width: 260px;
}
.qr-badge-box {
  width: 150px;
  height: 150px;
  padding: 10px;
  background: #ffffff;
  border: 1px solid var(--color-border);
  border-radius: 16px;
  box-shadow: var(--shadow-soft);
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
  z-index: 1; /* stays above any decorative background animation */
}
@media (min-width: 600px) {
  .qr-badge-box { width: 170px; height: 170px; }
}
@media (min-width: 1024px) {
  .qr-badge-box { width: 190px; height: 190px; }
}
.qr-badge-box--fallback {
  flex-direction: column;
  gap: 8px;
  border: 1.5px dashed var(--color-border);
  color: var(--color-tt);
  box-shadow: none;
}
.qr-badge-caption {
  /* Cleared past the decorative ring below (.qr-badge-box::after extends
     22px beyond the box edge), plus real breathing room on top of that -
     previously only 10px, which visually collided with the ring. */
  margin: 32px 0 0;
  max-width: 300px;
  font-size: 0.82rem;
  color: var(--color-ink-muted);
}
.qr-badge-fallback-link {
  font-size: 0.8rem;
  font-weight: 700;
  color: var(--color-tt);
  word-break: break-word;
  padding: 0 6px;
}

.btn-row {
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
  justify-content: center;
}

/* ---------------------------------------------------------------------- */
/* Cards                                                                   */
/* ---------------------------------------------------------------------- */
.card {
  background: var(--color-surface);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-soft);
  padding: 32px;
  border: 1px solid var(--color-border);
}

@media (max-width: 480px) {
  .card { padding: 22px; border-radius: var(--radius-md); }
}

/* ---------------------------------------------------------------------- */
/* Nickname screen - deliberate vertical rhythm (v2.14.0 polish pass)      */
/* ----------------------------------------------------------------------
   Structure: heading -> short supporting sentence -> nickname input ->
   privacy notice -> Continue button, each separated by one step of the
   spacing scale (--space-*) instead of ad-hoc margins, so the screen reads
   as intentionally composed rather than cramped. Centred and unchanged in
   substance - purely spacing/hierarchy, no new fields. */
.name-entry-panel .eyebrow { margin-bottom: var(--space-8); }
.name-entry-panel h2 { margin-bottom: var(--space-12); }
.name-entry-subtext {
  margin: 0 0 var(--space-24);
  font-size: 1.05rem;
  color: var(--color-ink-muted);
}
.name-entry-panel .card--with-mascot { margin-top: var(--space-8); }
.name-entry-panel .field-label { margin-bottom: var(--space-12); }
.name-entry-panel .field-hint { margin-top: var(--space-8); }
.name-entry-panel .privacy-notice {
  margin-top: var(--space-16);
  padding-top: var(--space-16);
}
.name-entry-actions {
  margin-top: var(--space-24);
  justify-content: space-between;
}

/* Mascot peeking over the top-right corner of the nickname-entry card -
   "near the form card" per the brief, kept well inside the screen's own
   side padding so it can never cause horizontal overflow on narrow phones. */
.card--with-mascot { position: relative; overflow: visible; }
.card-mascot {
  position: absolute;
  top: -34px;
  right: 18px;
  z-index: 1;
}
.card-mascot img { height: clamp(60px, 12vw, 84px) !important; }

/* ---------------------------------------------------------------------- */
/* Landing page                                                            */
/* ---------------------------------------------------------------------- */
.landing-hero {
  text-align: center;
  padding-top: 40px;
}

.landing-hero .btn-primary {
  margin: 28px 0 8px;
  font-size: 1.2rem;
  padding: 18px 44px;
}

/* Official SITizen-DNA info link - visually secondary to Start Quiz (plain
   underlined text, no button chrome) but still clearly visible/tappable,
   on both the landing page and the result page. */
.official-link-row {
  margin: 4px 0 0;
}
.official-link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 0.88rem;
  font-weight: 600;
  color: var(--color-ink-muted);
  text-decoration: underline;
  text-underline-offset: 2px;
  transition: color var(--transition-fast);
}
.official-link:hover,
.official-link:focus-visible {
  color: var(--color-tt);
}

.meta-row {
  display: flex;
  gap: 18px;
  justify-content: center;
  flex-wrap: wrap;
  color: var(--color-ink-muted);
  font-size: 0.92rem;
  margin-bottom: 8px;
}
.meta-row span {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.trait-preview-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 16px;
  margin-top: 36px;
}
/* v2.17.0: breakpoint lowered from 560px to 420px, per the refinement
   brief's explicit "1 column on very small screens, 2 columns above
   approximately 420px" - the old 560px threshold meant a fairly common
   phone width like 430px (one of this app's own standard test widths) was
   still forced to a single column, making the landing page noticeably
   longer/more cramped-feeling than it needed to be at that size. */
@media (max-width: 420px) {
  .trait-preview-grid { grid-template-columns: 1fr; }
}

.trait-preview-card {
  border-radius: var(--radius-md);
  padding: 20px;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-top: 6px solid var(--trait-color, var(--color-tt));
  text-align: left;
  box-shadow: var(--shadow-soft);
}
.trait-preview-card h3 {
  color: var(--trait-color, var(--color-ink));
  margin-bottom: 6px;
}
.trait-preview-card p {
  font-size: 0.92rem;
  margin: 0;
}
/* v2.17.0: now that two columns kick in as low as 420px (see
   .trait-preview-grid above), each card's own column is fairly narrow at
   that width - less internal padding so the trait name/tagline get more of
   it, rather than the cards feeling cramped. */
@media (max-width: 480px) {
  .trait-preview-card { padding: 14px; }
}

.no-wrong-answers {
  margin-top: 28px;
  padding: 14px 20px;
  border-radius: var(--radius-sm);
  background: rgba(214, 0, 0, 0.06);
  background: color-mix(in srgb, var(--color-tt) 7%, var(--sams-white));
  border: 1px solid rgba(214, 0, 0, 0.15);
  color: var(--color-ink);
  font-size: 0.95rem;
  display: inline-block;
}

/* ---------------------------------------------------------------------- */
/* Nickname + instructions                                                 */
/* ---------------------------------------------------------------------- */
.field-label {
  display: block;
  font-weight: 700;
  margin-bottom: 10px;
}

.text-input {
  width: 100%;
  font-size: 1.15rem;
  padding: 16px 18px;
  border-radius: var(--radius-sm);
  border: 2px solid var(--color-border);
  background: var(--color-surface);
  transition: border-color var(--transition-fast);
}
.text-input:focus {
  border-color: var(--color-tt);
}

.field-hint {
  /* v2.17.0: raised from 0.85rem to the brief's own caption floor
     (0.875-0.9rem) - was a hair below even the smallest text size the
     refinement brief asks for anywhere in the app. */
  font-size: 0.875rem;
  color: var(--color-ink-muted);
  margin-top: 8px;
}

/* Brief, non-intrusive data-collection notice directly below the nickname
   field - shown only when config.dataCollection.enabled is true (see
   nameEntry.js). Visually subtle (small, muted, no icon/border/background)
   but still fully readable - never a modal, never a mandatory checkbox. */
.privacy-notice {
  /* v2.17.0: raised from 0.8rem to the same 0.875rem caption floor - still
     clearly the quietest text on the screen (no icon/border/background,
     directly under the field-hint), just no longer below the app's own
     stated minimum for any caption-level text. */
  font-size: 0.875rem;
  line-height: 1.5;
  color: var(--color-ink-muted);
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid var(--color-border);
}

.field-error {
  color: var(--color-danger);
  font-size: 0.88rem;
  margin-top: 8px;
  min-height: 1.2em;
}

.instructions-list {
  display: grid;
  gap: 14px;
  margin: 24px 0 32px;
  /* Real root cause of the numbered boxes reading as off-centre (confirmed
     via DevTools box-model inspection): every browser's default UA
     stylesheet gives <ul>/<ol> elements a 40px left/start padding, reserved
     for bullet indentation. list-style:none on the <li> below removes the
     bullet GLYPH, but does nothing about that leftover 40px padding on the
     <ul> itself - so the four rows were shifted 40px right of the heading
     and Back/Let's go buttons (neither of which are lists, so neither had
     this padding), even though .screen-inner/.instructions-nav were - and
     still are - correctly centred the whole time. */
  padding: 0;
  list-style: none;
}
.instructions-list li {
  list-style: none;
  display: flex;
  gap: 14px;
  align-items: flex-start;
  padding: 14px 16px;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
}
.instructions-list .num {
  flex-shrink: 0;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: var(--color-tt);
  color: white;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  font-size: 0.9rem;
}

/* ---------------------------------------------------------------------- */
/* Review screen (all answers, before result calculation)                 */
/* ---------------------------------------------------------------------- */
.review-subtitle {
  color: var(--color-ink-muted);
  margin-top: 8px;
  font-size: 0.95rem;
}
.review-list {
  display: grid;
  gap: 10px;
  margin: 22px 0 30px;
  padding: 0;
  list-style: none;
  max-height: 52vh;
  overflow-y: auto;
  /* A little breathing room so the last row's focus ring/hover state isn't
     clipped by the scroll container edge. */
  padding-right: 2px;
}
.review-row {
  list-style: none;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 14px;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
}
.review-row-body {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  flex: 1;
  min-width: 0;
}
.review-row-num {
  flex-shrink: 0;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: var(--color-tt);
  color: white;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  font-size: 0.8rem;
}
.review-row-text { min-width: 0; }
.review-row-scenario {
  font-size: 0.78rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  color: var(--color-ink-muted);
  margin-bottom: 2px;
}
.review-row-answer {
  font-size: 0.92rem;
  color: var(--color-ink);
  overflow: hidden;
  text-overflow: ellipsis;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}
.review-edit-btn {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--color-tt);
  background: none;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  /* Comfortably >=44px tall (the standard minimum recommended touch
     target), not just the font's own natural height, so this is easy to
     tap accurately on a phone screen even inside a fairly dense list. */
  padding: 13px 14px;
  min-height: 44px;
  cursor: pointer;
  touch-action: manipulation;
  transition: background-color 150ms ease, border-color 150ms ease;
}
.review-edit-btn:hover,
.review-edit-btn:focus-visible {
  background: color-mix(in srgb, var(--color-tt) 8%, transparent);
  border-color: var(--color-tt);
}
@media (max-width: 480px) {
  .review-row { flex-wrap: wrap; }
  .review-edit-btn { margin-left: 38px; }
}

/* ---------------------------------------------------------------------- */
/* Progress bar                                                            */
/* ---------------------------------------------------------------------- */
.progress-wrap {
  width: 100%;
  margin-bottom: 24px;
}
.progress-stage {
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-tt);
  margin: 0 0 6px;
}
.progress-label {
  display: flex;
  justify-content: space-between;
  font-size: 0.85rem;
  color: var(--color-ink-muted);
  font-weight: 600;
  margin-bottom: 8px;
}
.progress-track {
  width: 100%;
  height: 10px;
  background: var(--color-border);
  border-radius: 999px;
  overflow: hidden;
}
.progress-fill {
  height: 100%;
  background: linear-gradient(90deg, var(--color-tt), var(--color-lur), var(--color-ct), var(--color-gc));
  border-radius: 999px;
  transition: width var(--transition-med);
}

/* ---------------------------------------------------------------------- */
/* Quiz question                                                           */
/* ---------------------------------------------------------------------- */
.quiz-scenario {
  font-size: 0.9rem;
  font-weight: 700;
  color: var(--color-tt);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  margin-bottom: 6px;
}

.answers-grid {
  display: grid;
  gap: 14px;
  margin: 24px 0 28px;
}

.answer-card {
  display: flex;
  align-items: center;
  gap: 16px;
  width: 100%;
  text-align: left;
  padding: 20px 22px;
  min-height: 64px;
  border-radius: var(--radius-md);
  border: 2px solid var(--color-border);
  background: var(--color-surface);
  cursor: pointer;
  font-size: 1.05rem;
  color: var(--color-ink);
  transition: border-color var(--transition-fast), box-shadow var(--transition-fast), transform var(--spring), background var(--transition-fast);
}
.answer-card:hover {
  border-color: var(--color-tt);
  background: rgba(214, 0, 0, 0.03);
  background: color-mix(in srgb, var(--color-tt) 4%, var(--sams-white));
  box-shadow: var(--shadow-soft);
  transform: translateY(-3px);
}
.answer-card:active {
  transform: scale(0.99);
}
.answer-card:disabled {
  color: var(--color-ink-muted);
  border-color: var(--color-border);
  background: var(--sams-grey-light);
  cursor: not-allowed;
  opacity: 0.7;
}
.answer-card .marker {
  flex-shrink: 0;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  border: 2px solid var(--color-border);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.85rem;
  font-weight: 800;
  color: transparent;
  transition: all var(--transition-fast);
}
/* Persistent selected state - applied on every render (initial mount,
   navigating back to a previously-answered question, etc.) so it never
   depends on an animation having played. Deliberately stronger than before:
   a heavier 3px border, a visible tinted background, AND a soft coloured
   glow ring (box-shadow) - so the selection reads clearly even for anyone
   less sensitive to the subtler tint/border-colour-only difference alone. */
.answer-card.is-selected {
  border-color: var(--color-tt);
  border-width: 3px;
  padding: 19px 21px; /* -1px to offset the +1px border so the card doesn't shift size */
  background: color-mix(in srgb, var(--color-tt) 7%, var(--sams-white));
  box-shadow: var(--shadow-soft), 0 0 0 4px color-mix(in srgb, var(--color-tt) 14%, transparent);
}
.answer-card .marker .icon {
  opacity: 0; /* checkmark icon fades in on selection - never colour-only */
  transition: opacity var(--transition-fast);
}
.answer-card.is-selected .marker {
  background: var(--color-tt);
  border-color: var(--color-tt);
  color: var(--sams-white);
}
.answer-card.is-selected .marker .icon {
  opacity: 1;
}

/* Transient "just picked this" flourish - added briefly by quizQuestion.js
   ONLY at the moment the participant actively selects an answer (click,
   Enter/Space, or a 1-4 number key), then removed once the animation ends.
   Kept deliberately separate from `.is-selected` above so simply mounting
   the screen (e.g. pressing Back to a question you already answered) shows
   the strong static state instantly, without replaying a pop animation
   that's only meaningful as feedback for the live act of choosing. */
.answer-card.answer-just-selected {
  animation: answerSelectPop var(--spring);
}
.answer-card.answer-just-selected .marker .icon {
  animation: answerMarkerPop 220ms ease-out;
}
@keyframes answerSelectPop {
  0% { transform: scale(0.97); }
  55% { transform: scale(1.02); }
  100% { transform: scale(1); }
}
@keyframes answerMarkerPop {
  0% { transform: scale(0); opacity: 0; }
  60% { transform: scale(1.3); opacity: 1; }
  100% { transform: scale(1); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .answer-card.answer-just-selected,
  .answer-card.answer-just-selected .marker .icon {
    animation: none;
  }
}

.quiz-nav {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  margin-top: 8px;
}
.quiz-nav .spacer { flex: 1; }

@media (max-width: 480px) {
  .quiz-nav { flex-wrap: wrap; }
  .quiz-nav .btn { flex: 1; min-width: 120px; }
}

/* ---------------------------------------------------------------------- */
/* Loading screen                                                           */
/* ---------------------------------------------------------------------- */
.loading-screen {
  min-height: 100vh;
  min-height: 100dvh;
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}
/* This screen doesn't use .screen-inner (it's a centred beat, not a
   document-style page), so it gets its own white panel here for the same
   reason: the gradient behind it can be quite dark at vertical centre, so
   the logo/spinner/message need a guaranteed-legible surface of their own. */
.loading-panel {
  width: 100%;
  max-width: 420px;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 26px;
  background: var(--color-surface);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lift);
  padding: 44px 32px;
}
.dna-spinner {
  width: 96px;
  height: 96px;
  position: relative;
}
/* Mascot layered on top of the spinner rings on the loading screen - a
   more prominent placement per the brief, with a one-time scale/fade
   entrance (separate from the mascot's own continuous float, which is
   unaffected since they're on different elements). */
.loading-mascot-wrap {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 8px 0;
}
.loading-mascot {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  animation: mascotScaleIn 600ms cubic-bezier(0.22, 1, 0.36, 1) both;
}
@keyframes mascotScaleIn {
  from { opacity: 0; transform: scale(0.8); }
  to { opacity: 1; transform: scale(1); }
}
.dna-spinner svg {
  width: 100%;
  height: 100%;
  animation: spin 2.4s linear infinite;
}
@keyframes spin {
  to { transform: rotate(360deg); }
}
.loading-message {
  font-size: 1.15rem;
  font-weight: 600;
  color: var(--color-ink-muted);
  min-height: 1.6em;
  animation: fadeMessage 1.4s ease-in-out infinite;
}

/* ---------------------------------------------------------------------- */
/* Transition screen (brief beat between Start Quiz -> nickname, and       */
/* instructions -> quiz)                                                   */
/* ---------------------------------------------------------------------- */
.transition-screen {
  min-height: 100vh;
  min-height: 100dvh;
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  animation: transitionIn 550ms cubic-bezier(0.22, 1, 0.36, 1) both;
}
/* Same reasoning as .loading-panel above - a guaranteed-white surface for
   the logo/spinner/message, since this screen also sits at vertical centre
   of the full-page gradient, which can be quite dark there. */
.transition-panel {
  width: 100%;
  max-width: 420px;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 22px;
  background: var(--color-surface);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lift);
  padding: 44px 32px;
}
.transition-logo {
  animation: transitionLogoPop 650ms cubic-bezier(0.34, 1.56, 0.64, 1) both;
}
.transition-message {
  font-size: 1.1rem;
  font-weight: 700;
  color: var(--color-ink);
}
@keyframes transitionIn {
  from { opacity: 0; }
  to { opacity: 1; }
}
@keyframes transitionLogoPop {
  0% { opacity: 0; transform: scale(0.8); }
  70% { opacity: 1; transform: scale(1.05); }
  100% { opacity: 1; transform: scale(1); }
}
@keyframes fadeMessage {
  0%, 100% { opacity: 0.55; }
  50% { opacity: 1; }
}

/* ---------------------------------------------------------------------- */
/* Result page                                                             */
/* ---------------------------------------------------------------------- */
.result-hero {
  text-align: center;
  padding: 12px 0 8px;
}
.result-greeting {
  font-size: 1.1rem;
  color: var(--color-ink-muted);
  font-weight: 600;
}
.result-trait-name {
  font-size: clamp(2.1rem, 6vw, 3.2rem);
  font-weight: 800;
  margin: 6px 0 4px;
  color: var(--trait-color, var(--color-tt));
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 10px;
  text-align: center;
}
.result-trait-name .icon {
  width: clamp(24px, 5vw, 32px);
  height: clamp(24px, 5vw, 32px);
}
/* v2.17.0: the trait name text lives in its own flex-item span (see
   resultPage.js) specifically so it can wrap independently of the icon
   beside it - text-wrap:balance asks the browser to split a wrapped
   heading across an even number of visually-balanced lines (e.g. "Able to
   Learn, / Unlearn and Relearn" rather than one long line plus a short
   orphan word) instead of the default "greedy" wrap. No-op in browsers
   that don't yet support it, which simply keeps the ordinary wrap. */
.result-trait-name-text {
  text-wrap: balance;
}
.result-tagline {
  font-style: italic;
  font-size: 1.15rem;
  color: var(--color-ink-muted);
  margin-bottom: 8px;
}

/* v2.18.0: a short, nickname-stable personality summary gives the hero a
   more personal 'award reveal' feel without adding another heavy card. */
.result-one-liner {
  width: min(100%, 620px);
  margin: var(--space-16) auto 0;
  padding: var(--space-16) var(--space-20, 20px);
  border-top: 1px solid color-mix(in srgb, var(--trait-color, var(--color-tt)) 28%, transparent);
  border-bottom: 1px solid color-mix(in srgb, var(--trait-color, var(--color-tt)) 18%, transparent);
}
.result-one-liner-label {
  display: block;
  margin-bottom: var(--space-8);
  color: var(--trait-color, var(--color-tt));
  font-family: var(--font-display);
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.result-one-liner p {
  margin: 0;
  font-size: 1.05rem;
  line-height: 1.62;
  font-weight: 600;
  color: var(--color-ink);
}
.result-deep-dive-intro {
  margin: 0 0 var(--space-24);
  font-size: 1rem;
  line-height: 1.65;
}
.social-cta-challenge {
  margin: 0 auto var(--space-16);
  max-width: 520px;
  color: rgba(255,255,255,0.8);
  font-size: 0.92rem;
  line-height: 1.5;
}
@media (max-width: 480px) {
  .result-one-liner {
    padding: var(--space-12) 0;
    margin-top: var(--space-12);
  }
  .result-one-liner p { font-size: 1rem; }
}

.event-mode-banner {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  background: var(--color-ink);
  color: white;
  padding: 8px 18px;
  border-radius: 999px;
  font-size: 0.85rem;
  font-weight: 600;
  margin-bottom: 18px;
}

.result-section-title {
  display: flex;
  align-items: center;
  gap: var(--space-12);
  margin: var(--space-32) 0 var(--space-16);
}
.result-section-title .dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--trait-color, var(--color-tt));
}

.result-grid {
  display: grid;
  gap: 16px;
}
@media (min-width: 640px) {
  .result-grid--2 { grid-template-columns: 1fr 1fr; }
}

.result-block {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: 20px 22px;
}
.result-block h3 {
  font-size: 1rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--color-ink-muted);
  margin-bottom: 10px;
}
.result-block ul {
  margin: 0;
  padding-left: 20px;
}
.result-block li { margin-bottom: 6px; }

.probably-quote {
  border-left: 4px solid var(--trait-color, var(--color-tt));
  padding: 16px 20px;
  background: rgba(0,0,0,0.02);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  font-size: 1.05rem;
  font-style: italic;
}

.closing-message {
  text-align: center;
  font-weight: 700;
  font-size: 1.15rem;
  color: var(--trait-color, var(--color-ink));
  margin: 28px 0 8px;
}

/* v2.13.0: emphasised per request - bold, one step up from body text, and
   full-ink contrast (was muted grey at body size) so it reads as a genuine
   "every trait counts" statement rather than a footnote, while still
   staying clearly secondary to the result page's actual heading (no larger
   than 1.2rem, no display font, no letter-spacing/eyebrow treatment).
   Deliberately has no constellation motif positioned behind it - it sits
   directly under the plain percentage bars (see renderDnaBarsHtml() in
   traitBreakdown.js), not inside any of the decorated hero/explore zones. */
.all-traits-note {
  text-align: center;
  color: var(--color-ink);
  font-weight: 700;
  font-size: 1.15rem;
  line-height: 1.5;
  margin-top: 12px;
}
@media (max-width: 640px) {
  .all-traits-note {
    font-size: 1.05rem;
  }
}

/* ---------------------------------------------------------------------- */
/* Result page - deep-dive content inside "Explore My Full DNA"           */
/* ----------------------------------------------------------------------
   v2.14.0: the separate Quick Result / Explore My Full DNA tabs from
   v2.9.0 are gone - the result page is now one continuous flow (see
   resultPage.js), with this content living inside the "Explore My Full
   DNA" accordion instead of a second tab. */
.result-deep-dive {
  margin-top: var(--space-8);
}
.dna-explore-subheading {
  margin: var(--space-24) 0 var(--space-16);
  font-size: 1rem;
}

/* Combination block (personalised opening + supporting trait + title) */
.result-combo {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-left: 4px solid var(--trait-color, var(--color-tt));
  border-radius: var(--radius-md);
  padding: 20px 22px;
  margin: 8px 0 24px;
  text-align: center;
}
/* v2.16.0 mobile refinement: split into two stacked lines (a small eyebrow
   label, then the trait name + icon on its own bolder line) instead of one
   inline-flex run-on sentence, which had no flex-wrap and could crowd or
   overflow next to a long trait name on a narrow phone (see resultPage.js
   for the matching markup change). */
.result-combo-supporting-label {
  font-size: 0.76rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--color-ink-muted);
  /* v2.17.0: eyebrow-to-trait-name gap widened from 4px to 8px, and the
     gap below the trait name (before the combination title) widened from
     10px to 16px - per the refinement brief's own suggested rhythm, so the
     three lines (label / trait name / combination title) read as two
     clearly separate steps rather than one dense cluster. */
  margin: 0 0 8px;
}
.result-combo-supporting-value {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 6px;
  font-size: 1rem;
  color: var(--color-ink);
  margin: 0 0 16px;
}
.result-combo-title {
  font-size: 1.35rem;
  color: var(--trait-color, var(--color-tt));
  margin: 0 0 10px;
  /* v2.17.0: same balanced-wrap treatment as the primary trait name above -
     "You're The Grounded Community Builder" reads better split evenly than
     with one long line and a short leftover word. */
  text-wrap: balance;
}
.result-combo-opening {
  margin: 0;
  font-size: 1rem;
}

/* v2.17.0: "What do the percentages mean?" is no longer its own card - it
   now reuses the .why-result accordion styling directly (see
   traitBreakdown.js's renderPercentExplainer(), which emits the same
   .why-result/.why-result-toggle/.why-result-panel-wrap markup as "Why this
   result?"), so there is no separate .percent-explainer rule here any more.
   Kept as a comment (not deleted silently) since a few earlier versions'
   changelog entries in README.md reference the old panel by this class name. */

/* ---------------------------------------------------------------------- */
/* Trait breakdown bars                                                    */
/* ---------------------------------------------------------------------- */
.dna-bars {
  display: grid;
  gap: var(--space-24);
  margin: var(--space-24) 0;
}
.dna-bar-row {
  /* Staged reveal: each row fades/slides in with a per-row delay set inline
     via --stagger-delay (see traitBreakdown.js), so the four traits appear
     one after another rather than all at once - matching the percentage
     count-up and bar-fill animations, which use the same delay value. */
  animation: dnaBarRowIn 500ms cubic-bezier(0.22, 1, 0.36, 1) both;
  animation-delay: var(--stagger-delay, 0ms);
}
@keyframes dnaBarRowIn {
  from { opacity: 0; transform: translateY(10px); }
  to { opacity: 1; transform: translateY(0); }
}
.dna-bar-row .dna-bar-label {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  font-weight: 700;
  font-size: 0.95rem;
  margin-bottom: var(--space-8);
}
.dna-bar-row .dna-bar-label > span:first-child {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}
/* Stronger, more deliberate numeral treatment than the trait-name label
   next to it - percentages are the thing someone's eye should land on
   first in this row. */
.dna-bar-pct {
  font-family: var(--font-display);
  font-size: 1.1rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.dna-bar-track {
  width: 100%;
  height: 16px;
  background: var(--color-border);
  border-radius: 999px;
  overflow: hidden;
}
.dna-bar-fill {
  height: 100%;
  border-radius: 999px;
  width: 0%;
  transition: width 900ms cubic-bezier(0.22, 1, 0.36, 1);
}

/* Subtle emphasis for the participant's primary trait row - a soft tinted
   backing card and a touch more weight, without turning it into a second
   heading (the actual "your strongest trait" moment already happened in
   the hero above; this is just a quiet visual echo of it here). */
.dna-bar-row.is-primary {
  /* box-shadow (not padding/negative-margin) so the tinted "spread" is
     purely visual - it never changes this row's actual box size, so it
     can never nudge its own text out of alignment with the other three
     rows, cause horizontal bleed past the panel edge, or eat into the
     grid's gap toward its neighbours on narrow screens. */
  border-radius: var(--radius-sm);
  box-shadow: 0 0 0 var(--space-12) color-mix(in srgb, var(--trait-color, var(--sams-red)) 6%, transparent);
}
.dna-bar-row.is-primary .dna-bar-label > span:first-child {
  font-weight: 800;
}
.dna-bar-row.is-primary .dna-bar-pct {
  font-size: 1.22rem;
  color: var(--trait-color, var(--color-ink));
}
.dna-bar-row.is-primary .dna-bar-track {
  height: 18px;
}
.dna-bar-primary-tag {
  font-size: 0.68rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--trait-color, var(--sams-red));
  background: color-mix(in srgb, var(--trait-color, var(--sams-red)) 12%, transparent);
  border-radius: 999px;
  padding: 2px 8px;
  margin-left: 6px;
  vertical-align: middle;
  white-space: nowrap;
}

/* ---------------------------------------------------------------------- */
/* "Why this result?" accordion                                            */
/* ---------------------------------------------------------------------- */
.why-result {
  margin: var(--space-24) 0;
  border-top: 1px solid var(--color-border);
  border-bottom: 1px solid var(--color-border);
}
.why-result-toggle {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-12);
  background: none;
  border: none;
  padding: var(--space-16) var(--space-8);
  font: inherit;
  font-weight: 700;
  font-size: 1rem;
  color: var(--color-ink);
  cursor: pointer;
  text-align: left;
  transition: color var(--transition-fast);
}
.why-result-toggle:hover {
  color: var(--sams-red);
}
.why-result-chevron {
  display: inline-flex;
  transition: transform var(--transition-fast);
  flex-shrink: 0;
}
.why-result-toggle[aria-expanded="true"] .why-result-chevron {
  transform: rotate(180deg);
}
/* CSS grid 0fr->1fr row trick: smoothly animates open/close height without
   any JS scrollHeight measuring, and degrades gracefully to an instant
   snap under the app's existing prefers-reduced-motion override (which
   forces every transition-duration near-zero, same as the rest of the
   app - no special-cased branch needed here). */
.why-result-panel-wrap {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows var(--transition-med);
}
.why-result-panel-wrap.is-open {
  grid-template-rows: 1fr;
}
.why-result-panel-inner {
  overflow: hidden;
}
.why-result-panel-inner p {
  margin: 0 0 var(--space-16);
  padding: 0 var(--space-8);
  color: var(--color-ink-muted);
  /* v2.17.0: raised from 0.92rem - this is genuinely-secondary explainer
     copy, so 0.95rem (the brief's own stated floor for secondary/helper
     text) rather than shrinking it further to save space. */
  font-size: 0.95rem;
  line-height: 1.6;
}

.dna-explore {
  margin-top: 24px;
}
.dna-explore summary {
  cursor: pointer;
  font-weight: 700;
  padding: 16px 20px;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  list-style: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  transition: border-color var(--transition-fast), background var(--transition-fast), transform var(--spring-fast);
}
.dna-explore summary:hover {
  border-color: var(--color-tt);
  background: color-mix(in srgb, var(--color-tt) 4%, var(--sams-white));
}
.dna-explore summary:active {
  transform: scale(0.99);
}
.dna-explore summary::-webkit-details-marker { display: none; }
.dna-explore summary::after {
  content: '+';
  font-size: 1.3rem;
  transition: transform var(--transition-fast);
}
.dna-explore[open] summary::after {
  transform: rotate(45deg);
}
.dna-explore-content {
  padding: 18px 4px 4px;
  display: grid;
  gap: 14px;
}
.dna-explore-item {
  position: relative;
  overflow: hidden;
  padding: 16px 20px;
  border-radius: var(--radius-sm);
  border-left: 5px solid var(--trait-color, var(--color-tt));
  background: var(--color-surface);
}
.dna-explore-item h4 {
  margin: 0 0 4px;
  color: var(--trait-color, var(--color-ink));
  display: flex;
  align-items: center;
  gap: 8px;
  position: relative;
}
/* v2.17.0: raised from 0.92rem, same reasoning as .why-result-panel-inner p
   above. */
.dna-explore-item p { margin: 0; font-size: 0.95rem; position: relative; }

/* The card's own low-opacity constellation (see constellation.js /
   renderConstellationSvg) - corner-anchored, masked so it fades to fully
   transparent before it ever reaches the text, and z-index:-1 so it can
   never sit visually in front of the trait name/description even though
   it's positioned absolute over the same box. */
.dna-explore-motif {
  position: absolute;
  inset: -10px -10px auto auto;
  width: 62%;
  height: 100%;
  pointer-events: none;
  z-index: -1;
  -webkit-mask-image: radial-gradient(circle at 100% 0%, black 0%, black 20%, transparent 68%);
  mask-image: radial-gradient(circle at 100% 0%, black 0%, black 20%, transparent 68%);
}
.dna-explore-motif svg { width: 100%; height: 100%; }

/* ---------------------------------------------------------------------- */
/* Shareable card + actions (v2.14.0: "designed feature", not utility panel) */
/* ---------------------------------------------------------------------- */
.share-card-feature {
  text-align: center;
  padding: var(--space-8) 0;
}
/* Large, prominent Story preview - the headline element of this section,
   per the brief's exact structure (preview -> Download -> Share -> helper
   text). Sized generously (up to 300px) rather than the old, smaller
   340px-Post/240px-Story treatment, while a max-width keeps it from ever
   overpowering the page on wide desktop viewports. */
.share-card-preview {
  border-radius: var(--radius-lg);
  overflow: hidden;
  box-shadow: var(--shadow-lift);
  border: 1px solid var(--color-border);
  margin: 0 auto;
}
.share-card-preview img { width: 100%; height: 100%; display: block; object-fit: cover; }
/* Story format is a 9:16 canvas (vs. Post's 4:5). v2.17.0: an explicit
   width + aspect-ratio (not just a max-width on the eventual <img>) means
   this box is already the right size and shape the instant it's inserted,
   while the "Preparing your card…" loading state is still showing - no
   layout jump once the real preview image loads in, and the box reads as
   "substantial but not enormous" (per the brief) at every viewport instead
   of shrink-wrapping to whatever the loading text's own width happens to
   be. min(100%, 320px) keeps it comfortably inside even a 320px-wide
   phone's content area with a hair of breathing room either side. */
.share-card-preview--story {
  width: min(100%, 320px);
  aspect-ratio: 9 / 16;
}
/* Post format (1080x1350 = 4:5, secondary/optional toggle - unchanged size
   intent from earlier versions) gets the same width+aspect-ratio treatment
   for the same reason - its own loading state would otherwise collapse to
   zero height before the real image arrives. */
.share-card-preview--post {
  width: min(100%, 360px);
  aspect-ratio: 4 / 5;
}
.share-card-loading {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-16);
  text-align: center;
  color: var(--color-ink-muted);
  font-size: 0.9rem;
  box-sizing: border-box;
}

/* Secondary, low-emphasis control for the optional Post format - a plain
   text-style toggle beneath the main preview rather than a second
   equally-weighted button row, so "Download Story Card" reads clearly as
   the one primary action per the brief. */
.format-switch {
  display: block;
  margin: var(--space-16) auto 0;
  background: none;
  border: none;
  font: inherit;
  font-size: 0.85rem;
  color: var(--color-ink-muted);
  cursor: pointer;
  padding: var(--space-8);
}
.format-switch-label {
  color: var(--color-tt);
  font-weight: 700;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.format-switch:hover .format-switch-label,
.format-switch:focus-visible .format-switch-label {
  color: color-mix(in srgb, var(--color-tt) 80%, black);
}

.share-card-note {
  text-align: center;
  font-size: 0.85rem;
  color: var(--color-ink-muted);
  margin: var(--space-16) 0 0;
}

.share-actions {
  margin-top: var(--space-24);
  justify-content: center;
}

.share-card-helper {
  text-align: center;
  font-size: 0.85rem;
  color: var(--color-ink-muted);
  margin: var(--space-12) 0 0;
}

/* ---------------------------------------------------------------------- */
/* Result confetti (v2.15.0) - purely decorative, never blocks anything    */
/* ----------------------------------------------------------------------
   Full-viewport overlay canvas created/removed at runtime by
   confetti.js - pointer-events:none for its entire lifetime so it can
   never intercept a tap on Download/Share/social buttons underneath it.
   z-index (40) sits above the page's own content (.screen-inner is 2)
   but deliberately below every persistent fixed UI element - the copy
   toast (50), the Event Mode countdown badge (60), and the accessibility
   widget (70) - so a same-moment badge/toast is never visually buried
   under confetti, and well below the range any future modal/dialog would
   need (nothing on the result page currently uses a modal - the admin
   dashboard's own modals in adminData.js live on a completely separate
   page/stylesheet). Never rendered at all under prefers-reduced-motion -
   see launchResultConfetti()'s own guard in confetti.js. */
.result-confetti-canvas {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 40;
}

.copy-toast {
  position: fixed;
  bottom: 28px;
  left: 50%;
  transform: translateX(-50%) translateY(20px);
  background: var(--color-ink);
  color: white;
  padding: 12px 22px;
  border-radius: 999px;
  font-weight: 600;
  font-size: 0.92rem;
  opacity: 0;
  pointer-events: none;
  transition: all var(--transition-med);
  z-index: 50;
}
.copy-toast.is-visible {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}

/* ---------------------------------------------------------------------- */
/* Accessibility/settings widget (see accessibilityPanel.js) - a single    */
/* floating control present on every screen, bottom-left so it never      */
/* collides with the bottom-centre copy-toast or the top-right event      */
/* countdown badge.                                                        */
/* ---------------------------------------------------------------------- */
.a11y-widget {
  position: fixed;
  left: 16px;
  bottom: 16px;
  /* Respects the safe area on notched phones without needing extra JS. */
  left: max(16px, env(safe-area-inset-left));
  bottom: max(16px, env(safe-area-inset-bottom));
  z-index: 70;
}
.a11y-toggle-btn {
  width: 46px;
  height: 46px;
  border-radius: 50%;
  border: 1px solid var(--color-border);
  background: var(--color-surface);
  color: var(--color-ink-muted);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  box-shadow: var(--shadow-soft);
  transition: color 150ms ease, box-shadow 150ms ease;
}
.a11y-toggle-btn:hover,
.a11y-toggle-btn[aria-expanded='true'] {
  color: var(--color-tt);
  box-shadow: var(--shadow-lift);
}
.a11y-panel {
  position: absolute;
  left: 0;
  bottom: 56px;
  width: 230px;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-lift);
  padding: 14px 16px 12px;
}
.a11y-panel-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-weight: 700;
  font-size: 0.92rem;
  margin-bottom: 10px;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--color-border);
}
.a11y-panel-close {
  background: none;
  border: none;
  color: var(--color-ink-muted);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  margin: -6px -6px -6px 0;
  border-radius: 50%;
}
.a11y-panel-close:hover { color: var(--color-tt); background: var(--color-bg); }
.a11y-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  font-size: 0.88rem;
  /* The whole row is the clickable <label>, so a comfortable >=44px tap
     target comes from this padding, not from the checkbox's own visual
     size below. */
  padding: 12px 0;
  min-height: 44px;
  cursor: pointer;
}
.a11y-row input[type='checkbox'] {
  width: 20px;
  height: 20px;
  accent-color: var(--color-tt);
  cursor: pointer;
  flex-shrink: 0;
}
.a11y-panel-hint {
  font-size: 0.74rem;
  color: var(--color-ink-muted);
  margin: 8px 0 0;
  padding-top: 8px;
  border-top: 1px solid var(--color-border);
}
@media (max-width: 480px) {
  .a11y-toggle-btn { width: 42px; height: 42px; }
  .a11y-panel { width: 200px; }
}

.event-countdown-badge {
  position: fixed;
  top: 16px;
  right: 16px;
  background: var(--color-ink);
  color: white;
  padding: 10px 18px;
  border-radius: 999px;
  font-weight: 700;
  font-size: 0.85rem;
  z-index: 60;
  box-shadow: var(--shadow-soft);
}
.event-countdown-badge[hidden] {
  display: none;
}

/* ---------------------------------------------------------------------- */
/* Social CTA                                                               */
/* ---------------------------------------------------------------------- */
.social-cta {
  text-align: center;
  margin-top: 48px;
  padding: 40px 24px;
  /* Charcoal rather than flat black - a "premium dark section" reads
     softer and less harsh than pure black at this size, with a very
     subtle red glow underneath it for depth (see --shadow-dark). */
  background: var(--sams-charcoal);
  color: var(--sams-white);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-dark);
  position: relative;
  overflow: hidden;
}
.social-cta h2 {
  color: var(--sams-white);
}
.social-cta p {
  color: rgba(255, 255, 255, 0.78);
}
.social-buttons {
  display: flex;
  gap: 14px;
  justify-content: center;
  flex-wrap: wrap;
  margin-top: 20px;
}
/* v2.17.0: min-height/font-size/touch-action brought in line with the main
   .btn system (52px / 1.05rem / manipulation) - Instagram and Linktree are
   two of the "major participant-facing actions" the refinement brief asks
   to review for consistency, and were previously a few px shorter with a
   smaller ambient font-size than every other button on the page even
   though they're pill-shaped like every .btn too. Colour treatment (white
   pill, red border) stays intentionally distinct - it's a deliberate,
   different visual moment for the closing CTA section, not an oversight. */
.social-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 14px 24px;
  min-height: 52px;
  border-radius: 999px;
  background: var(--sams-white);
  border: 2px solid var(--color-tt);
  color: var(--sams-black);
  text-decoration: none;
  font-size: 1.05rem;
  font-weight: 700;
  touch-action: manipulation;
  transition: transform var(--spring), background var(--transition-fast), border-color var(--transition-fast), color var(--transition-fast), box-shadow var(--transition-fast);
}
.social-btn:hover,
.social-btn:focus-visible {
  background: var(--color-tt);
  border-color: var(--color-tt);
  color: var(--sams-white);
  transform: translateY(-2px) scale(1.03);
  box-shadow: var(--shadow-soft);
}
.social-btn:active { transform: scale(0.97); }

/* v2.14.0: "Continue your SIT journey with us." restructure - QR first,
   then the session-complete actions (lighter/ghost treatment, since
   they're a kiosk convenience rather than the section's main message),
   then the heading + Instagram/Linktree buttons at full emphasis. */
.social-cta-thanks {
  margin: 0 0 var(--space-8);
  font-size: 0.92rem;
}
.completion-actions {
  margin-top: var(--space-8);
  margin-bottom: var(--space-24);
}
.social-btn--ghost {
  background: transparent;
  border-color: rgba(255, 255, 255, 0.35);
  color: rgba(255, 255, 255, 0.85);
  padding: 10px 18px;
  font-size: 0.85rem;
}
.social-btn--ghost:hover,
.social-btn--ghost:focus-visible {
  background: rgba(255, 255, 255, 0.1);
  border-color: rgba(255, 255, 255, 0.6);
  color: var(--sams-white);
}
.social-cta-qr {
  margin: var(--space-24) 0;
  padding-top: var(--space-24);
  border-top: 1px solid rgba(255, 255, 255, 0.14);
}
.social-cta h2 {
  margin-top: 0;
}

/* ---------------------------------------------------------------------- */
/* Footer                                                                   */
/* ---------------------------------------------------------------------- */
.app-footer {
  text-align: center;
  color: var(--color-ink-muted);
  font-size: 0.85rem;
  margin-top: 40px;
}
.app-footer a { text-decoration: underline; display: inline-flex; align-items: center; gap: 5px; }
.trait-preview-card h3 { display: flex; align-items: center; gap: 8px; }

/* ---------------------------------------------------------------------- */
/* Error state                                                              */
/* ---------------------------------------------------------------------- */
.error-banner {
  background: #fbe9e9;
  border: 1px solid #e3a8a8;
  color: var(--color-danger);
  padding: 16px 20px;
  border-radius: var(--radius-sm);
  margin-bottom: 20px;
}

/* ---------------------------------------------------------------------- */
/* Persistent SAms header (logo bar shown on every quiz-flow screen)      */
/* ---------------------------------------------------------------------- */
.sams-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 4px 2px 16px;
  margin-bottom: 18px;
  border-bottom: 2px solid var(--color-border);
}
.sams-header-title {
  font-weight: 700;
  font-size: 0.92rem;
  color: var(--color-ink-muted);
}
@media (max-width: 480px) {
  .sams-header {
    padding: 2px 0 12px;
    margin-bottom: 14px;
  }
  .sams-header-title {
    font-size: 0.82rem;
  }
}
.sams-header-mascot {
  display: flex;
  align-items: center;
  flex-shrink: 0;
  margin-left: auto;
}
/* Sizing + halo + narrow-mobile hide rule for .sams-header-mascot now live
   together further down, in the "Quiz question mascot" section, right
   next to the pixel-area comparison that explains the 52px figure. */

/* ---------------------------------------------------------------------- */
/* Trait decorative patterns (distinguish traits without colour alone)    */
/* ---------------------------------------------------------------------- */
.trait-pattern-tt {
  background-image: repeating-linear-gradient(45deg, rgba(214, 0, 0, 0.09) 0 2px, transparent 2px 14px);
}
.trait-pattern-lur {
  background-image: repeating-radial-gradient(circle at 30% 30%, rgba(138, 20, 32, 0.12) 0 3px, transparent 3px 18px);
}
.trait-pattern-ct {
  background-image: linear-gradient(135deg, rgba(17, 17, 17, 0.07) 25%, transparent 25%),
    linear-gradient(-135deg, rgba(214, 0, 0, 0.09) 25%, transparent 25%);
  background-size: 26px 26px;
}
.trait-pattern-gc {
  background-image: radial-gradient(circle, rgba(17, 17, 17, 0.09) 2px, transparent 2.5px);
  background-size: 18px 18px;
}

/* Note: "Explore My Full DNA" cards (.dna-explore-item) no longer use the
   .trait-pattern-* classes above at all - they now get their own
   constellation-based decoration instead (see .dna-explore-motif near the
   .dna-explore-item rules, and constellation.js). The .trait-pattern-*
   classes above remain in use only on the landing page's trait-preview
   cards, unchanged. */

/* ---------------------------------------------------------------------- */
/* Decorative shape kit                                                    */
/* ----------------------------------------------------------------------
   Purely ornamental circles/rings/lines/dots, red/black/white/grey only,
   used across every screen for "more fun shapes" per the brief. Every
   instance below is `position:absolute; pointer-events:none; z-index:-1`
   inside a `position:relative` content container, which is the standard,
   reliable way to keep decoration painted behind real text/buttons/inputs
   regardless of DOM order - never on top, never intercepting clicks/taps.
   Motion (rotate/float/drift) reuses the SAME elements already covered by
   the global prefers-reduced-motion override near the top of this file,
   so every animated shape here automatically freezes under reduced motion
   with no extra media query needed.
   ---------------------------------------------------------------------- */
.deco-shape {
  position: absolute;
  pointer-events: none;
  z-index: -1;
}
.deco-ring {
  border-radius: 50%;
  background: transparent;
}
.deco-dot {
  border-radius: 50%;
}
.deco-rotate-slow { animation: decoRotate 46s linear infinite; }
.deco-float-slow { animation: decoFloat 7s ease-in-out infinite; }
@keyframes decoRotate { to { transform: rotate(360deg); } }
@keyframes decoFloat { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-10px); } }
/* deco-rotate-slow--rev and deco-drift-slow (plus its decoDrift keyframe)
   removed in v2.19.0 - confirmed zero references anywhere in src/**/*.js or
   *.html via a cross-referencing sweep before deletion. */

/* Hero frame - a light "viewfinder" corner-bracket pair around the
   landing page's title/button block, per "strong frame around
   title/button". Kept to two opposite corners rather than a full box so
   it reads as a deliberate accent, not a literal border round the text. */
.hero-frame { position: relative; }
.hero-frame::before,
.hero-frame::after {
  content: '';
  position: absolute;
  width: 46px;
  height: 46px;
  border: 3px solid var(--sams-red);
  opacity: 0.4;
  pointer-events: none;
}
.hero-frame::before {
  top: -14px;
  left: -6px;
  border-right: none;
  border-bottom: none;
  border-top-left-radius: 14px;
}
.hero-frame::after {
  bottom: -14px;
  right: -6px;
  border-left: none;
  border-top: none;
  border-bottom-right-radius: 14px;
}
@media (max-width: 480px) {
  .hero-frame::before, .hero-frame::after { width: 30px; height: 30px; }
}

/* Nickname-entry card - previously had a red ring sitting just outside the
   card's edges; that read as an unwanted "red outline" around the whole
   panel, so it's been replaced with a plain elevated surface instead (a
   deeper shadow, no border at all). The screen still reads as branded
   through the red Continue button, the SAms logo, the mascot and the
   red focus ring on the actual input below - the card itself just needed
   to be clean. */
.card--with-mascot {
  box-shadow: var(--shadow-lift);
}

/* QR code - decorative framing rings sit outside the white QR box itself
   (never behind/over the code image), so the code's own quiet white space
   stays completely clean and scannable. */
.qr-badge-box::before,
.qr-badge-box::after {
  content: '';
  position: absolute;
  border-radius: 26px;
  pointer-events: none;
  z-index: -1;
}
.qr-badge-box::before {
  inset: -14px;
  border: 2px dashed var(--sams-red);
  opacity: 0.32;
}
.qr-badge-box::after {
  inset: -22px;
  border: 1px solid var(--sams-black);
  opacity: 0.12;
  border-radius: 34px;
}

/* Loading screen - rotating rings + drifting dots around the centred
   panel, in the open gradient area surrounding it (not inside the panel
   itself, so the loading message stays on a clean white surface). */
.loading-screen { position: relative; overflow: hidden; }
.loading-screen::before,
.loading-screen::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  border-radius: 50%;
  border: 2px solid rgba(255, 255, 255, 0.35);
  pointer-events: none;
  z-index: 0;
}
.loading-screen::before {
  width: 520px;
  height: 520px;
  margin: -260px 0 0 -260px;
  animation: decoRotate 50s linear infinite;
}
.loading-screen::after {
  width: 380px;
  height: 380px;
  margin: -190px 0 0 -190px;
  border-color: rgba(214, 0, 0, 0.28);
  animation: decoRotate 38s linear infinite reverse;
}
.loading-panel { position: relative; z-index: 1; }
@media (max-width: 480px) {
  .loading-screen::before { width: 320px; height: 320px; margin: -160px 0 0 -160px; }
  .loading-screen::after { width: 230px; height: 230px; margin: -115px 0 0 -115px; }
}

/* Quiz question screen - the logo area (top-left, inside .sams-header) is
   kept deliberately clean/undecorated (no dots directly behind it). The
   one remaining accent is a low-key diagonal red line cluster tucked in
   the screen's outer top-right corner, well clear of both the logo and
   the header mascot slot, so it reads as general page texture rather
   than something "coming out from behind" either branded element. */
.screen-inner--quiz-deco { position: relative; }
.screen-inner--quiz-deco::after {
  content: '';
  position: absolute;
  top: -6px;
  right: -6px;
  width: 90px;
  height: 60px;
  background-image: repeating-linear-gradient(115deg, rgba(214, 0, 0, 0.1) 0 2px, transparent 2px 16px);
  opacity: 0.55;
  pointer-events: none;
  z-index: -1;
}
@media (max-width: 480px) {
  .screen-inner--quiz-deco::after { display: none; }
}

/* Quiz question mascot - sized to visually MATCH the SAms logo next to it,
   not just share its CSS box height. The two source images have very
   different amounts of transparent padding (the logo PNG is ~92%/88%
   visible content within its canvas; the mascot PNG has none - it's a
   full-bleed illustration), so matching only the outer <img> height would
   still make the mascot look disproportionately large. Comparing actual
   rendered pixel area instead: the logo at its existing (unchanged)
   40px header height shows about 2,090px^2 of real artwork; a 52px-tall
   mascot (its real aspect ratio, ~0.72 width:height, applied via
   height:auto) shows about 1,950px^2 - a close visual match, not merely
   a matching bounding box. See src/components/quizQuestion.js. */
.sams-header-mascot img {
  height: 52px !important;
  width: auto !important;
}
@media (max-width: 400px) {
  /* At the narrowest phone widths, a small static logo + progress label
     already fill the header row comfortably - hide the extra mascot here
     rather than risk crowding, per the brief's "prevent horizontal
     overflow"/"keep it away from important text" mobile guidance. */
  .sams-header-mascot { display: none; }
}
/* A soft red halo behind the mascot instead of lines "coming out of it" -
   a plain, low-opacity ring centred on the mascot, well behind it
   (z-index:-1) and never overlapping its face (it's centred, not offset
   across it, so it can never look like it's cutting through the art). */
.sams-header-mascot {
  position: relative;
}
.quiz-mascot-halo {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 64px;
  height: 64px;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  background: radial-gradient(circle, rgba(214, 0, 0, 0.18) 0%, transparent 72%);
  pointer-events: none;
  z-index: -1;
}

/* Result page - a container for the per-trait static motif drawn behind
   the hero mascot/title (see resultPage.js renderResultTraitMotif()) -
   the live-app echo of the same shape language used on the downloadable
   card's drawHeroMotif(), so the two feel like one consistent system. */
.result-hero { position: relative; }
/* v2.16.0 mobile refinement: corner-anchored (was a full-width strip
   spanning the entire hero behind the trait name) and masked so it fades
   to nothing well before it reaches the text - paired with the much lower
   opacity set in renderTraitMotifSvg() (resultPage.js), this keeps it a
   faint textural hint in one corner rather than full-bleed decoration
   competing with the trait name for attention on a small screen. */
.result-trait-motif {
  position: absolute;
  inset: -10px -10px auto auto;
  width: 72%;
  height: 220px;
  overflow: hidden;
  pointer-events: none;
  z-index: -1;
  -webkit-mask-image: radial-gradient(circle at 100% 0%, black 0%, black 25%, transparent 72%);
  mask-image: radial-gradient(circle at 100% 0%, black 0%, black 25%, transparent 72%);
}
.result-trait-motif svg { width: 100%; height: 100%; }
@media (max-width: 480px) {
  .result-trait-motif { width: 60%; height: 180px; }
}

/* Trait breakdown bars - a subtle bracket accent framing the section,
   echoing the brief's "decorative framing around percentage bars". */
.dna-bars { position: relative; }
.dna-bars::before {
  content: '';
  position: absolute;
  top: -10px;
  bottom: -10px;
  left: -18px;
  width: 6px;
  border-radius: 999px;
  background: linear-gradient(to bottom, var(--color-tt), var(--color-lur), var(--color-ct), var(--color-gc));
  opacity: 0.5;
  pointer-events: none;
  z-index: -1;
}
@media (max-width: 480px) {
  .dna-bars::before { display: none; }
}

/* ---------------------------------------------------------------------- */
/* Mobile fine-tuning (390px target)                                        */
/* ---------------------------------------------------------------------- */
@media (max-width: 400px) {
  .screen { padding: 16px 10px 26px; }
  .card { padding: 18px; }
  .btn { padding: 14px 22px; font-size: 1rem; }
  .answer-card { padding: 16px 16px; font-size: 0.98rem; }
  .trait-preview-grid { gap: 12px; }
}

/* v2.14.0 polish pass - a few narrow-viewport fixes for elements added/
   changed in this round, checked against 390px/430px per the brief. */
@media (max-width: 480px) {
  /* The longest trait name ("Able to Learn, Unlearn and Relearn") plus the
     "Your strongest" pill can outgrow a 390px row next to the percentage
     number - let the row wrap onto two lines instead of squeezing, with
     the percentage staying right-aligned via margin-left:auto either way
     (see .dna-bar-pct below), rather than letting the two collide/overlap
     or forcing the track underneath to run past the panel's edge. */
  .dna-bar-row .dna-bar-label {
    flex-wrap: wrap;
    row-gap: 4px;
  }
  .dna-bar-pct {
    margin-left: auto;
  }
  .dna-bar-primary-tag {
    font-size: 0.62rem;
    padding: 2px 6px;
  }

  /* The Story-card preview and its Download/Share row already shrink with
     the viewport (max-width + 100%-width buttons via .btn-row's own
     flex-wrap), but give the buttons themselves room to stack cleanly
     instead of squeezing onto one line at 390px. */
  .share-actions.btn-row {
    flex-direction: column;
    align-items: stretch;
  }
  .share-actions.btn-row .btn {
    width: 100%;
    justify-content: center;
  }

  /* "Continue your SIT journey with us." section - reduce the dark panel's
     own side padding so its content gets the same usable width as every
     other card on a 390px phone instead of compounding with
     .screen-inner's own edge padding. */
  .social-cta {
    padding: 28px 16px;
  }
  .completion-actions {
    flex-direction: column;
    align-items: center;
  }
}

/* ---------------------------------------------------------------------- */
/* Result page - dedicated mobile-layout refinement (v2.16.0)              */
/* ----------------------------------------------------------------------
   Root cause of the "compressed" mobile result page: .screen (14px side
   padding under 480px) and .screen-inner (a flat 26px side padding on
   every viewport, with no mobile-specific reduction) both apply at once,
   then every card inside it (.result-block, .result-combo,
   .percent-explainer, .dna-explore-item, .why-result-toggle) adds its own
   16-22px of side padding on top of that again - on a 390px phone that
   nested padding was eating roughly a third of the usable width before any
   actual content. Scoped to .result-reveal (only ever rendered on this one
   page - see resultPage.js) so none of these changes touch the quiz,
   landing, or nickname screens, which are not part of this brief and
   already read fine at mobile widths. Spacing here intentionally follows
   an 8/12/16/20/24/32px scale (the existing --space-* custom properties)
   rather than one-off values. */
@media (max-width: 480px) {
  /* .screen-inner's own side padding (26px) roughly halved - combined with
     .screen's existing 14px, this still leaves a clear coloured "frame"
     around the white panel (per the original design intent) while giving
     the panel itself meaningfully more usable width to lay content out in. */
  .screen--wide .screen-inner {
    padding: var(--space-24) var(--space-16) var(--space-32);
  }

  /* Nested cards: less internal padding so text gets more of the panel's
     own width, without losing the visual "cardness" (border/radius/tint)
     that separates each section. (.percent-explainer's own padding rule
     was removed in v2.17.0 - the percentage explainer is now the same
     .why-result accordion as "Why this result?", covered by the
     .why-result-toggle/.why-result rules right below.) */
  .result-block { padding: var(--space-16) var(--space-16); }
  .result-combo { padding: var(--space-16) var(--space-16); margin: var(--space-8) 0 var(--space-20); }
  .dna-explore-item { padding: var(--space-12) var(--space-16); }
  .dna-explore summary { padding: var(--space-12) var(--space-16); }
  .why-result-toggle { padding: var(--space-12) var(--space-8); }
  .result-section-title { margin: var(--space-24) 0 var(--space-12); }

  /* Vertical rhythm: the v2.14.0 desktop spacing (24-32px between major
     sections) is generous by design on a tall panel, but compounds fast on
     a short mobile viewport where the participant has to scroll past all
     of it to reach Download/Share. Tightened, not removed - sections still
     read as clearly separate. */
  .dna-bars { gap: var(--space-16); margin: var(--space-16) 0; }
  .why-result { margin: var(--space-16) 0; }
  .dna-explore { margin-top: var(--space-16); }
  .dna-explore-content { gap: var(--space-12); }

  /* Overflow safety net: nothing on this page should ever be responsible
     for horizontal scroll on a real device. Long unbroken strings (a
     nickname with no spaces, a long combination title) wrap instead of
     forcing the panel wider than the viewport. */
  .result-reveal,
  .result-reveal * {
    max-width: 100%;
  }
  /* v2.17.0: .result-trait-name/.result-combo-title no longer force
     word-break here - they're known short phrases, not user input, and
     breaking mid-word read as unpolished (see their own base rules above
     for the replacement text-wrap:balance treatment, which applies at
     every width, not just mobile). Only .result-greeting/.result-combo-
     opening (which can contain a participant's own free-typed nickname -
     a genuinely unpredictable, possibly very long single "word") still
     need the defensive break-word behaviour. */
  .result-greeting,
  .result-combo-opening {
    overflow-wrap: break-word;
    word-break: break-word;
  }

  /* Body-text readability (v2.17.0): the v2.16.0 pass nudged several
     secondary text blocks down to 0.92rem on mobile to save vertical space
     - on review that read as slightly too small for comfortable phone
     reading. Reverted: the space savings now come from the wider content
     area and tighter card padding/margins above instead. No mobile-only
     font-size override is needed here any more - .result-combo-opening is
     already 1rem and .why-result-panel-inner p / .dna-explore-item p are
     already 0.95rem in their base (non-mobile-specific) rules above, so
     they're already correct at every width without a duplicate override. */
}
