/*
  MeisterScan — the shared visual system. EVERY page in this project links this file and
  nothing else, so "the same visual" is a fact rather than an intention.

    <link rel="stylesheet" href="/web/theme.css">          (from a page under the server root)
    <link rel="stylesheet" href="../../../web/theme.css">  (from ml/eval/annotate/, which is
                                                            also shipped as a flat kit zip)

  Plain CSS, no build, no dependencies — the same rule the pages themselves follow. A page may
  add its own <style> for things only it has; it must not restate a colour, a radius or an
  easing that is defined here, because the moment two files both own the navy they diverge.

  ── THE PALETTE COMES FROM THE LOGO, NOT FROM TASTE ─────────────────────────────────────────
  web/logo-navy.png is navy ground, white monogram, white "Meister", red "Scan". That is the
  whole palette: navy, white, one red. Values are read from web/brand.json, which make-logo.py
  writes — do not retype them from memory, and do not sample them off a screenshot.

  ⛔ THE RED IS LIGHTENED FOR DARK BACKGROUNDS AND HAS TO BE. Brand red #94161f is 1.97:1 on
  our navy; WCAG wants 4.5:1 for body text and 3:1 for large text. --accent is #e23945, the
  same hue lightened until it measures 4.05:1, and that number is produced by make-logo.py
  rather than guessed. If the navy is ever corrected from a real brand guide, re-run the script
  and take the red it prints — the two values are a pair.

  ── WHY ERRORS ARE A SHAPE, NOT A HUE ───────────────────────────────────────────────────────
  CLAUDE.md records the hazard plainly: the brand red sits one shade from the error red, and an
  interaction colour that matches the failure colour is how a worker clicks the wrong thing.
  Accent red across the whole product was a deliberate choice (2026-09-09), so the separation
  had to move somewhere else — it moved into GEOMETRY:

      a thing you press   is FILLED with the accent gradient and carries a glow  → .btn
      a thing that failed is a TINTED PANEL with a 3px left rule and a ⚠ glyph   → .alert

  A filled red control and a ruled red panel are never confusable at a glance, whatever the
  hue. So: never style an .alert to look pressable, and never give a .btn a left rule.
*/

/* ────────────────────────────────────────────────────────────────────────── tokens */
:root {
  /* Identity. See web/brand.json — generated, not hand-picked. */
  --navy:        #141a2b;   /* the logo's own ground, so logo-reversed.png composites exactly */
  --red:         #94161f;   /* brand red, for LIGHT surfaces only — 8.77:1 on white */
  --accent:      #e23945;   /* the same hue lightened to 4.05:1 on --navy */
  --accent-hi:   #f2606c;   /* gradient partner, one step lighter again */

  /* Surfaces, darkest to lightest. Cards are lighter than the page, never darker: a dark card
     on a dark page reads as a hole, and this product shows photographs inside those cards. */
  --bg:          #141a2b;
  --bg-alt:      #171e31;   /* alternating section band, the quietest possible step */
  --surface:     #1b2338;
  --surface-hi:  #222c46;
  --line:        #2b3552;
  --line-hi:     #3b4870;

  --fg:          #f7f8fa;   /* 16.30:1 on --bg */
  --fg-muted:    #9aa4bd;   /*  6.94:1 on --bg,  6.26:1 on --surface */
  /* Was #6d7794, which measured 3.89:1 on --bg and 3.51:1 on --surface — under the 4.5:1 floor
     for the small text it is actually used on (.eyebrow, .tiny, the footer, a <summary>).
     Lightened to clear it on BOTH surfaces, which is the binding one. */
  --fg-faint:    #828cab;   /*  5.19:1 on --bg,  4.68:1 on --surface */

  /* Semantic. Deliberately NOT the brand red — a failure must never be mistakable for the
     logo, and .alert's geometry does the rest of that work. */
  --ok:          #34d399;
  --warn:        #fbbf24;
  --bad:         #ff6b6b;
  --info:        #7dd3fc;

  /*
    TWO red gradients, and the difference is not decorative.

    --grad-accent is LIGHT, and is for red ON navy: the hero headline, an icon, a meter. It is
    read against the dark page, so it has to be light to be seen.

    --grad-btn is DARK, and is for WHITE ON red: any filled control. The light one fails here —
    white on #e23945 measures 4.28:1 and on #f2606c only 3.40:1, against the 4.5:1 that a 15px
    semibold button label needs (WCAG's "large text" allowance starts at 18.66px bold, which no
    button in this product reaches). Using one gradient for both is the obvious simplification
    and it is wrong in the direction this project has already shipped once — see the note on
    the logo's red in CLAUDE.md. Both ends of --grad-btn are checked:
        white on #a71b27 = 7.42:1      white on #ca212f = 5.57:1
    Same hue as the brand red #94161f throughout, just lighter.
  */
  --grad-accent: linear-gradient(135deg, var(--accent) 0%, var(--accent-hi) 100%);
  --grad-btn:    linear-gradient(135deg, #a71b27 0%, #ca212f 100%);
  /* The hero wash: navy, through a deep maroon, into the brand red at the far corner. Held
     well below full saturation on purpose — red at cyan's volume is a fire alarm. */
  --grad-hero:   linear-gradient(135deg, #141a2b 0%, #2c1730 48%, #8e1f2c 100%);

  --shadow-card: 0 8px 32px rgba(6, 9, 17, .45);
  --shadow-lift: 0 16px 44px rgba(6, 9, 17, .55);
  --glow:        0 0 48px rgba(226, 57, 69, .28);

  --r:           12px;      /* cards, panels */
  --r-sm:        10px;      /* controls */
  --r-pill:      999px;

  --ease:        cubic-bezier(.4, 0, .2, 1);
  --t:           .3s var(--ease);
  --t-fast:      .15s var(--ease);

  --nav-h:       66px;
  --container:   1280px;

  color-scheme: dark;
}

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

/*
  ⛔ KEEP THIS, AND KEEP THE !important.
  `hidden` is only a User-Agent `display: none`, so ANY author rule that sets `display` beats
  it — and this stylesheet sets `display` on .row, .stack, .alert, .card__icon and more. Every
  page here toggles visibility with `el.hidden = true`, so without this reset an element that
  happens to carry one of those classes ignores being hidden. It is not theoretical: the first
  render of the new landing page showed the OAuth spinner and an empty error box on top of the
  marketing copy, because #bridge had `display: grid`.
*/
[hidden] { display: none !important; }

html { scroll-behavior: smooth; scroll-padding-top: calc(var(--nav-h) + 16px); }

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font: 16px/1.6 ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
        Roboto, "Helvetica Neue", sans-serif;
  -webkit-font-smoothing: antialiased;
  -webkit-tap-highlight-color: transparent;
  overflow-x: hidden;
}

h1, h2, h3, h4 { margin: 0; font-weight: 700; letter-spacing: -.025em; line-height: 1.1; }
h1 { font-size: clamp(2.5rem, 5.6vw, 4.5rem); }
h2 { font-size: clamp(1.9rem, 3.4vw, 2.75rem); }
h3 { font-size: 1.125rem; letter-spacing: -.01em; }
p  { margin: 0; }

a { color: var(--accent-hi); text-decoration: none; transition: color var(--t-fast); }
a:hover { color: var(--fg); }

code, kbd, samp {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: .875em;
  background: rgba(255, 255, 255, .06);
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: .1em .4em;
}

::selection { background: rgba(226, 57, 69, .35); }

:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: 2px solid var(--accent-hi);
  outline-offset: 2px;
  border-radius: 4px;
}

/* ────────────────────────────────────────────────────────────────────────── layout */
.container {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: 32px;
}
@media (max-width: 640px) { .container { padding-inline: 20px; } }

.section { padding: 96px 0; position: relative; }
.section--band { background: var(--bg-alt); }
@media (max-width: 860px) { .section { padding: 64px 0; } }

.stack   { display: flex; flex-direction: column; gap: 16px; }
.row     { display: flex; align-items: center; gap: 12px; }
.row--wrap { flex-wrap: wrap; }
.spacer  { flex: 1 1 auto; }

.grid { display: grid; gap: 20px; }
.grid--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.grid--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
@media (max-width: 980px) { .grid--3 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 680px) { .grid--2, .grid--3 { grid-template-columns: minmax(0, 1fr); } }

/* ────────────────────────────────────────────────────────────────────────── nav */
/*
  Fixed, translucent, blurred — the page scrolls beneath the wordmark rather than pushing it
  away. `position: fixed` means every page must also carry .page-top (below) or the hero hides
  under it; .hero does that itself.
*/
.nav {
  position: fixed; inset: 0 0 auto 0; z-index: 50;
  height: var(--nav-h);
  display: flex; align-items: center;
  background: rgba(20, 26, 43, .82);
  backdrop-filter: blur(10px) saturate(140%);
  -webkit-backdrop-filter: blur(10px) saturate(140%);
  border-bottom: 1px solid transparent;
  transition: border-color var(--t), box-shadow var(--t), background var(--t);
}
/* Added by web/theme.js once the page has moved: the cue that there is content behind the
   bar. Absent at the top, where a border would cut the hero in half for no reason. */
.nav.is-scrolled {
  border-bottom-color: var(--line);
  box-shadow: 0 8px 28px rgba(6, 9, 17, .45);
}
.nav .container { display: flex; align-items: center; gap: 16px; }

/*
  LOGO, TOP LEFT. logo-reversed.png — white mark, white "Meister", red "Scan", on a transparent
  ground so the navy under it is OURS and stays exactly ours. logo-navy.png bakes its own navy
  in and would show here as a slightly-wrong rectangle the moment the two greys disagree.
  Height only; the width follows, so a re-locked-up logo cannot come out stretched.
*/
.brand { display: flex; align-items: center; flex: 0 0 auto; }
.brand img { height: 32px; width: auto; display: block; transition: opacity var(--t-fast); }
.brand:hover img { opacity: .82; }
@media (max-width: 480px) { .brand img { height: 26px; } }

.page-top { padding-top: var(--nav-h); }

/*
  THE LANGUAGE SWITCH. Built by web/i18n.js into any element carrying `data-lang-switch` — never
  hand-authored, so DE/EN/TR always render together and never go stale relative to each other.
  A single native <select>, not a custom-drawn dropdown: it gets keyboard support, a working
  native picker on a phone, and a closed-state width that is just the current flag and name —
  all for free, and it is also why this is a MORE specific selector than the bare `select` rule
  below rather than a class that fights it: only the properties that need to differ (width,
  size, colour) are redeclared here, and `appearance: none` / the focus ring / etc. are
  inherited from there unchanged.
*/
.lang-switch { display: flex; flex: 0 0 auto; }
.lang-select {
  width: auto;
  min-height: 32px; padding: 0 26px 0 10px;
  border-color: var(--line);
  background-color: transparent;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' stroke='%239aa4bd' stroke-width='1.6' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-position: right 10px center;
  color: var(--fg-muted);
  font: 600 .8125rem/1 inherit;
  cursor: pointer;
  transition: border-color var(--t-fast), color var(--t-fast), background-color var(--t-fast);
}
.lang-select:hover { color: var(--fg); border-color: var(--line-hi); background-color: rgba(247, 248, 250, .05); }
/* An <option> is rendered by the OS, not by this stylesheet — background/color here are a
   best-effort match on platforms that honour them (most desktop browsers), and a harmless
   no-op on ones that don't (iOS Safari draws its own picker sheet regardless). */
.lang-select option { background: var(--surface); color: var(--fg); }
/* Three controls (brand, switch, one button) at a phone width, and "Deutsch" — the longest of
   the three closed-state labels — is what actually has to fit; tighten rather than truncate,
   since cutting the text would leave an ambiguous flag-only control. */
@media (max-width: 480px) {
  .lang-select {
    padding: 0 20px 0 6px;
    background-position: right 6px center;
    font-size: .6875rem;
    min-height: 30px;
  }
}

/* ────────────────────────────────────────────────────────────────────────── buttons */
/*
  A FILLED, GLOWING control is the only thing in this system that means "press me". Kept
  visually far from .alert on purpose — see the header of this file.
*/
.btn {
  --btn-bg: var(--surface-hi);
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  min-height: 44px; padding: 0 24px;
  border: 1px solid transparent; border-radius: var(--r-sm);
  background: var(--btn-bg); color: var(--fg);
  font: 600 .9375rem/1 inherit; font-family: inherit;
  white-space: nowrap; cursor: pointer;
  transition: transform var(--t), box-shadow var(--t), background var(--t),
              border-color var(--t), opacity var(--t);
}
.btn:hover  { transform: translateY(-2px); }
.btn:active { transform: translateY(0); transition-duration: .06s; }

.btn--primary { background: var(--grad-btn); border-color: transparent; color: #fff; }
.btn--primary:hover { box-shadow: var(--glow); }

.btn--ghost {
  background: rgba(247, 248, 250, .05);
  border-color: rgba(247, 248, 250, .12);
}
.btn--ghost:hover { background: rgba(247, 248, 250, .1); border-color: var(--line-hi); }

/* Nav-scale: the bar is 66px and a 44px control inside it looks like a landing on a staircase. */
.btn--sm { min-height: 38px; padding: 0 16px; font-size: .875rem; }

.btn--block { display: flex; width: 100%; }

.btn:disabled, .btn[aria-disabled="true"] {
  background: var(--surface); color: var(--fg-faint);
  border-color: var(--line); box-shadow: none;
  cursor: not-allowed; transform: none; opacity: .75;
}

.btn__arrow { transition: transform var(--t); }
.btn:hover .btn__arrow { transform: translateX(4px); }

/* ────────────────────────────────────────────────────────────────────────── cards */
.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: 24px;
  box-shadow: var(--shadow-card);
  transition: transform var(--t), border-color var(--t), box-shadow var(--t);
}
.card--hover:hover {
  transform: translateY(-4px);
  border-color: rgba(226, 57, 69, .45);
  box-shadow: var(--shadow-lift), var(--glow);
}
.card__icon {
  width: 42px; height: 42px; flex: 0 0 auto;
  display: grid; place-items: center;
  border-radius: var(--r-sm);
  background: rgba(226, 57, 69, .14);
  border: 1px solid rgba(226, 57, 69, .3);
  color: var(--accent-hi);
}
.card__title { font-size: 1.0625rem; font-weight: 600; letter-spacing: -.01em; }
.card__sub   { font-size: .8125rem; color: var(--accent-hi); }
.card__body  { font-size: .9375rem; color: var(--fg-muted); }

/* ────────────────────────────────────────────────────────────────────────── bits */
.badge {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 6px 14px; border-radius: var(--r-pill);
  background: rgba(226, 57, 69, .12);
  border: 1px solid rgba(226, 57, 69, .28);
  color: var(--accent-hi);
  font-size: .8125rem; font-weight: 500; letter-spacing: .01em;
}
.badge__dot {
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--accent); animation: ms-pulse 2s var(--ease) infinite;
}

/* Gradient type. -webkit- prefixes stay: Safari still needs both. */
.grad-text {
  background: var(--grad-accent);
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent; color: transparent;
}

.eyebrow {
  font-size: .75rem; font-weight: 700; letter-spacing: .12em; text-transform: uppercase;
  color: var(--fg-faint);
}
.lede  { font-size: 1.125rem; color: var(--fg-muted); line-height: 1.65; }
.muted { color: var(--fg-muted); }
.small { font-size: .8125rem; }

.stat__n { font-size: 1.875rem; font-weight: 700; letter-spacing: -.02em; color: var(--accent-hi); }
.stat__l { font-size: .8125rem; color: var(--fg-muted); }

.rule { height: 1px; background: var(--line); border: 0; margin: 0; }

/* ────────────────────────────────────────────────────────────────── alerts (NOT buttons) */
/*
  Left rule + glyph + tint. Never filled, never a gradient, never a glow — those three belong
  to .btn and to nothing else. This is the whole safeguard that lets the product use the brand
  red as its accent without a failure looking like an invitation.
*/
.alert {
  display: flex; gap: 12px;
  padding: 14px 16px;
  border: 1px solid var(--line);
  border-left: 3px solid var(--fg-faint);
  border-radius: var(--r-sm);
  background: var(--surface);
  color: var(--fg);
  font-size: .875rem; line-height: 1.55;
  white-space: pre-wrap; word-break: break-word;
}
.alert::before { content: "•"; flex: 0 0 auto; line-height: 1.55; }

.alert--bad  { border-left-color: var(--bad);  background: rgba(255, 107, 107, .08); }
.alert--bad::before  { content: "⚠"; color: var(--bad); }
.alert--warn { border-left-color: var(--warn); background: rgba(251, 191, 36, .08); }
.alert--warn::before { content: "⏳"; }
.alert--info { border-left-color: var(--info); background: rgba(125, 211, 252, .07); }
.alert--info::before { content: "ℹ"; color: var(--info); }
.alert--ok   { border-left-color: var(--ok);   background: rgba(52, 211, 153, .08); }
.alert--ok::before   { content: "✓"; color: var(--ok); }

/* ────────────────────────────────────────────────────────────────────────── forms */
/*
  Bare element selectors, so a new page gets usable controls without inventing class names.
  That reach is deliberate but it is also the one part of this file that can surprise a page
  with its own dense chrome — ml/eval/annotate/ resets these explicitly for exactly that
  reason, and any future tool screen should do the same rather than weaken the default.
  (`.field` is NOT in this list on purpose: annotate/ already uses that class name for a
  layout wrapper, and claiming it here gave its header fields a 44px border box.)
*/
input[type="text"], input[type="email"], input[type="password"], select, textarea {
  width: 100%;
  min-height: 44px; padding: 10px 14px;
  background: var(--bg);
  color: var(--fg);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  font: inherit; font-size: .9375rem;
  transition: border-color var(--t-fast), box-shadow var(--t-fast);
}
textarea { min-height: 88px; resize: vertical; }
input:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(226, 57, 69, .18);
}
::placeholder { color: var(--fg-faint); }

select {
  appearance: none;
  /* Chevron drawn in the SVG rather than added as an element, so a <select> stays a <select>. */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' stroke='%239aa4bd' stroke-width='1.6' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 14px center;
  padding-right: 38px;
}

/* ────────────────────────────────────────────────────────────────────────── motion */
/*
  Reveal-on-scroll. The class is applied by web/theme.js via IntersectionObserver; if that
  script never runs, `.reveal` without `.is-in` would leave the whole page invisible — so the
  hidden state is set by JS too (html.js-reveal), never by CSS alone. A stylesheet must not be
  able to blank a page on its own.
*/
.js-reveal .reveal {
  opacity: 0;
  transform: translateY(24px);
  transition: opacity .6s var(--ease), transform .6s var(--ease);
  transition-delay: var(--d, 0ms);
  will-change: opacity, transform;
}
.js-reveal .reveal.is-in { opacity: 1; transform: none; }

@keyframes ms-pulse { 50% { opacity: .35; } }
@keyframes ms-float {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-10px); }
}
@keyframes ms-spin  { to { transform: rotate(360deg); } }
@keyframes ms-in    { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }

.animate-float { animation: ms-float 7s var(--ease) infinite; }
.animate-in    { animation: ms-in .35s var(--ease) both; }

.spinner {
  width: 16px; height: 16px; flex: 0 0 auto;
  border: 2px solid var(--line-hi); border-top-color: var(--accent);
  border-radius: 50%; animation: ms-spin .7s linear infinite;
}

/*
  Vestibular safety, and it is not optional. Everything above degrades to a plain cut.
*/
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
  .js-reveal .reveal { opacity: 1; transform: none; }
}
