/* ReVolt Research: the one stylesheet.
   Phase 1 lays down tokens and the base layer only. Components arrive in
   Phase 2. Every colour ratio quoted below is computed, not estimated, and is
   reproduced by `python3 scripts/check-contrast.py`, which gates the build. */

:root {
  /* ---------------------------------------------------------- SURFACES ---
     Light page, lighter band. surface-alt is #f6f6f6 and not a deeper grey
     for a measured reason: at #f2f2f2 the link token cleared 4.5:1 by 0.03,
     a pass with no margin that any later nudge would break silently. */
  --color-surface: #ffffff;
  --color-surface-alt: #f6f6f6;

  /* Emphasis bands ONLY, never the header or footer. The logo master has an
     opaque white background and no knockout variant exists, so the shell
     bands stay light and the dark surface is reserved for blocks that carry
     no logo. */
  --color-surface-dark: #141414;

  /* ------------------------------------------ FOREGROUND, LIGHT SURFACES ---
     Ratios against surface / surface-alt:
       text        17.40:1 / 16.10:1
       muted        7.00:1 /  6.48:1
       link         5.08:1 /  4.70:1
       link-hover   7.88:1 /  7.29:1 */
  --color-text: #1a1a1a;
  --color-muted: #595959;
  --color-link: #df0000;
  --color-link-hover: #a80000;

  /* --------------------------------------------- FOREGROUND, DARK SURFACE ---
     Each of these is for --color-surface-dark ONLY and FAILS on both light
     surfaces. Measured on light, so the failure is documented and not
     discovered later:
       text-on-dark   16.90:1 on dark, 1.09:1 and 1.01:1 on light
       muted-on-dark   9.29:1 on dark, 1.98:1 and 1.84:1 on light
       accent-on-dark  5.63:1 on dark, 3.27:1 and 3.03:1 on light */
  --color-text-on-dark: #f5f5f5;
  --color-muted-on-dark: #b8b8b8;
  --color-accent-on-dark: #ff4d4d;

  /* ------------------------------------------------------------- BRAND ---
     The exact logo red, sampled from images/ReVoltResearchLogo1-2016.png.
     4.00:1 on surface and 3.70:1 on surface-alt: valid for LARGE text (at or
     above 24px, or 18.66px bold) and for non-text UI, never for body text on
     a light surface. */
  --color-accent: #ff0000;

  /* The one interactive red, used for link text and for the filled control
     alike, so the palette carries two reds rather than three. Kept as its own
     token so the roles can diverge later without a hunt.
       label on cta        5.08:1
       cta on surface      5.08:1
       cta on surface-alt  4.70:1
       cta on surface-dark 3.63:1, which clears 1.4.11 non-text contrast, so
         the filled control needs no border workaround on the dark band. */
  --color-cta: #df0000;
  --color-cta-hover: #a80000;

  /* border-subtle measures 1.37:1 and is DECORATION. It must never be the
     only signal for anything. border-strong is 4.54:1 and delimits UI. */
  --color-border-subtle: #dcdcdc;
  --color-border-strong: #767676;

  /* Focus needs two tokens because no single value serves both grounds. */
  --color-focus: #111111;
  --color-focus-on-dark: #ffffff;

  /* Semantic states, deliberately not the brand red so a validation error
     never reads as a link. Meaning is never carried by colour alone: each
     state also ships an icon and a text prefix. */
  --color-success: #1e7b34;
  --color-warning: #8a5a00;
  --color-danger: #b3261e;

  /* -------------------------------------------------------- TYPOGRAPHY ---
     System stack, no webfont: zero bytes, zero requests, and no swap reflow.
     All sans. A serif heading face was considered and rejected: the register
     is corporate rather than editorial, and mixing families buys nothing when
     the logo already supplies the brand's letterforms. */
  --font-body:
    -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue",
    Arial, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  /* Body size is FLAT, not fluid. A fluid base is how a previous build in
     this workspace silently rendered 16.14px on mobile; headings scale, body
     does not, and measure is controlled by --width-prose instead. */
  --text-xs: 0.8125rem;
  --text-sm: 0.9375rem;
  --text-base: 1.0625rem;
  --text-lg: 1.25rem;
  --text-xl: clamp(1.5rem, 1.35rem + 0.65vw, 1.875rem);
  --text-2xl: clamp(1.875rem, 1.6rem + 1.2vw, 2.5rem);
  --text-3xl: clamp(2.25rem, 1.8rem + 2vw, 3.25rem);
  /* Stat tile values. Large enough that --color-accent is valid on them. */
  --text-stat: clamp(2.75rem, 2rem + 3.2vw, 4rem);

  --leading-tight: 1.15;
  --leading-snug: 1.35;
  --leading-body: 1.6;

  --weight-normal: 400;
  --weight-medium: 600;
  --weight-bold: 700;

  --tracking-tight: -0.02em;
  --tracking-wide: 0.08em;

  /* ------------------------------------------------------------ SPACE ---
     Generous by design: low visual density is the point of the register. */
  --space-3xs: 0.25rem;
  --space-2xs: 0.5rem;
  --space-xs: 0.75rem;
  --space-s: 1rem;
  --space-m: 1.5rem;
  --space-l: 2rem;
  --space-xl: 3rem;
  --space-2xl: 4rem;
  --space-3xl: 6rem;

  /* Restrained. The brief rejects excessively rounded cards. */
  --radius-sm: 2px;
  --radius-md: 4px;

  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.06);
  --shadow-md: 0 2px 8px rgba(0, 0, 0, 0.08);

  /* --width-prose is in ch so the measure tracks the rendered font. */
  --width-prose: 68ch;
  --width-content: 72rem;
  --width-wide: 90rem;
  --gutter: var(--space-s);

  --z-base: 0;
  --z-raised: 10;
  --z-sticky: 100;
  --z-overlay: 1000;

  --duration-fast: 150ms;
  --duration-base: 200ms;
  --ease-out: cubic-bezier(0.2, 0.8, 0.3, 1);

  /* WCAG 2.2 SC 2.5.8 floor is 24x24; the parent treats 44 as the standard. */
  --target-min: 44px;
  --border-control: 2px;
}

/* ------------------------------------------------------------- BASE ---- */

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  /* Not `scroll-behavior: smooth` unconditionally: it is motion, and it is
     disabled below under prefers-reduced-motion. */
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background-color: var(--color-surface);
  color: var(--color-text);
  font-family: var(--font-body);
  font-size: var(--text-base);
  line-height: var(--leading-body);
}

h1,
h2,
h3,
h4 {
  margin: 0 0 var(--space-s);
  line-height: var(--leading-tight);
  letter-spacing: var(--tracking-tight);
  font-weight: var(--weight-bold);
}

h1 { font-size: var(--text-3xl); }
h2 { font-size: var(--text-2xl); }
h3 { font-size: var(--text-xl); }
h4 { font-size: var(--text-lg); }

p,
ul,
ol,
dl {
  margin: 0 0 var(--space-m);
}

/* Frequently-asked-question pairs and other term/definition content.
   dt reuses the heading weight rather than a bare <strong>, since a
   question genuinely is a heading-level unit at this scale. */
dt {
  margin-block-start: var(--space-m);
  font-weight: var(--weight-bold);
}

dt:first-child {
  margin-block-start: 0;
}

dd {
  margin-inline-start: 0;
  margin-block-start: var(--space-2xs);
}

/* Underlined so link identity never rests on colour alone. */
a {
  color: var(--color-link);
  text-underline-offset: 0.15em;
}

a:hover {
  color: var(--color-link-hover);
}

/* One focus treatment reused site-wide. The offset creates a light gap so the
   ring stays visible even when the control's own fill is dark. */
:focus-visible {
  outline: 3px solid var(--color-focus);
  outline-offset: 2px;
}

/* height: auto is required alongside max-width: 100%. The width and height
   attributes every img carries for CLS defence would otherwise fix the
   rendered height while the width scales, distorting the image. */
img {
  max-width: 100%;
  height: auto;
}

/* Never `opacity` to de-emphasise text: it composites against whatever is
   behind it and silently breaks the measured contrast above. Use
   --color-muted. */

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ------------------------------------------------------------ LAYOUT --- */

.container {
  width: 100%;
  max-width: var(--width-content);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

main {
  display: block;
}

/* Generous vertical rhythm between the nav band and page content is the
   point of the investor-deck register (DECISIONS.md D24): content must
   never butt directly against the dark nav band below it. Scoped to
   main.container specifically so it does not also add block padding to the
   header and footer inner wrappers, which already set their own. */
main.container {
  padding-block: var(--space-2xl) var(--space-3xl);
}

/* ---------------------------------------------------------- SKIP LINK --- */

.skip-link {
  position: absolute;
  top: 0;
  left: 0;
  z-index: var(--z-overlay);
  display: flex;
  align-items: center;
  min-height: var(--target-min);
  padding-inline: var(--space-s);
  background-color: var(--color-surface);
  color: var(--color-link);
  transform: translateY(-100%);
  transition: transform var(--duration-fast) var(--ease-out);
}

.skip-link:focus {
  transform: translateY(0);
}

/* -------------------------------------------------------- SITE HEADER --- */

/* Light, on --color-surface, never dark: the shipping logo master
   (ReVoltResearchLogo1-2016.png) is 99.5% opaque with a pure white ground
   and no knockout variant exists, so placing it on any darker surface shows
   a white rectangle (DECISIONS.md D27). The nav band below carries no logo
   and is free to be dark. */
.site-header {
  background-color: var(--color-surface);
}

.site-header__inner {
  display: flex;
  align-items: center;
  padding-block: var(--space-m);
}

/* Capped and responsive: at the 412px mobile gate width a fixed logo width
   would crowd the content column. The lockup's own aspect ratio (1062:368)
   is preserved by width/height attributes on the img.

   The DEFINITE width belongs on the anchor, not on the image. The anchor is
   a flex item, so it is content-sized: an image sized `width: 100%;
   max-width: 220px` inside it resolves its percentage against a parent whose
   width depends on the image, and the box is not reserved until the
   intrinsic 1062px is clamped to 220px. The header then collapses from the
   intrinsic height to 76px and everything below it jumps. That produced an
   intermittent 0.063 CLS on whichever page happened to lose the race, which
   is why it looked like a page-specific defect for two phases and was
   actually shell-wide. Giving the anchor a definite width makes the image's
   percentage resolvable on the first pass, so the aspect-ratio box is
   reserved before the bytes land. */
.site-header__logo {
  display: block;
  width: 220px;
  max-width: 100%;
}

.site-header__logo img {
  display: block;
  width: 100%;
  height: auto;
}

/* --------------------------------------------------------- SITE NAV ----
   Dark band. Six items wrap to a second row at narrow widths with no JS and
   therefore no way for the navigation to break: this is the preferred
   option, and the item count allows it. */

.site-nav {
  background-color: var(--color-surface-dark);
}

/* The dark band needs its own focus colour: the default --color-focus is
   near-black and would be invisible against --color-surface-dark. */
.site-nav :focus-visible,
.site-footer :focus-visible {
  outline-color: var(--color-focus-on-dark);
}

.site-nav__list {
  display: flex;
  flex-wrap: wrap;
  max-width: var(--width-content);
  margin-inline: auto;
  padding-inline: var(--gutter);
  list-style: none;
  margin-block: 0;
}

/* Layout only. Shared with .btn--cta on purpose, so the Contact button sizes
   and sits identically to a plain nav link; .btn already supplies its own
   min-height and padding-inline of the same value, so this is reinforcement,
   not a conflict. */
.site-nav__list a {
  display: flex;
  align-items: center;
  min-height: var(--target-min);
  padding-inline: var(--space-s);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  text-decoration: none;
}

/* Colour is split out from the rule above and scoped with :not(.btn). Found
   by checking COMPUTED styles rather than trusting the source: without this
   guard, `.site-nav__list a` (one class plus one element, specificity 0,1,1)
   outranks `.btn--cta` (one class, specificity 0,1,0) regardless of source
   order, and silently repaints the Contact button's label as
   --color-text-on-dark instead of the pure white .btn--cta actually
   declares. The two values are close enough (4.66:1 versus 5.08:1 on the
   button fill) that the bug was invisible on screen and only surfaced by
   reading getComputedStyle. This is the same class of bug the aria-current
   guard below exists to prevent, on a different property. */
.site-nav__list a:not(.btn) {
  color: var(--color-text-on-dark);
}

.site-nav__list a:not(.btn):hover {
  color: var(--color-accent-on-dark);
}

/* The :not(.btn) guard is required here for the same reason: without it,
   this rule outranks .btn on specificity and repaints the filled Contact
   button's label in the same token as its own fill, 1:1 contrast, invisible,
   while Lighthouse still scores the page 100. */
.site-nav a[aria-current]:not(.btn) {
  color: var(--color-accent-on-dark);
  font-weight: var(--weight-bold);
  text-decoration: underline;
}

/* ------------------------------------------------------------- BUTTON --- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--target-min);
  padding-inline: var(--space-m);
  border-radius: var(--radius-md);
  font-weight: var(--weight-medium);
  text-decoration: none;
  border: var(--border-control) solid transparent;
}

.btn--cta {
  background-color: var(--color-cta);
  color: var(--color-surface);
}

.btn--cta:hover {
  background-color: var(--color-cta-hover);
}

/* No on-dark border is added here, unlike the sibling ztech4 build, which
   needed one: there the fill measured 2.95:1 against its dark band and
   failed WCAG 1.4.11. Here .btn--cta on --color-surface-dark measures
   3.63:1 (scripts/check-contrast.py, "CTA fill against dark band"), which
   clears the 3:1 non-text minimum on its own. Adding an unneeded border
   would be a raw value with no measured justification. */

.btn--ghost {
  background-color: transparent;
  border-color: currentColor;
  color: var(--color-link);
}

.site-nav .btn--ghost,
.site-footer .btn--ghost {
  color: var(--color-text-on-dark);
}

.btn:disabled,
.btn[aria-disabled="true"] {
  opacity: 0.6;
  pointer-events: none;
}

/* -------------------------------------------------------- SITE FOOTER --- */

.site-footer {
  background-color: var(--color-surface-dark);
  color: var(--color-text-on-dark);
}

.site-footer__inner {
  padding-block: var(--space-xl);
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-l) var(--space-2xl);
  justify-content: space-between;
}

.site-footer a {
  color: var(--color-accent-on-dark);
}

.site-footer__nap {
  font-style: normal;
  font-size: var(--text-sm);
  line-height: var(--leading-snug);
}

.site-footer__nap p {
  margin: 0 0 var(--space-2xs);
}

/* Decorative marks beside real link text (the tel: and mailto: links
   already carry the meaning), so the icon is aria-hidden with no separate
   text equivalent required. */
.site-footer__icon {
  display: inline-block;
  width: 1em;
  height: 1em;
  margin-inline-end: var(--space-3xs);
  vertical-align: -0.15em;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.site-footer__credit {
  flex-basis: 100%;
  margin: 0;
  padding-block-start: var(--space-m);
  border-block-start: 1px solid rgba(255, 255, 255, 0.15);
  font-size: var(--text-xs);
  color: var(--color-muted-on-dark);
}

/* ----------------------------------------------------------- BREADCRUMB ---
   Pattern only in Phase 2; the first live instance ships in Phase 3 on the
   first non-home page. Defined now so every page copies an already-verified
   rule rather than each page inventing its own. */

.breadcrumb {
  padding-block: var(--space-s);
  font-size: var(--text-sm);
}

.breadcrumb__list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3xs);
  list-style: none;
  margin: 0;
  padding: 0;
}

.breadcrumb__list li:not(:last-child)::after {
  content: "/";
  margin-inline-start: var(--space-3xs);
  color: var(--color-muted);
}

.breadcrumb__list a {
  text-decoration: underline;
}

.breadcrumb__list [aria-current] {
  color: var(--color-muted);
  text-decoration: none;
}

/* ----------------------------------------------------------- STAT TILE ---
   The value is real text at display size, never an image of text.
   --color-accent is valid here because --text-stat is well above the WCAG
   large-text threshold (24px / 18.66px bold). */

.stat-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-l) var(--space-xl);
}

.stat {
  flex: 1 1 12rem;
  min-width: 0;
}

.stat__value {
  margin: 0;
  color: var(--color-accent);
  font-size: var(--text-stat);
  font-weight: var(--weight-bold);
  line-height: 1;
  letter-spacing: var(--tracking-tight);
}

.stat__label {
  margin: var(--space-2xs) 0 0;
  font-size: var(--text-base);
  font-weight: var(--weight-medium);
}

.stat__note {
  margin: var(--space-3xs) 0 0;
  color: var(--color-muted);
  font-size: var(--text-sm);
}

.stat--lead .stat__value {
  font-size: clamp(3.5rem, 2.4rem + 5vw, 6rem);
}

/* ------------------------------------------------------- COMPARE FIGURE ---
   The bar is decoration: every number it encodes is also present as text
   in the same figure (.compare__label), so the figure survives CSS failing
   to load and meaning is never carried by the shape alone. The bar
   container is aria-hidden in the markup. */

.compare {
  margin: 0;
}

.compare__row {
  display: flex;
  align-items: baseline;
  gap: var(--space-s);
  margin-block-end: var(--space-xs);
}

.compare__row:last-child {
  margin-block-end: 0;
}

.compare__label {
  flex: 0 0 auto;
  width: 11rem;
  font-size: var(--text-sm);
}

/* An SVG rather than a CSS-sized div, because the parent's section 2 bans
   inline style attributes with exactly one exception (JSON-LD), and there
   is no such exception for a per-row percentage. The svg's viewBox is
   0..100 wide, so a <rect width="66"> is simply the percentage as a plain
   SVG geometry attribute, the same kind of structural data as an <img>'s
   width and height, not inline styling. */
.compare__track {
  display: block;
  flex: 1 1 auto;
  min-width: 0;
  width: 100%;
  height: 1.75rem;
  border-radius: var(--radius-sm);
  overflow: hidden;
}

.compare__track-bg {
  fill: var(--color-surface-alt);
}

.compare__fill {
  fill: var(--color-accent);
}

@media (max-width: 30rem) {
  .compare__row {
    flex-wrap: wrap;
  }

  .compare__label {
    width: 100%;
  }
}

/* --------------------------------------------------- FIGURE AND CAPTION ---
   Every content image on this project is a standalone figure in document
   order, never a shared-height row or a mosaic: this build has no gallery.
   So the caption-width fix the parent documents for a shared-height row is
   not needed generically here; if /efficacy-data/'s photo grid ends up
   sharing a height, that page adds its own scoped rule rather than this one
   growing a special case nothing else uses. */

.figure {
  margin: 0;
}

.figcaption {
  margin-block-start: var(--space-2xs);
  color: var(--color-muted);
  font-size: var(--text-sm);
}

/* Standalone figures: width: 100%, scoped to this exact parent chain. A
   lazy image with width and height both auto has no intrinsic size, so an
   unscoped rule here would collapse the link's box to about 2px before the
   bytes land and fail the WCAG target-size check; every image already
   carries width/height HTML attributes, so this only fills the column. */
.figure > .media-link > img {
  width: 100%;
}

/* Wrapping a photo in .media-link inserts an <a> between .figure and the
   image, which breaks a bare .figure > img radius rule. Both variants are
   listed so neither renders sharp-edged. */
.figure > img,
.figure > .media-link > img {
  border-radius: var(--radius-md);
}

.media-link {
  display: block;
}

/* A diagram or chart figure that should not stretch to the full prose
   column width the way a photograph does: the source PNG is a flat
   copy-through with no width ladder, so this caps it at a size where its
   labels stay comfortably legible instead of blowing up to fill a wide
   container. */
.figure--diagram {
  max-width: 40rem;
}

/* ----------------------------------------------------- MEDIA LINK STATES ---
   Every content photo opens its largest existing derivative in a new tab
   (parent section 5). No distinct visual chrome beyond the shared focus
   ring and a hover state on the image itself, so a linked photo reads as
   interactive without competing with the photograph. */
.media-link:hover img {
  opacity: 0.92;
}

/* --------------------------------------------------------- CONTACT FORM --- */

.form-field label {
  display: block;
  margin-block-end: var(--space-3xs);
  font-weight: var(--weight-medium);
}

.form-field input,
.form-field textarea {
  width: 100%;
  min-height: var(--target-min);
  padding: var(--space-2xs) var(--space-s);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  background-color: var(--color-surface);
  color: var(--color-text);
  font: inherit;
}

.form-field textarea {
  min-height: 10rem;
  resize: vertical;
}

/* The shared focus-visible outline already signals focus; this only
   removes the doubled default ring some browsers add on top of it. */
.form-field input:focus-visible,
.form-field textarea:focus-visible {
  border-color: var(--color-text);
}

/* Exactly two standalone figures side by side, for the rare case of a
   deliberate pair (for example two comparable photos). Not a gallery or a
   mosaic: this project has neither. Each figure keeps its own caption
   directly beneath it, so nothing here needs the shared-height
   caption-width fix a true mosaic would. */
.photo-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-m) var(--space-l);
}

.photo-row > .figure {
  flex: 1 1 16rem;
  min-width: 0;
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}
