/* ==========================================================================
   screencraft.css — shared design tokens + shared components
   --------------------------------------------------------------------------
   Loaded by every page BEFORE that page's own <style> block, so page CSS
   always wins. Tokens here are the ones verified byte-identical across
   pages; page-specific tokens (Leonidas gold/board, index status colours)
   deliberately stay in their own page.

   Phase 1 consolidation — 2026-07-30
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Shared design tokens
   -------------------------------------------------------------------------- */
:root {
  /* Colors */
  --color-bg: #0b1936;
  --color-bg-alt: #0e1e40;

  /* --color-primary is TIER 3 -- NON-TEXT ONLY (GL-003 section 2.2).
     Focus rings, borders, button fills, gradient stops, glows, dots. It clears
     the 3:1 non-text floor and it does NOT clear 4.5:1 as a glyph: measured
     2.35:1 from rendered pixels on the composited language-button hover
     interior (#2b3853), 3.28:1 on #0d1e44. NEVER paint text with it --
     use --color-primary-light below. */
  --color-primary: #2b60ff;
  --color-primary-rgb: 43, 96, 255;
  --color-primary-glow: rgba(43, 96, 255, 0.35);

  /* Tier 1/2 -- the only blue permitted to paint glyphs (GL-003 section 2.1).
     Ratified 2026-07-30 at #85a3ff; RE-RULED 2026-07-31 to #8fabff on a third
     worst-surface discovery (GL-003 section 2.1, Amendment 2). Same hue and
     saturation as --color-primary, lightness the only axis ever moved:
     hsl(225,100%,58.4%) -> hsl(225,100%,76%) -> hsl(225,100%,78%).

     THE BINDING SURFACE IS #2b3c67 (Y = 0.04725). It is generated by
     `a.btn.btn-primary` -- the hero CTA's own brand-blue fill -- sliding under
     `header.scrolled` with the .lang-btn hover wash on top. Named from
     document.elementsFromPoint at the sample coordinates (GL-003 section 2.2
     rule 8, axis 5), not inferred. #2b3853, which GL-003's original
     ratification called "the binding case", has never appeared in a rendered
     cell on either typeface.

     Measured from RENDERED PIXELS, two-pass composited background per GL-003
     section 2.2 rule 5, positive control that the fg colour is present in
     frame A. Felix, 2026-07-31, RE-MEASURED ON THE INTER BUILD -- the previous
     figures were sampled with Outfit setting the Latin, and the surface set
     moved with the face. Sweep, per rule 8: 9 widths x 13 scroll offsets x 2
     languages x rest/hover = 468 cells, then a 4px-step rescan of every band
     left within 10% of the floor = 118 more. 586/586 PASS, 0 FAIL, 0
     DISCARDED; n_fg 608-619 per cell. Format is fg / bg / ratio / where the bg
     came from:
       #8fabff on #34405a  4.66:1  lightest bg pixel occupying >=1% of the
                                   glyph box, EN @1024px, scroll y304,
                                   HOVERED -- h1.hero-title's own glyphs
                                   under the header. THE WORST CELL ON THE
                                   SITE.                                 PASS
       #8fabff on #2b3c67  4.85:1  modal composite, EN @320px, y668-712,
                                   HOVERED -- the hero CTA under the header.
                                   The cell that failed at 4.46:1 before the
                                   re-ruling.                            PASS
       #8fabff on #303c57  4.94:1  modal composite, EN @1024px, y~304      PASS
       #8fabff on #2b3853  5.26:1  GL-003's stated "binding case" -- still
                                   never rendered                        PASS
       #8fabff on #0d1e44  7.34:1  named base surface                    PASS

     THE WARRANT BOUND, which is the number to check against, not the table
     above (GL-003 section 2.2 rule 9). #8fabff clears 4.5:1 against ANY
     composited surface with Y <= 0.0548. That is arithmetic on this hex alone
     -- (Y_fg + 0.05)/4.5 - 0.05 -- so it does not rot, and any newly sampled
     surface can be checked against it in one line with no table to consult.
     Today's worst is Y = 0.05135 (#34405a), sitting 6.8% under the bound.

     THE ESCALATION CLAUSE, and it is a commitment rather than a note. If a
     surface is ever found ABOVE Y = 0.0548, the answer is NOT a fourth
     lightness step. At that point the token is chasing a surface the product
     should not be generating, and the fix moves to the surface:
     `header.scrolled`'s rgba(11,25,54,0.8) alpha, its backdrop-filter, or the
     CTA's own fill. ONE MORE DISCOVERY AND THE HEADER MOVES INSTEAD OF THE
     BLUE.

     TWO THINGS FOR THE NEXT READER, because both would otherwise be
     rediscovered the hard way.
     (a) The worst surface moved when the TYPEFACE moved, and the generator
         changed class with it. On Outfit it was the CTA's solid fill at
         320px/y700. On Inter the worst cell is at 1024px/y304 and the thing
         under the header is the HERO TITLE'S OWN GLYPHS -- large white text
         showing through an 0.8-alpha navy header. A font swap is a surface
         change. Re-measure after one; do not carry the old numbers across.
     (b) 4.85 and 4.66 are the SAME cell set read two ways, not two builds.
         4.85 is the modal composite behind the glyph box; 4.66 is the
         lightest background occupying >=1% of it. Rule 8 mandates both and the
         verdict is taken on the worse. The pre-re-ruling number (4.46) was
         modal-only, so part of the apparent movement is a stricter instrument
         rather than a changed page. Compare modal to modal: 4.46 -> 4.85. */
  --color-primary-light: #8fabff;

  /* Tier 3 -- NON-TEXT ONLY. Gradient partner to --color-primary, bar fills,
     active-filter gradients, glow. Fails 4.5:1 on every elevated surface,
     including its own 15% tint badge (4.22:1). Promoted from index.html /
     thumbs.html page-local to shared, 2026-07-31. */
  --color-accent-pink: #d936ff;
  --color-accent-pink-rgb: 217, 54, 255;

  --color-text-main: #fafbfd;
  --color-text-muted: #8fa0ba;

  /* Glassmorphism */
  --glass-bg: rgba(255, 255, 255, 0.03);
  --glass-bg-hover: rgba(255, 255, 255, 0.06);
  --glass-border: rgba(255, 255, 255, 0.08);
  --glass-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37);

  /* Typography
     -----------------------------------------------------------------------
     `system-ui, -apple-system, sans-serif` in the terminal positions is a
     LAST RESORT, NOT A DECISION (GL-003 section 3 -> Fallback stacks, scope
     bullet (b)). It names no face: it is an instruction to the reader's OS to
     pick one for us, and it is here only because on a page served to strangers
     no named face is guaranteed to exist. Nobody chose it; do not read it as
     a choice, and do not add a named face to that tail without ratifying it.
     The webfont-backed positions are the ones that must cover every script the
     page sets.

     ONE webfont, deliberately, since 2026-07-31. It replaced 'Outfit','Inter',
     where Outfit carried every Latin glyph and Inter every Greek one -- Outfit
     has no Greek at any weight, so the "fallback" was the shipping design on
     every bilingual line, on every machine. That is glyph-level fallback, and
     it is a different and worse defect than the load-level kind this tail is
     for (GL-003 section 3 -> Fallback stacks, rule 5).

     Greg reviewed Geom and eight other Greek-covering families rendered in the
     site's own copy and preferred Inter. Greek is therefore unchanged -- it was
     already Inter -- so the binding SC 1.4.10 case did not move. Latin did.

     THE WEIGHT RANGE IS LOAD-BEARING. The old link requested Inter at
     400;500;600 only, because Outfit supplied 700/800/900. Inter alone must be
     requested at 400..900 or every bold heading renders as synthetic bold.
     If you ever narrow this range, check what sets weight >=700 first. */
  --font-sans: 'Inter', system-ui, -apple-system, sans-serif;

  /* Type scale -- GL-003 section 3. `rem` on a `font-size: 100%` root, so the
     reader's own browser setting is respected (SC 1.4.4).
     --text-caption (12px) is the floor; nothing below it.
     Line-heights are the scale's paired values and are applied at the call
     site; they are recorded here rather than tokenised because GL-003 authors
     nine tokens, not eighteen.
     NOT A RETROFIT. New work builds against these; existing literals collapse
     on the boy-scout rule, one page at a time, each with its own gate. */
  --text-display: 3.5rem;   /* 56px, line-height 1.2  -- hero display title */
  --text-h1: 2.5rem;        /* 40px, line-height 1.2  -- page/section titles */
  --text-h2: 2rem;          /* 32px, line-height 1.2  -- section heads */
  --text-h3: 1.5rem;        /* 24px, line-height 1.3  -- subsection heads, card titles */
  --text-h4: 1.25rem;       /* 20px, line-height 1.35 -- small card titles, stat labels */
  --text-lead: 1.125rem;    /* 18px, line-height 1.6  -- hero/section subtitle */
  --text-body: 1rem;        /* 16px, line-height 1.6  -- paragraphs, fields, buttons */
  --text-sm: 0.875rem;      /* 14px, line-height 1.5  -- metadata, footer links, badges */
  --text-caption: 0.75rem;  /* 12px, line-height 1.4  -- fine print. THE FLOOR. */

  /* Spacing scale -- GL-003 section 4. Base unit 0.25rem = 4px at the default
     root. `rem`, never `px`, for the same reason the type scale is `rem`.
     A parallel legacy 0.2rem family (0.2/0.4/0.6/0.8/1.2/2.4rem) also runs
     through all four pages. It is NOT extended and NOT swept -- collapse it on
     the boy-scout rule, one page at a time. Do not search-and-replace.
     NOT A RETROFIT, same as the type scale above. */
  --space-2xs: 0.25rem;     /* 4px  -- hairline gaps, badge interior, icon nudges */
  --space-xs: 0.5rem;       /* 8px  -- icon-to-text, inline chip gaps, tight flex rows */
  --space-sm: 0.75rem;      /* 12px -- dense list gaps, small-button interior */
  --space-md: 1rem;         /* 16px -- heading-to-body, form-field gaps, list rhythm */
  --space-lg: 1.5rem;       /* 24px -- THE SITE'S DOMINANT VALUE. Card padding,
                                       container gutter, standard grid gap. */
  --space-xl: 2rem;         /* 32px -- grid gaps between cards, desktop card padding */
  --space-2xl: 3rem;        /* 48px -- interior padding of large panels */
  --space-3xl: 4rem;        /* 64px -- section-header bottom margin, 2-col layout gap */
  --space-4xl: 6rem;        /* 96px -- VERTICAL SECTION RHYTHM. `padding: 6rem 0`. */

  /* System transitions */
  --transition-smooth: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}

/* --------------------------------------------------------------------------
   2. Footer site-link row (shared component)
   --------------------------------------------------------------------------
   Markup contract:

     <nav class="footer-nav-row" aria-labelledby="footer-nav-label">
       <span id="footer-nav-label" class="footer-nav-label"> ... </span>
       <ul class="footer-nav">
         <li><a href="index.html">Home</a></li>
         <li aria-current="page">                          <- current page
           <span class="footer-nav-current">Thumbs</span>
         </li>
       </ul>
     </nav>

   Notes:
   - The current page's entry is not a link: present, marked, and not a
     pointless self-navigation for keyboard/AT users. aria-current sits on
     the <li>; .footer-nav-current on the inner <span> carries the styling.
   - The bilingual accessible name works because the inactive .lang-* span is
     display:none, which excludes it from name computation. Do NOT "tidy" this
     with aria-hidden — aria-hidden does not remove text from an
     aria-labelledby computation, and adding it concatenates both languages
     into a single name (tested by Felix, confirmed by Vera, 2026-07-30).
   - No text-transform anywhere in this component. Greek labels stay mixed
     case and therefore keep their τόνοι (GL-003 §3). If uppercase is ever
     wanted here, the Greek must also resolve under lang="el" — all four
     pages already set that on the toggle, so the mechanism is in place.
   - Wraps rather than scrolls, so it cannot overflow horizontally on mobile.
   -------------------------------------------------------------------------- */

.footer-nav-row {
  border-top: 1px solid var(--glass-border);
  margin-top: 1.75rem;
  padding-top: 1.25rem;
}

/* Accessible name for the nav, bilingual via the site-wide .lang-* spans.
   Clipped rather than display:none so it still contributes to the a11y tree. */
.footer-nav-label {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.footer-nav {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0.5rem 1.5rem;
  margin: 0;
  padding: 0;
  list-style: none;
  font-family: var(--font-sans);
  font-size: 0.85rem;
  line-height: 1.5;
}

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

.footer-nav a {
  display: inline-block;
  padding: 2px 0;
  color: var(--color-text-muted);
  text-decoration: none;
  transition: var(--transition-smooth);
}

.footer-nav a:hover,
.footer-nav a:focus-visible {
  color: var(--color-text-main);
}

.footer-nav a:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 3px;
  border-radius: 3px;
}

/* Current page — shown, weighted, not a link.
   aria-current lives on the <li> and this class carries only the appearance.
   That split is a maintainability preference, nothing more: the <li> is the more
   idiomatic ARIA host and it also picks up posInSet/setSize. It is NOT a fix for
   a real defect.
   CORRECTED 2026-07-30: this comment used to say a bare <span aria-current>
   "announced nothing", credited to Vera. Vera retracted that finding the same
   day. It was an instrument artefact — CDP's Accessibility domain in Chrome 150
   cannot report aria-current on ANY element, including a plain
   <a aria-current="page"> control, so a negative read there meant nothing.
   Chrome's own native tree (chrome://accessibility) shows ariaCurrentState=page
   exposed on the <span>, on the <li> and on the <a> alike. The attribute was
   exposed correctly all along. Do not re-derive a rationale from this split.

   The aria-hidden warning in section 2 above is a different matter and is
   genuine — that one was tested directly and still holds. */
.footer-nav-current {
  display: inline-block;
  padding: 2px 0;
  color: var(--color-text-main);
  font-weight: 600;
}

@media (prefers-reduced-motion: reduce) {
  .footer-nav a {
    transition: none;
  }
}

/* --------------------------------------------------------------------------
   3. Subpage header — Reflow hardening (WCAG 2.2 SC 1.4.10)
   --------------------------------------------------------------------------
   Applies to thumbs.html, leonidas.html and leonidas-privacy.html, which
   ship a byte-identical header: .header-inner is a space-between flex row
   holding .brand (logo image + product tag) and .header-right (back link +
   EN|GR switch). index.html has a different header and is untouched by this
   block.

   The defect (measured 2026-07-30, Vera's Phase 1 gate + Felix's own sweep):
   the row had no flex-wrap and nothing in it could shrink, so its min-content
   width was pinned regardless of viewport --

     thumbs   24 + 237.8 + 95.8  = 357.6px
     leonidas 24 + 246.5 + 127.4 = 397.9px EN / 428.2px GR
     privacy  24 + 246.5 + 153.4 = 423.9px EN / 426.2px GR

   Worst case: leonidas.html in Greek overflowed by 108px at a 320px viewport.

   CORRECTED 2026-07-30: this paragraph used to say "all three pages also set
   `body { overflow-x: hidden }`". They do not. The three pages that set it are
   **index.html, leonidas.html and leonidas-privacy.html**; **thumbs.html does
   not** -- its computed body overflow-x is `visible`, refuted by Vera with real
   compositor gestures (Input.synthesizeScrollGesture), not with
   window.scrollTo, which succeeds even against overflow: hidden and would have
   agreed with the wrong answer.

   The mechanism is still real where it applies: body's overflow-x propagates to
   the viewport, so on leonidas.html and leonidas-privacy.html the clipped
   header chrome was genuinely unreachable rather than merely off screen. On
   thumbs.html it was off screen but a user could scroll to it, so the severity
   there was lower. Greek is consistently the worse case (GL-003 section 3),
   which is why the numbers above are quoted per language.

   Two declarations, chosen over the alternatives:

   1. `flex-wrap: wrap` on the row rather than `min-width: 0` + flex-shrink.
      Shrinking cannot work: the flex items are a raster logo sized by height
      and a pair of 24px-minimum tap targets, neither of which has any give.
      min-width: 0 would only have let .brand shrink below its own content and
      pushed the same overflow one level down.
   2. A fluid cap on the logo so the wrap point stays as low as possible and
      the brand row still fits at 240px. 6.67vw reaches the original 32px at
      480px, so nothing changes at or above that width. The page-level
      `.brand img { height: 32px }` is deliberately left in place as the
      fallback if this stylesheet ever fails to load; the selector below is
      scoped one level deeper (.header-inner .brand img) so it wins on
      specificity rather than on load order.

   Net effect. Zero horizontal overflow on all three pages at every 5px step
   from 240px to 800px, plus 1024/1280/1366/1440/1920, both languages. Desktop
   geometry is unchanged: the only box that moves at 1280/1440 is .back-link,
   +0.96px tall, by the rule directly below.

   WRAP POINTS -- RE-MEASURED 2026-07-31 AFTER THE TYPEFACE CHANGE (Vera).
   Every figure above this line was taken with Outfit setting the Latin. The
   site now sets one webfont, Inter, at 400..900 (see the --font-sans comment in
   section 1), and the wrap points moved 8-14px because the min-content widths
   that pin them are text widths:

     thumbs.html               386px -> 397px
     leonidas.html    EN       473px -> 487px
     leonidas.html    GR       532px -> 540px

   Below its number the page's header is two rows; above it, one. The min-content
   decompositions at lines 314-316 are therefore STALE for Latin -- they are kept
   as the record of what the defect was, not as current measurements. Do not
   re-derive from them; re-measure.

   The rule this cost us, and it is the reusable half: A FONT SWAP IS A LAYOUT
   CHANGE AND A SURFACE CHANGE. Anything calibrated against measured text widths
   -- wrap points, min-content pins, clamp lower bounds -- has to be re-measured
   after one, and so does anything calibrated against composited colour, because
   different glyph shapes and antialiasing move the pixels behind the text too.
   The section-title clamp lower bound survived this swap with 30.16px of
   headroom; .service-card's headroom did not fare as well, 5.4px -> 2.08px.
   -------------------------------------------------------------------------- */

.header-inner {
  flex-wrap: wrap;
  row-gap: 0.5rem;
  column-gap: 0.75rem;
}

.header-inner .brand img {
  height: clamp(18px, 6.67vw, 32px);
}

/* SC 2.5.8 Target Size (Minimum), and it is here because of the wrap above.
   .back-link is a bare inline <a> at font-size 0.9rem / line-height 1.6, so
   its box has always been 23.04px tall -- 0.96px under the 24px floor -- at
   every width where its label fits on one line, desktop included. Before the
   wrap it happened to clear 24px on narrow viewports only because the label
   was being squeezed onto two or three lines, which is not a pass, it is a
   different bug wearing the same number. Now that the label sits on one line
   at every width, the real 23.04px shows everywhere, so it gets a real floor.
   inline-flex + align-items:center rather than inline-block so the extra
   0.96px is split above and below the text instead of added underneath.

   min-width is the same floor on the other axis, and it is only ever load
   bearing on thumbs.html below 561px, where the label is visually hidden and
   the visible content is the bare `←` glyph (~13px). Everywhere else the label
   is wider than 24px, so the declaration is a no-op: measured 72.6px (EN) /
   129.6px (GR) on thumbs at 561px and up, and wider still on the leonidas
   pair, which has no `.full` wrapper at all. justify-content centres the glyph
   inside that 24px box; with content wider than the floor it has no effect. */
.header-inner .back-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 24px;
  min-width: 24px;
}

/* --------------------------------------------------------------------------
   4. Unbreakable tokens — Reflow hardening (WCAG 2.2 SC 1.4.10)
   --------------------------------------------------------------------------
   `support@screencraft.gr` is a 22-character token with no soft wrap
   opportunity, and it is the last thing pinning layout below ~260px: it set
   the min-content width of the .policy grid track on leonidas-privacy.html
   (216.7px EN / 224.4px GR) and of the mailto .btn-gold on leonidas.html
   (238.6px EN / 245.8px GR).

   `anywhere`, not `break-word`, and the difference is load-bearing: per CSS
   Text, break opportunities introduced by `break-word` are explicitly NOT
   counted when computing min-content intrinsic size, so `break-word` would
   have left both of those numbers exactly where they were. Only `anywhere`
   lowers the intrinsic floor, which is what a grid track and a flex item
   actually read.

   Scoped to mailto links rather than set on body: at body scope this would
   drop the min-content of every element on the site to roughly one character
   and change intrinsic sizing far beyond the defect. The address only ever
   breaks when it genuinely does not fit on a line.
   -------------------------------------------------------------------------- */

a[href^="mailto:"] {
  overflow-wrap: anywhere;
}

/* --------------------------------------------------------------------------
   5. Reduced motion — site-wide (WCAG 2.2 SC 2.3.3 Animation from
      Interactions, AAA-adjacent; also 2.2.2 Pause, Stop, Hide for the
      infinite decorative loops)
   --------------------------------------------------------------------------
   Shared rather than page-local because the preference is a property of the
   reader, not of a page. Measured before this block: index.html reported 20
   running animations under `prefers-reduced-motion: reduce`, and
   scroll-behavior stayed `smooth` on index / thumbs / leonidas.
   (20, not the 17 first recorded -- that was a Phase 2 undercount, re-measured
   twice by Vera on 2026-07-30. Verified after: 0 running on all four pages, and
   0 elements left invisible by a stopped animation.)

   `animation-duration: 0.01ms` + `animation-iteration-count: 1`, NOT
   `animation: none`. This is deliberate and it is the whole reason the block
   is shaped this way: killing an animation leaves the element at its BASE
   style, and index.html has keyframes that animate *out of* a hidden base
   (`fadeIn`: from opacity 0; `scaleUp`: from scale(0.95) + opacity 0). With
   `animation: none` those elements would stay invisible -- a reduced-motion
   preference would silently cost the reader content. Snapping the duration
   instead lands every animation on its 100% keyframe, i.e. the finished frame,
   which is the same principle thumbs.html's `.ce-scene` comment already
   states ("base styles are the FINAL state") reached from the other side.

   thumbs.html's own `.ce-scene *, .ce-scene { animation: none !important }`
   still wins there on specificity, which is correct -- that scene's base IS
   its final frame, so removing the animation is safe and cheaper.

   `!important` and the universal selector are unavoidable here: there are 40+
   animation declarations and a `--transition-smooth` token used at ~100 call
   sites, and enumerating them would rot on the next edit. The block is inert
   unless the reader has actually asked for reduced motion.
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    animation-delay: 0s !important;
    transition-duration: 0.01ms !important;
    transition-delay: 0s !important;
    scroll-behavior: auto !important;
  }
}
