/* ============================================================
   WriteBoard landing page — layout modeled on bluereceipt.com
   Palette: white bg, #081131 text, #0083FF accent, #e1e6f5 lines
   ============================================================ */

:root {
  --navy: #081131;
  --blue: #0083ff;
  --line: #e1e6f5;
  --ph-bg: #e9edf5;
  --ph-border: #c6cfe2;
  --ph-text: #8a94ad;
  /* The only two non-brand hues on the site. They exist solely for the
     onboarding result dialog, which has to say "worked" or "did not work"
     in a language the blue accent cannot. */
  --ok: #12a150;
  --err: #e5484d;

  /* --- Brand accent families --------------------------------------------
     Sampled from the app icon (assets/logos/WriteBoard_Icon_v2.png): four hue
     families, one per facet-pair of the diamond. --blue above stays the single
     INTERACTIVE accent (links, buttons, focus, form controls); these are
     categorical BRAND colours and must never be used for an interactive
     affordance, or the page loses the one colour that means "you can click".

     TWO JOBS, TWO STEPS. The icon hues are light, so a hue cannot be both a
     fill and ink at the same value:

       *-FILL   the icon facet at full strength, behind a dark glyph. This is
                what makes the 44px tiles read as the app icon.
       *-TEXT   the same hue darkened only as far as legibility demands, for
                the heading highlight.

     The -TEXT steps are derived by holding the icon hue and saturation fixed
     and lowering lightness until the colour clears 3:1 on white - the WCAG bar
     for large text, which every .highlight is (h1 44-64px, h2 32-48px). That is
     the same level the site blue #0083FF has always sat at (3.69:1), so these
     are no bolder a claim than the existing accent.

     Targeting 4.5:1 instead - the NORMAL-text bar, which does not apply here -
     is what produced the earlier muddy set (#0F7A4F, #B31247, #8A6410): correct
     arithmetic against the wrong requirement, and far enough from the icon to
     stop looking like the brand. Do not re-darken these without re-reading this.

       mint   icon #2AB77E L44%  ->  #26A773 L40%  3.05:1
       amber  icon #EDB32A L55%  ->  #BE8A11 L41%  3.05:1
       rose   icon #E1185A        ->  #E1185A       4.69:1  (icon value, unchanged)

     Rose is the one hue dark enough to use exactly as the icon draws it. Amber
     is the one that cannot win: yellow carries high luminance, so ANY amber
     legible as text on white reads gold-brown. #BE8A11 is the brightest that
     clears 3:1 - there is nothing brighter available, only less legible. */
  --sky-500:    #33C6F1;  /* tile fill, ink-900 glyph 5.51 */

  --mint-100:   #C4FBE3;  /* eyebrow tint */
  --mint-300:   #64F4B6;  /* tile fill, ink-900 glyph 7.96 */
  --mint-800:   #0F7A4F;  /* eyebrow text on tint, 4.66 */
  --mint-text:  #26A773;  /* heading highlight, 3.05 */

  --pink-100:   #FFD1E0;
  --pink-500:   #E1195A;  /* tile fill - the one that takes a WHITE glyph, 4.68 */
  --pink-700:   #B31247;  /* eyebrow text on tint, 4.99 */
  --pink-text:  #E1185A;  /* heading highlight - the icon rose itself, 4.69 */

  --amber-100:  #FDEEC6;
  --amber-500:  #EDB32B;  /* tile fill, ink-900 glyph 5.82 */
  --amber-800:  #8A6410;  /* eyebrow text on tint, 4.66 */
  --amber-text: #BE8A11;  /* heading highlight, 3.05 */

  /* The near-black the bright fills carry, from the icon pushpin family. */
  --ink-900:    #3C3C3C;

  /* The blue tint used by the default .eyebrow pill. */
  --blue-100:   rgba(0, 131, 255, 0.08);
  --max-w: 1320px;

  /* Display face for headings. Luckiest Guy ships ONE weight (400) and no
     italic, so every rule that applies it must also set font-weight: 400 -
     otherwise the browser synthesises bold and smears the glyphs. */
  --font-heading: "Luckiest Guy", "Plus Jakarta Sans", cursive, sans-serif;
}

* { margin: 0; padding: 0; box-sizing: border-box; }

html { scroll-behavior: smooth; }

body {
  font-family: 'Plus Jakarta Sans', sans-serif;
  color: var(--navy);
  /* CSS has no opacity for a background image, so a white wash is layered
     over the tile. On a white page that looks the same as fading the image:
     the wash alpha is the image's transparency (0.6 = 60% transparent). */
  --bg-tile-transparency: 0.6;
  background-color: #fff;
  background-image:
    linear-gradient(rgba(255, 255, 255, var(--bg-tile-transparency)), rgba(255, 255, 255, var(--bg-tile-transparency))),
    url('../assets/background.png');
  background-repeat: repeat;
  background-position: top left;
  line-height: 1.5;
  overflow-x: hidden;
}

img { max-width: 100%; display: block; }
a { text-decoration: none; color: inherit; }
ul { list-style: none; }

.container {
  width: 100%;
  max-width: var(--max-w);
  margin: 0 auto;
  padding: 0 24px;
}

/* ---------- Type scale ---------- */

/* Headings carry the display face. font-weight is pinned to 400 because
   Luckiest Guy has no bold; font-synthesis: none stops the browser faking one
   if a weight reset is ever missed further down the cascade. The negative
   tracking the old sans-serif headings used is cancelled - a heavy display
   face collides with itself at tight letter-spacing.
   h5 is deliberately excluded: it is a 14px uppercase label, not a heading. */
h1, h2, h3, h4 {
  font-family: var(--font-heading);
  font-weight: 400;
  letter-spacing: normal;
  font-synthesis: none;
}

h1 {
  font-size: 54px;
  line-height: 1.1;
}

h2 {
  font-size: 42px;
  line-height: 1.15;
  max-width: 1040px;
}

h4 { font-size: 18px; }
h5 { font-size: 14px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: var(--ph-text); }

/* The payoff phrase at the end of a heading - coloured text, no background.
   Sky (the site blue) is the default; the three below carry meaning - mint for
   engagement and outcomes, pink for the problem and anything time-critical,
   amber for section banners. See the -TEXT note in :root for why these are the
   values they are, and why amber cannot be brighter. */
.highlight { color: var(--blue); }
.highlight--mint  { color: var(--mint-text); }
.highlight--pink  { color: var(--pink-text); }
.highlight--amber { color: var(--amber-text); }

.lead {
  font-size: 18px;
  color: #4a5470;
  max-width: 560px;
}

.lead-wide {
  font-size: 18px;
  color: #4a5470;
  max-width: 720px;
}

.lead.center { text-align: center; margin: 0 auto; }

.microcopy { font-size: 13px; color: var(--ph-text); max-width: 420px; }

.eyebrow {
  display: inline-block;
  padding: 8px 18px;
  border-radius: 999px;
  background: var(--blue-100);
  color: var(--blue);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.08em;
  margin-bottom: 20px;
}

/* Same four families, same tint+dark-step pairing. Mirrors the eyebrow--*
   modifiers already in css/tokens.css / css/components.css, so a pill looks
   identical whether it is rendered here or in the design system. */
.eyebrow--mint  { background: var(--mint-100);  color: var(--mint-800); }
.eyebrow--pink  { background: var(--pink-100);  color: var(--pink-700); }
.eyebrow--amber { background: var(--amber-100); color: var(--amber-800); }

/* ---------- Buttons ---------- */

.btn {
  display: inline-block;
  padding: 12px 28px;
  border-radius: 999px;
  font-weight: 700;
  font-size: 15px;
  transition: transform 0.15s ease, box-shadow 0.15s ease;
}

.btn-primary {
  background: var(--blue);
  color: #fff;
  box-shadow: 0 8px 24px rgba(0, 131, 255, 0.3);
}

.btn-primary:hover { transform: translateY(-2px); box-shadow: 0 12px 28px rgba(0, 131, 255, 0.38); }

.btn-lg { padding: 16px 36px; font-size: 16px; }

.btn-login { font-weight: 600; font-size: 15px; }
.btn-login:hover { color: var(--blue); }

/* ---------- Placeholder blocks ---------- */

.ph {
  background: var(--ph-bg);
  border: 2px dashed var(--ph-border);
  border-radius: 20px;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}

.ph > span {
  color: var(--ph-text);
  font-size: 13px;
  font-weight: 600;
  text-align: center;
  padding: 4px;
}

/* ---------- Nav ---------- */

.navbar {
  position: sticky;
  top: 0;
  z-index: 100;
  background: rgba(255, 255, 255, 0.82);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  border-bottom: 1px solid var(--line);
}

.nav-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 76px;
  gap: 24px;
}

/* Brand lockup: app icon + wordmark. The icon is square and sits slightly
   taller than the wordmark (32 vs 24) so the two read as one mark rather
   than the icon looking undersized against the letterforms. align-items
   centre, never stretch - both images carry an explicit height. */
.brand, .footer-brand { display: flex; align-items: center; gap: 10px; }
/* .footer-brand is a grid item: without this it stretches to the row height
   and align-items:center then drops the lockup to the middle, out of line
   with the PRODUCT / SUPPORT column headings beside it. */
.footer-brand { align-self: start; }
.brand-icon { height: 32px; width: auto; }
.brand-logo { height: 24px; width: auto; }

.nav-menu {
  display: flex;
  align-items: center;
  gap: 32px;
}

.nav-link { font-size: 15px; font-weight: 600; }
.nav-link:hover { color: var(--blue); }

.nav-actions { display: flex; align-items: center; gap: 20px; margin-left: 16px; }

.nav-toggle { display: none; }
.nav-toggle-label { display: none; }

/* ---------- Hero (split) ---------- */

.hero {
  position: relative;
  /* Tuned so the trusted-by logo row clears a ~900px viewport:
     nav 77 + hero 640 + 155 to the logo bottom = 872px. Raising this
     pushes the social proof back below the fold. */
  min-height: 640px;
  overflow: hidden;
}

.hero-media {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  max-width: 1680px;
  margin: 0 auto;
  padding-top: 88px;
  display: flex;
  justify-content: flex-end;
  pointer-events: none;
}

/* Teachers fixed on the left, a rotating family on the right.
   704px wide keeps the left edge the old 780px placeholder had after its
   76px off-screen shift, so the headline/media overlap budget is unchanged
   without clipping the art at the viewport edge. */
.hero-illustration {
  width: 704px;
  height: 470px;
  display: flex;   /* no gap: each crop already carries a 12px margin */
}

/* The teachers take their natural width; the family slot gets the rest,
   370.6 x 470, a ratio of 0.789.

   The four family frames are all 576x1024 (ratio 0.5625) and that sameness
   is the whole mechanism: object-fit: contain fits the CANVAS into the slot,
   so identical canvases get one identical scale and the figures keep their
   relative heights. They are generated that way by
   tools/build-hero-carousel.js, which pre-scales each group so a standing
   adult is the same height in every frame.

   Two things break it. A frame cropped tight to its own figures renders its
   tallest ink at the full 470px whatever that ink is - a child, a stooped
   grandmother - which is the defect this replaced. And a frame wider than
   0.789 would be width-constrained by the slot and render shorter than the
   rest. 0.5625 leaves room for both. */
.hero-teachers {
  flex: none;
  height: 100%;
  width: auto;
  max-width: none;
}

.hero-families { position: relative; flex: 1; min-width: 0; }

/* Stacked one on top of another for the crossfade, and bottom-anchored so
   every figure stands on the same ground line. The inline script at the foot
   of index.html walks .is-active along them; it guards on a hard-coded count,
   so see the comment beside the <img> list before adding or removing one. */
.hero-families img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: bottom center;
  opacity: 0;
  transition: opacity 0.8s ease;
}

.hero-families img.is-active { opacity: 1; }

.hero-text {
  position: relative;
  z-index: 2;
  max-width: 600px;
  padding-top: 96px;
  padding-bottom: 56px;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 20px;
}

.hero-headline { position: relative; }

.hero-ctas {
  display: flex;
  align-items: center;
  gap: 28px;
  flex-wrap: wrap;
}

/* ---------- Trusted-by band (auto-scrolling logo strip) ---------- */

.trusted-band {
  position: relative;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  /* Padding is tuned to keep the logo row above the fold, NOT to match the
     old stats band. 1 + 44 + 20 + 26 + 64 = 155px from the top border down to
     the bottom of the logos; that figure is what the hero height budgets for. */
  padding: 44px 0 52px;
  margin-bottom: 104px;
  overflow: hidden;
}

.trusted-title {
  /* An h2 by tag, a small uppercase label by function - same case as h5 and
     .legal-toc h2. Opts back out of the display face and keeps its own bold. */
  font-family: 'Plus Jakarta Sans', sans-serif;
  font-size: 14px;
  line-height: 1.4;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  text-align: center;
  color: var(--ph-text);
  max-width: none;
  margin: 0 auto 26px;
}

/* Window: clips the rail and softens both edges so logos fade out rather
   than hard-clip. The -webkit- duplicate covers Safari < 15.4; a browser
   with neither simply clips at the overflow edge, which is fine. */
.trusted-marquee {
  overflow: hidden;
  -webkit-mask-image: linear-gradient(to right, transparent 0, #000 96px, #000 calc(100% - 96px), transparent 100%);
          mask-image: linear-gradient(to right, transparent 0, #000 96px, #000 calc(100% - 96px), transparent 100%);
}

/* The rail. width:max-content is REQUIRED — the % in the keyframe resolves
   against this element's own width, so without it the shift would be a
   fraction of the viewport instead of a fraction of the content.
   The keyframe divisor must always equal the number of .trusted-group kids. */
.trusted-track {
  display: flex;
  width: max-content;
  will-change: transform;
  animation: wb-logo-scroll 45s linear infinite;   /* group is 1104px — holds ~25px/s */
}

.trusted-marquee:hover .trusted-track,
.trusted-marquee:focus-within .trusted-track {
  animation-play-state: paused;
}

/* NEVER put `gap` on .trusted-track — n items have only n-1 gaps, so the
   groups would differ by one gap and the loop would jerk every cycle.
   padding-right supplies the seam gap and MUST always equal `gap`. */
.trusted-group {
  display: flex;
  align-items: center;
  flex: 0 0 auto;
  gap: 64px;
  padding-right: 64px;
}

.trusted-group > li { flex: 0 0 auto; }

/* Logo slot. Despite the ph- prefix these are REAL crests, not placeholders —
   the name is kept only because index.html carries it on 18 <img> tags.
   School crests are portrait or square, so the slot is tall enough (64px)
   for a crest to read; object-fit keeps wide wordmarks in the same 120px
   width. Files in assets/schools/ are transparent and cropped to the artwork. */
.ph-logo {
  width: 120px;
  height: 64px;
  border-radius: 10px;
  object-fit: contain;
  filter: grayscale(1);
  opacity: 0.72;
  transition: opacity 0.2s ease, filter 0.2s ease;
}

/* Full colour on hover, so a visitor can pick out a crest they recognise. */
.trusted-group > li:hover .ph-logo {
  filter: grayscale(0);
  opacity: 1;
}

@keyframes wb-logo-scroll {
  from { transform: translateX(0); }
  to   { transform: translateX(calc(-100% / 3)); }
}

/* ---------- Split media (video beside copy) ---------- */

/* The media half of a .split-grid row. Kept as its own element so the
   responsive block can flip it past the copy with `order: 2`, and so any
   overlay art dropped in later has a positioning context. */
.split-media { position: relative; }

.split-video {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  border-radius: 20px;
  background: var(--ph-bg);
}

/* ---------- Generic section rhythm ---------- */

.section { padding: 52px 0; }

.center-col {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 24px;
}

/* ---------- Feature overview (staggered grid) ---------- */

.feature-grid {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  grid-auto-rows: 120px;
  gap: 20px;
  width: 100%;
  margin-top: 24px;
}

.ph-shot { border-radius: 18px; }

/* Real dashboard captures filling the feature grid. Each tile is a different
   shape (1.58:1, 1.03:1 and 3.42:1 - see .ph-shot-a..g below) and every one of
   them collapses to the SAME portrait shape under 992px, so the shots are
   framed with their subject centred and cropped here rather than letterboxed.
   height:100% makes the image fill its grid area; without it the width/height
   attributes win and the tile grows to the file's own height. */
img.ph-shot {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* These captures are near-white UI on a near-white page and float without an
     edge. Same 1px var(--line) every card on the site uses. box-sizing is
     border-box globally, so the border eats 2px of the tile rather than
     growing it, and object-fit absorbs that. */
  border: 1px solid var(--line);
}

.ph-shot-a { grid-column: span 2; grid-row: span 2; }
.ph-shot-b { grid-column: span 2; grid-row: span 3; }
.ph-shot-c { grid-column: span 2; grid-row: span 2; }
.ph-shot-d { grid-column: span 2; grid-row: span 2; }
.ph-shot-e { grid-column: span 2; grid-row: span 1; }
.ph-shot-f { grid-column: span 2; grid-row: span 2; }
.ph-shot-g { grid-column: span 2; grid-row: span 1; }

/* ---------- Message types ---------- */

.message-type-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 20px;
  width: 100%;
  max-width: 900px;
  margin: 24px 0 32px;
}

/* One card, used by two grids: the message-type grid here and the value grid
   further down. They are the same component — icon tile beside a title and a
   line of copy — so they share a definition rather than drifting apart. */
.message-type-card,
.value-block {
  display: flex;
  align-items: flex-start;
  gap: 16px;
  text-align: left;
  padding: 22px;
  border: 1px solid var(--line);
  border-radius: 18px;
  background: #fff;
}

.message-type-card p,
.value-block p { font-size: 14px; color: #4a5470; margin-top: 4px; }

/* Tinted tile holding an inline SVG. The stroke lives here rather than on each
   <svg> so every icon in both grids stays in step. */
.card-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  min-width: 44px;
  border-radius: 12px;
  background: var(--sky-500);
  color: var(--ink-900);
}

/* Feature tiles are categorical, so they rotate through the families. Each is
   the icon facet at full strength carrying a near-black glyph - the pairing
   tokens.css prescribes, and the reason these read as the app icon rather than
   as washed-out tints. Pink is the exception: it is the one fill dark enough to
   need a white glyph (4.68 AA) - ink-900 on it would measure 2.3 and fail. */
.card-icon--mint  { background: var(--mint-300);  color: var(--ink-900); }
.card-icon--amber { background: var(--amber-500); color: var(--ink-900); }
.card-icon--pink  { background: var(--pink-500);  color: #fff; }

.card-icon svg {
  width: 22px;
  height: 22px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.75;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.ph-icon {
  width: 44px;
  height: 44px;
  min-width: 44px;
  border-radius: 12px;
}

.ph-icon-lg { width: 64px; height: 64px; border-radius: 16px; }

/* ---------- Benefits group intro ---------- */
/* The two .benefits-intro bands are headed by a full-body illustration
   (transparent PNG, no placeholder box) scaled down to icon weight. Sized by
   HEIGHT, not width, so both figures read at the same scale whatever their
   aspect - their widths then land near the 64px of the .ph-icon-lg box these
   replaced. Set height alone; width:auto keeps the figures from squashing. */
.benefits-icon {
  display: block;
  height: 104px;
  width: auto;
}

/* The group label under each illustration. It was a small .eyebrow pill, which
   read as a caption on the image rather than as the heading of the five
   sections below it. It is now a real h2 so the two bands sit at the same rank
   as the section headings they introduce, and it inherits the h2 type scale
   (42px, 32px under 991px) instead of restating it - change the scale in one
   place. Deliberately NOT .eyebrow: that pill is a shared component
   documented in styleguide.html and is still used there. */
.benefits-title {
  max-width: 20ch;
}

/* ---------- About Us (centered) ---------- */

.bullet-row {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 12px 28px;
  font-weight: 600;
  color: #4a5470;
}

.bullet-row li::before { content: '✓'; color: var(--blue); font-weight: 800; margin-right: 8px; }

/* The About Us clip. Wider than the .split-video cells (this one is centred in
   the column rather than sharing a grid row), but the same 16:9 and the same
   surface treatment, so the two read as one family. */
.about-video {
  display: block;
  width: 100%;
  max-width: 880px;
  aspect-ratio: 16 / 9;
  margin-top: 16px;
  object-fit: cover;
  border-radius: 20px;
  background: var(--ph-bg);
}

/* ---------- Spotlights B & C (split) ---------- */

.split-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: center;
  gap: 64px;
}

.split-text {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 20px;
}

.ph-split-img { width: 100%; aspect-ratio: 4 / 3; }

/* A finished illustration standing in for a split row's media. It keeps the
   4/3 footprint the .ph-split-img placeholder had, so swapping one in does not
   change the row's height or the alternating rhythm. object-fit:contain rather
   than cover because these are transparent figure PNGs, not screenshots -
   cropping them cuts off heads and feet, and the letterboxing is invisible.
   height:auto is LOAD-BEARING: the img carries width/height attributes (for
   the intrinsic ratio, so nothing shifts while it loads), and those map to a
   definite CSS height - with both dimensions definite, aspect-ratio is ignored
   and the row renders at the file's own height instead of 4/3.
   Always wrap it in .split-media, or a mirrored row stacks wrong on mobile. */
.split-illustration {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 3;
  object-fit: contain;
}

/* ---------- The Problem ---------- */

/* height:auto is LOAD-BEARING, for the reason spelled out at .split-illustration
   above: the img carries width/height attributes, which map to a definite CSS
   height and would otherwise render the file at its full 1019px. No
   aspect-ratio here — the file's own 2048/1019 is the ratio we want, and it
   is not a round number because the art is cropped (tools/crop-problem.js).
   Spacing comes from .center-col's gap, so this rule sets no margin. */
.problem-illustration {
  display: block;
  width: 100%;
  height: auto;
}

/* ---------- Testimonials ---------- */

/* The side padding is what lets the FIRST and LAST card reach the centre.
   Without it scroll-snap-align:center is unreachable at both ends and the
   carousel jerks back. The 150px is half of .t-card's 300px flex-basis —
   CHANGE BOTH TOGETHER or the ends stop centring.
   The 40px on the cross axis is also load-bearing: it absorbs the centred
   card's 1.05 scale, which overflow-x would otherwise clip. */
.testimonial-track {
  display: flex;
  gap: 24px;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  padding: 40px calc(50% - 150px);
  -ms-overflow-style: none;
  scrollbar-width: none;
}

.testimonial-track::-webkit-scrollbar { display: none; }

/* Cards sit REDUCED by default and the script promotes the centre one, so a
   visitor with JS off still sees a readable, evenly-sized row rather than
   every card stuck at 90%/55% with nothing ever emphasised. See .no-js below. */
.t-card {
  scroll-snap-align: center;
  flex: 0 0 300px;
  transform: scale(0.9);
  opacity: 0.55;
  transition: transform 0.35s ease, opacity 0.35s ease;
  padding: 28px;
  border: 1px solid var(--line);
  border-radius: 20px;
  background: #fff;
  box-shadow: 0 10px 30px rgba(8, 17, 49, 0.05);
}

.t-card.is-centred { transform: scale(1.05); opacity: 1; }

/* Until the script runs, nothing is centred. Rather than leave every card
   dimmed, the track starts un-dimmed and the script removes this class. */
.testimonial-track.is-static .t-card { transform: none; opacity: 1; }

.t-card p { font-size: 14px; color: #4a5470; margin-top: 12px; }

.ph-avatar { width: 56px; height: 56px; border-radius: 50%; margin-bottom: 14px; }

.t-name { font-weight: 700; }
.t-handle { font-size: 13px; color: var(--blue); }

/* Carousel controls. Arrows share .social-link's circle treatment (see the
   footer block) rather than redefining it; only the button reset is new. */
.testimonial-nav {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 20px;
}

.t-nav-arrow {
  background: none;
  font: inherit;
  cursor: pointer;
  padding: 0;
}

.t-dots { display: flex; align-items: center; gap: 8px; }

.t-dot {
  width: 8px;
  height: 8px;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: var(--line);
  cursor: pointer;
  transition: background 0.2s ease, width 0.2s ease;
}

.t-dot[aria-current='true'] { width: 22px; background: var(--blue); }

/* These are the only keyboard affordance for the carousel, so the focus ring
   is not optional. */
.t-nav-arrow:focus-visible,
.t-dot:focus-visible,
.testimonial-track:focus-visible {
  outline: 2px solid var(--blue);
  outline-offset: 3px;
}

/* ---------- Pilot-phase stand-in — TEMPORARY ----------
   One card, no nav, because no testimonials have been received yet. The
   carousel script skips this section entirely (no .testimonial-nav, so it
   returns at the !nav guard), which is why .is-static survives here and the
   lone card renders at full scale rather than the dimmed 0.9/0.55 that every
   uncentred card otherwise gets.
   DELETE this block together with the .testimonials--soon sections in
   index.html when the real quotes land — but NOT the .pilot-ribbon block
   below it, which the Trusted-by band uses too and outlives this one. */
.t-card--soon {
  position: relative;
  overflow: hidden;   /* clips the ribbon to the card's 20px corner radius */
  text-align: center;
}

/* .ph-avatar is left-aligned in a real testimonial card; this one is centred. */
.t-card--soon .ph-avatar { margin-left: auto; margin-right: auto; }

.t-soon-title { font-size: 20px; font-weight: 800; }

/* ---------- Pilot-phase corner ribbon — TEMPORARY, but SHARED ----------
   Worn by the two "Coming Soon" testimonial cards AND by the Trusted-by band.
   It therefore outlives the stand-in block above: deleting it when real
   testimonials land would strip the band's ribbon too. Both its hosts already
   supply the two things it needs — `position: relative` so it has a
   containing block, and `overflow: hidden` so it is clipped to the corner
   rather than hanging outside it.

   The offsets are geometry, not taste. For a corner bite of --ribbon-a, the
   strip's centre has to land on the midpoint of the corner chord, (W - a/2,
   a/2); rotating about that centre then meets the top and right edges
   symmetrically. Solving for the offsets cancels W out entirely, which is why
   one rule fits a 300px card and a full-width section alike:
       right = (a - width)  / 2
       top   = (a - height) / 2
   Those two are written as calc() rather than baked numbers ON PURPOSE —
   they used to be -50px/36px, and a hand-tuned pair silently slides off the
   corner the moment anyone changes the width or the padding. --ribbon-h must
   stay equal to line-height + 2 x padding, which is why line-height is pinned
   here instead of inherited. Resize by setting --ribbon-a / -w / -h only. */
.pilot-ribbon {
  --ribbon-a: 100px;                 /* corner bite along each edge */
  --ribbon-w: 200px;                 /* strip length, must exceed a x sqrt(2) */
  --ribbon-h: 28px;                  /* 14px line-height + 2 x 7px padding */

  position: absolute;
  top: calc((var(--ribbon-a) - var(--ribbon-h)) / 2);
  right: calc((var(--ribbon-a) - var(--ribbon-w)) / 2);
  width: var(--ribbon-w);
  padding: 7px 0;
  transform: rotate(45deg);
  background: var(--mint-100);
  color: var(--mint-800);
  font-size: 11px;
  font-weight: 800;
  line-height: 14px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  text-align: center;
  z-index: 1;   /* the marquee is a later sibling in the Trusted-by band */
}

/* Sizing note, learned the hard way. The run of text a ribbon can show is NOT
   the corner chord (a x sqrt(2)) — the clip cuts each end on the diagonal, so
   the usable run is `chord - --ribbon-h`. At the default 100/28 that is 113px
   against ~80px of "PILOT PHASE", which is comfortable. Shrinking the bite to
   56 to dodge the Trusted-by title left 57px of run for 73px of text and
   chopped the first and last letters. If you shrink a ribbon, check
   `a * 1.414 - h` against the rendered text width, not against the chord. */

/* ---------- Value props ---------- */

.value-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 24px;
}

/* .value-block and .card-icon are defined with .message-type-card, up in the
   Message types block — the two grids share one card. */

/* ---------- Big mockup ---------- */

.ph-big-mockup { width: 100%; max-width: 1064px; aspect-ratio: 16 / 9; }

/* ---------- Onboarding form ---------- */

/* Clears the 76px sticky navbar when an in-page link jumps here. Without it
   the nav sits on top of whatever the anchor scrolled to. */
#onboarding,
#about,
#for-schools,
#for-parents { scroll-margin-top: 90px; }

/* The roomier spacing the old .final-cta had, kept now that this is the
   closing section. Same specificity as .section (0,1,0), so it wins on
   SOURCE ORDER alone — move this rule above .section and the padding
   silently drops back to 52px. */
.onboarding { padding: 96px 0; }

.onboarding-card {
  width: 100%;
  max-width: 480px;
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 40px;
  text-align: left;
  background: #fff;
  border: 1px solid var(--line);
  border-radius: 20px;
  box-shadow: 0 10px 30px rgba(8, 17, 49, 0.05);
}

/* The form heading is an <h3> so it sits under the section's <h2>; styles.css
   has no base h3 rule, so it restates what h2 would have given it. */
.onboarding-card h3 {
  font-size: 32px;
  line-height: 1.15;
  text-align: center;
}

.onboarding-intro { font-size: 15px; color: #4a5470; margin-bottom: 8px; }

.field { position: relative; }

.field-icon {
  position: absolute;
  left: 14px;
  top: 50%;
  width: 18px;
  height: 18px;
  transform: translateY(-50%);
  fill: none;
  stroke: var(--ph-text);
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
  pointer-events: none;
}

.field input,
.field select,
.field textarea {
  width: 100%;
  padding: 12px 14px 12px 42px;
  font: inherit;
  font-size: 15px;
  color: var(--navy);
  background: #fff;
  border: 1px solid var(--line);
  border-radius: 12px;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

/* Added for support.html, the first and only page on the site with a textarea.
   Without it the box renders in the browser default font with square corners
   beside three styled inputs, because the rule above matched input and select
   alone.

   THREE DEPARTURES from the shared rule, each forced:
     - display:block kills the baseline gap a textarea's inline-block default
       leaves under it, which otherwise reads as a broken 4px margin.
     - padding-left returns to 14px. The 42px above is room for .field-icon,
       and a textarea has none: the icon is absolutely positioned at top:50%,
       which would put it halfway down a multi-line box.
     - resize:vertical. Horizontal resize escapes the card at 480px. */
.field textarea {
  display: block;
  min-height: 140px;
  padding-left: 14px;
  resize: vertical;
  line-height: 1.55;
}

.field input::placeholder,
.field textarea::placeholder { color: var(--ph-text); }

/* A <select> shares the input box above; this is only what the control needs
   on top of it. appearance:none drops the platform widget so the chevron can
   match .field-icon's stroke, and the right padding stops a long option label
   running underneath it. background-IMAGE only: the shared rule already set
   background-color, and a `background:` shorthand here would wipe the chevron. */
.field select {
  appearance: none;
  -webkit-appearance: none;
  cursor: pointer;
  padding-right: 38px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%238a94ad' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 14px center;
  background-size: 14px;
}

/* Unanswered, it should read like the placeholders above it rather than like a
   chosen value. :has() is the only route to that without a `required`
   attribute; where it is unsupported the text is navy, which is cosmetic. */
.field select:has(option[value=""]:checked) { color: var(--ph-text); }

.field input:focus,
.field select:focus,
.field textarea:focus {
  outline: none;
  border-color: var(--blue);
  box-shadow: 0 0 0 3px rgba(0, 131, 255, 0.15);
}

.onboarding-role {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 12px 24px;
  border: 0;
  margin: 8px 0 0;
  padding: 0 4px;
}

/* A legend ignores flex layout unless it is floated. */
.onboarding-role legend {
  float: left;
  font-size: 14px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--ph-text);
}

.onboarding-role label {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 15px;
  font-weight: 600;
  color: #4a5470;
  cursor: pointer;
}

.onboarding-role input {
  width: 18px;
  height: 18px;
  accent-color: var(--blue);
}

.onboarding-submit {
  align-self: center;
  margin-top: 16px;
  border: 0;
  font-family: inherit;
  cursor: pointer;
  /* Holds the button's width while the label is swapped for the 16px
     spinner. Without it the button visibly collapses on submit. */
  min-width: 196px;
}

/* .btn-primary:hover still fires on a disabled button, so the lift and the
   deepened glow have to be cancelled here or the pending state looks live. */
.onboarding-submit:disabled,
.onboarding-submit:disabled:hover {
  opacity: 0.65;
  cursor: default;
  transform: none;
  box-shadow: 0 8px 24px rgba(0, 131, 255, 0.3);
}

/* Lifted from the Angular landing page so the pending state matches.
   Note there is NO `.loader[hidden] { display: none }` rule here on purpose:
   the site-wide `[hidden] { display: none !important }` further down this
   file (search for it) already outranks `display: inline-block`. */
.loader {
  width: 16px;
  height: 16px;
  border: 2px solid #fff;
  border-bottom-color: transparent;
  border-radius: 50%;
  display: inline-block;
  vertical-align: middle;
  box-sizing: border-box;
  animation: rotation 1s linear infinite;
}

@keyframes rotation {
  0%   { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}

/* Slowed rather than stopped: a frozen spinner reads as a hung request. */
@media (prefers-reduced-motion: reduce) {
  .loader { animation-duration: 3s; }
}

/* ---------- Onboarding result dialog ---------- */

/* Answers the onboarding form. Hand-rolled because this page loads no
   third-party script and this is not the place to start one. */
.wb-dialog {
  /* The `* { margin: 0; padding: 0 }` reset at the top of this file zeroes the
     UA stylesheet's `margin: auto` on a dialog -- and that margin is the only
     thing centring a modal. Drop this line and the dialog pins to the top-left
     corner. The border is restated for the same reason. */
  margin: auto;
  width: calc(100% - 48px);
  max-width: 420px;
  padding: 36px 28px 28px;
  text-align: center;
  color: var(--navy);
  background: #fff;
  border: 1px solid var(--line);
  border-radius: 20px;
  box-shadow: 0 20px 50px rgba(8, 17, 49, 0.18);
}

/* A no-op where <dialog> is supported (the UA already hides a closed one).
   It is what keeps the element from rendering inline on a browser without
   <dialog>, where the script falls back to setting the open attribute. */
.wb-dialog:not([open]) { display: none; }

.wb-dialog::backdrop { background: rgba(8, 17, 49, 0.45); }

.wb-dialog-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 56px;
  height: 56px;
  margin: 0 auto 18px;
  border-radius: 50%;
}

.wb-dialog-icon svg {
  width: 28px;
  height: 28px;
  fill: none;
  stroke-width: 2.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.wb-dialog[data-kind='success'] .wb-dialog-icon { background: rgba(18, 161, 80, 0.12); }
.wb-dialog[data-kind='success'] .wb-dialog-icon svg { stroke: var(--ok); }
.wb-dialog[data-kind='error'] .wb-dialog-icon { background: rgba(229, 72, 77, 0.12); }
.wb-dialog[data-kind='error'] .wb-dialog-icon svg { stroke: var(--err); }

.wb-dialog-title { font-size: 24px; line-height: 1.2; }
.wb-dialog-text { margin: 10px 0 24px; font-size: 15px; color: #4a5470; }

.wb-dialog-ok { border: 0; font-family: inherit; font-size: 15px; cursor: pointer; }

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

/* ---------- Get-the-app page (getApp.html) ---------- */

.getapp { padding: 72px 0 96px; }

.getapp-card {
  width: 100%;
  max-width: 560px;
  padding: 40px;
  text-align: center;
  background: #fff;
  border: 1px solid var(--line);
  border-radius: 20px;
  box-shadow: 0 10px 30px rgba(8, 17, 49, 0.05);
}

.getapp-title { font-size: 38px; margin-bottom: 16px; }

.getapp-pending {
  font-size: 15px;
  color: #4a5470;
  margin-top: 20px;
}

.getapp-pending a { color: var(--blue); font-weight: 600; }

.getapp-stores {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: 24px;
}

/* The store buttons ship with the `hidden` attribute and are revealed by the
   script once STORES holds a URL. Without !important they would show anyway:
   `hidden` is only display:none from the UA stylesheet, and the author-level
   .btn { display: inline-block } outranks it. */
[hidden] { display: none !important; }

.getapp-back { margin-top: 28px; font-size: 14px; }
.getapp-back a { color: var(--ph-text); font-weight: 600; }
.getapp-back a:hover { color: var(--blue); }

/* ---------- Fixed "get the app" badge ---------- */

/* The site's first position:fixed element. z-index sits below the sticky
   navbar's 100 on purpose — they never overlap (opposite corners), but if a
   very short window ever brings them together the nav must win. */
.app-qr {
  position: fixed;
  right: 24px;
  bottom: 24px;
  z-index: 90;
  width: 188px;
  padding: 16px;
  text-align: center;
  background: #fff;
  border: 1px solid var(--line);
  border-radius: 20px;
  box-shadow: 0 10px 30px rgba(8, 17, 49, 0.12);
}

.app-qr-title {
  font-size: 14px;
  font-weight: 700;
  line-height: 1.3;
  color: var(--navy);
  margin-bottom: 12px;
}

.app-qr-title strong { color: var(--blue); }

.app-qr-scan { display: block; }

/* The white background is not decoration — it guarantees the quiet zone reads
   as white even though body carries a repeating background tile. A scanner
   needs that contrast at the symbol's edge. */
.app-qr-scan img {
  display: block;
  width: 100%;
  height: auto;
  background: #fff;
  border-radius: 6px;
}

/* Shown only below 992px; see the responsive block. */
.app-qr-btn { display: none; }

/* A fixed element otherwise repeats on every printed page. */
@media print {
  .app-qr { display: none; }
}

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

.footer {
  border-top: 1px solid var(--line);
  padding: 64px 0 32px;
}

.footer-grid {
  display: grid;
  grid-template-columns: 2fr 1fr 1fr 1fr;
  gap: 40px;
  margin-bottom: 48px;
}

.footer-col { display: flex; flex-direction: column; gap: 12px; }
.footer-col a { font-size: 14px; color: #4a5470; }
.footer-col a:hover { color: var(--blue); }

.footer-bottom {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  flex-wrap: wrap;
  border-top: 1px solid var(--line);
  padding-top: 24px;
  font-size: 13px;
  color: var(--ph-text);
}

/* The middle column of .footer-bottom. The heart is an INLINE SVG, not the
   Material Symbols "favorite" ligature the school app login footer uses: this
   site loads Plus Jakarta Sans only, so that font is absent here and the glyph
   name would render as the literal word "favorite". Colour and size track
   wb-login__heart in writeboardschool login.component.scss, and every other
   icon in this footer is inline SVG too. Sized in em so it tracks the
   surrounding 13px footer text rather than pinning a second number. */
.footer-heart {
  width: 1.1em;
  height: 1.1em;
  fill: #ef4444;
  vertical-align: -0.2em;
}

.footer-social { display: flex; gap: 12px; }

.social-link,
.t-nav-arrow {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--line);
  color: var(--navy);
  transition: color 0.2s, border-color 0.2s;
}

/* Sized by HEIGHT ONLY, with the width left to follow. The five social marks are
   official Font Awesome brand glyphs, which share a 512-unit height across widths
   from 320 (facebook-f) to 576 (youtube). Pinning both axes letterboxes each one by
   a different amount and the marks come out visibly unequal — a 320-wide glyph in an
   18x18 box renders 11px wide against YouTube's 18px. 16px inside the 36px circle is
   a conventional ~45% fill, deliberately below the arrows' 18px because a filled mark
   reads heavier than the stroke outlines these replaced.

   Split from .t-nav-arrow on purpose: that chevron is a 1.75px stroke drawn square on
   a 24 box and does want both axes pinned. Do not merge these back together. */
.social-link svg { height: 16px; width: auto; }

.t-nav-arrow svg { width: 18px; height: 18px; }

.social-link:hover,
.social-link:focus-visible,
.t-nav-arrow:hover,
.t-nav-arrow:focus-visible { color: var(--blue); border-color: var(--blue); }

/* ============================================================
   Responsive
   ============================================================ */

@media (max-width: 991px) {

  h1 { font-size: 44px; }
  h2 { font-size: 32px; }

  /* Nav collapses to hamburger */
  .nav-toggle-label {
    display: flex;
    flex-direction: column;
    gap: 5px;
    cursor: pointer;
    padding: 8px;
  }

  .nav-toggle-label span {
    width: 24px;
    height: 2px;
    background: var(--blue);
    border-radius: 2px;
  }

  .nav-menu {
    display: none;
    position: absolute;
    top: 76px;
    left: 0;
    right: 0;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    background: #fff;
    border-bottom: 1px solid var(--line);
    padding: 12px 24px 24px;
  }

  .nav-toggle:checked ~ .nav-menu { display: flex; }

  .nav-link { padding: 12px 0; }

  .nav-actions {
    margin-left: 0;
    margin-top: 12px;
    justify-content: flex-start;
  }

  /* Hero: the art is NOT hidden here, and must not go back to being hidden -
     the rotating family crop beside the fixed teachers is what makes
     "... no matter the shape" land. Above 992px .hero-media is an absolute
     overlay sitting behind the copy; below it, it drops into flow as a block
     UNDER the copy, which is why it is the second child of .hero in the
     markup. (The "hide video" this replaced was written for a video that has
     not been in the hero for some time.) */
  .hero { min-height: auto; }

  .hero-media {
    position: static;
    padding: 0 24px 40px;     /* 24px matches .container; 40px clears the trusted band */
    justify-content: center;  /* was flex-end, which framed the desktop overlay */
  }

  /* The desktop 704:470 stage, made fluid. aspect-ratio rather than a fixed
     height does two jobs: it holds the two crops in scale with each other at
     every width, and it reserves the box before the images load so there is
     no shift. Before making this TALLER, redo the sum beside .hero-teachers:
     the teachers (0.709 w:h) and the family frames (0.5625 w:h, and all four
     are that exact ratio by construction) need 1.2715 x height of width, so
     the stage cannot exceed ~0.786 x its width or object-fit shrinks the
     family out of scale with the teachers. 470/704 = 0.668 leaves 15%
     headroom. */
  .hero-illustration {
    width: 100%;
    max-width: 560px;         /* 560x374 at 991px, handing over to 564x420 at 992px */
    height: auto;
    aspect-ratio: 704 / 470;
  }

  .hero-text {
    max-width: 100%;
    padding-top: 48px;
    padding-bottom: 40px;
    align-items: center;
    text-align: center;
  }

  .hero-headline h1 br { display: none; }
  .hero-ctas { justify-content: center; }

  /* The hero lead is DESKTOP ONLY. At 375px it is ~190px tall and, with the
     20px column gap, it pushed the CTA and the rotating family art a full
     screen down - the art was only reachable by scrolling, which defeats the
     point of showing it here at all. Headline, CTA and art are the three
     things this screen has to deliver; the lead repeats what the headline
     already says and the sections below say again at length.
     Scoped to .hero-text: .lead is used by most sections on the page and
     must keep showing everywhere else. */
  .hero-text .lead { display: none; }

  .trusted-band { margin-bottom: 48px; padding: 36px 0 40px; }
  .trusted-title { font-size: 12px; letter-spacing: 0.1em; margin-bottom: 22px; }
  .trusted-group { gap: 40px; padding-right: 40px; }   /* padding-right MUST equal gap */
  .ph-logo { width: 100px; height: 52px; }
  .trusted-track { animation-duration: 34s; }          /* group is 840px here — holds ~25px/s */
  .trusted-marquee {
    -webkit-mask-image: linear-gradient(to right, transparent 0, #000 40px, #000 calc(100% - 40px), transparent 100%);
            mask-image: linear-gradient(to right, transparent 0, #000 40px, #000 calc(100% - 40px), transparent 100%);
  }

  .feature-grid { grid-template-columns: repeat(2, 1fr); grid-auto-rows: 110px; }
  .ph-shot-a, .ph-shot-b, .ph-shot-c, .ph-shot-d,
  .ph-shot-e, .ph-shot-f, .ph-shot-g { grid-column: span 1; grid-row: span 2; }

  /* e and g hold WIDE strips - a KPI row and a bar chart. Collapsing them into
     the same portrait cell as the rest crops a 3.42:1 image to 0.77:1, which
     leaves a sliver of one card and no legible content. They keep a full-width,
     short cell instead, close to their native shape. Must stay AFTER the rule
     above: same specificity, so source order decides. */
  .ph-shot-e, .ph-shot-g { grid-column: span 2; grid-row: span 1; }

  .message-type-grid { grid-template-columns: 1fr; }

  .split-grid { grid-template-columns: 1fr; gap: 32px; }
  .split-mirror .ph-split-img,
  .split-mirror .split-media { order: 2; }
  .split-text { align-items: center; text-align: center; margin: 0 auto; }
  .benefits-icon { height: 84px; }

  .value-grid { grid-template-columns: 1fr; }

  /* A phone cannot scan its own screen, so the QR gives way to a button that
     routes through getApp.html to that phone's own store. */
  .app-qr {
    right: 16px;
    bottom: 16px;
    width: auto;
    padding: 10px 12px;
    border-radius: 999px;
    display: flex;
    align-items: center;
    gap: 10px;
  }

  .app-qr-title { font-size: 13px; margin-bottom: 0; }
  .app-qr-scan { display: none; }
  .app-qr-btn { display: inline-block; padding: 8px 18px; font-size: 14px; }

  .getapp { padding: 48px 0 64px; }
  .getapp-card { padding: 28px 20px; }
  .getapp-title { font-size: 28px; }
  .getapp-stores { flex-direction: column; }

  .onboarding-card { padding: 28px 20px; }
  .onboarding-card h3 { font-size: 26px; }

  .footer-grid { grid-template-columns: 1fr; gap: 32px; }
  .footer-bottom { flex-direction: column; align-items: flex-start; }
}

/* Third breakpoint in this file, and it earns its place: below ~600px the
   centred Trusted-by title is a single line nearly as wide as the viewport and
   runs straight under the corner ribbon. No fixed ribbon size fixes it — the
   two constraints (text must fit `a * 1.414 - h`, title must clear the corner)
   leave under 2px of margin at 375px. So the title yields instead: the extra
   right padding shifts it off the corner and makes it wrap one line earlier,
   which also drops the lower lines into the wider part of the corner.
   Remove this when .pilot-ribbon goes. */
@media (max-width: 599px) {
  .trusted-title { padding-right: 56px; }
}

@media (min-width: 992px) and (max-width: 1279px) {
  .hero-illustration { width: 564px; height: 420px; }
  .hero { min-height: 600px; }
  /* Pinned to the pre-1280 column on purpose: inheriting the wider 600px
     would worsen the existing text/video overlap at 992px. Do not go below
     510px either — the headline then wraps to a 5th line and the hero grows. */
  .hero-text { max-width: 510px; }
}


/* ============================================================
   Motion preferences
   ============================================================ */

@media (prefers-reduced-motion: reduce) {

  html { scroll-behavior: auto; }

  /* Stop the rail, drop the duplicate groups, let the real row wrap. */
  .trusted-track {
    animation: none;
    width: auto;
    justify-content: center;
  }

  .trusted-group[aria-hidden='true'],
  .trusted-repeat { display: none; }

  .trusted-group {
    flex-wrap: wrap;
    justify-content: center;
    gap: 28px 44px;
    padding-right: 0;
  }

  .trusted-marquee {
    -webkit-mask-image: none;
            mask-image: none;
  }

  /* The script doesn't rotate here either; this covers a mid-session switch. */
  .hero-families img { transition: none; }

  /* Emphasis is a state, not an animation, so it stays — only the tween goes.
     The carousel's auto-advance is never started at all; see the script. */
  .t-card { transition: none; }
}

/* ============================================================
   COOKIE CONSENT
   ============================================================
   Markup is built by js/consent.js, not written into any page, so there is
   one copy of it. Only the <head> bootstrap and the footer control are
   per-page -- see tools/check-consent-tags.js.

   Toggling is done with the `hidden` ATTRIBUTE, never a class: the site-wide
   `[hidden] { display: none !important }` at the top of the Footer section
   outranks any display rule written here, which is exactly what makes the
   banner safe to ship with `hidden` set in the first place. */

.wb-cc {
  /* Above the sticky navbar's 100. Unlike .app-qr, which deliberately sits
     BELOW the nav because they occupy opposite corners, this spans the full
     width and must win against everything. */
  position: fixed;
  z-index: 110;
  right: 0;
  bottom: 0;
  left: 0;
  background: #fff;
  border-top: 1px solid var(--line);
  box-shadow: 0 -10px 40px rgba(8, 17, 49, 0.14);
  /* A long preferences panel on a short window has to stay reachable. */
  max-height: 88vh;
  overflow-y: auto;
}

.wb-cc-inner {
  display: flex;
  align-items: center;
  gap: 28px;
  max-width: 1200px;
  margin: 0 auto;
  padding: 20px 24px;
}

.wb-cc-copy { flex: 1 1 auto; }

.wb-cc-title {
  font-size: 17px;
  line-height: 1.3;
  color: var(--navy);
  margin-bottom: 6px;
}

.wb-cc-text { font-size: 14px; line-height: 1.55; color: #4a5470; }
.wb-cc-text a { color: var(--blue); font-weight: 600; }

.wb-cc-actions {
  display: flex;
  flex: 0 0 auto;
  align-items: center;
  gap: 12px;
}

/* Accept and Reject are rendered IDENTICALLY on purpose -- same class, same
   padding, same weight, same fill. Making the reject path quieter than the
   accept path is the specific dark pattern regulators act on, and "as easy to
   refuse as to accept" is not satisfied by an outline button beside a filled
   one. If this ever needs restyling, restyle both or neither. */
.wb-cc-btn {
  border: 0;
  font-family: inherit;
  white-space: nowrap;
  cursor: pointer;
}

/* Third rank, and it may be: opening a preferences panel is not refusing. */
.wb-cc-manage {
  padding: 0;
  background: none;
  border: 0;
  font-family: inherit;
  font-size: 14px;
  font-weight: 600;
  color: #4a5470;
  text-decoration: underline;
  cursor: pointer;
}

.wb-cc-manage:hover { color: var(--blue); }

/* ---------- Preferences panel ---------- */

.wb-cc-prefs {
  max-width: 1200px;
  margin: 0 auto;
  padding: 4px 24px 24px;
  border-top: 1px solid var(--line);
}

.wb-cc-row {
  display: flex;
  align-items: flex-start;
  gap: 14px;
  padding: 16px 0;
  border-bottom: 1px solid var(--line);
}

.wb-cc-row:last-of-type { border-bottom: 0; }

.wb-cc-row input[type='checkbox'] {
  flex: 0 0 auto;
  width: 18px;
  height: 18px;
  margin-top: 2px;
  accent-color: var(--blue);
}

.wb-cc-row-title {
  display: block;
  font-size: 15px;
  font-weight: 700;
  color: var(--navy);
}

.wb-cc-row-text { font-size: 13.5px; line-height: 1.5; color: #4a5470; }

/* The necessary row's box is checked and disabled. Showing it greyed is the
   honest rendering: it is not a choice, and a missing box invites the question
   of whether something is being hidden. */
.wb-cc-row input:disabled { opacity: 0.55; }

.wb-cc-prefs-actions { padding-top: 18px; }

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

/* Ships with `hidden` and is revealed by js/consent.js. With scripting off
   nothing non-essential is ever loaded, so there is no choice to withdraw and
   an inert button would be the only lie on the page. */
.footer-col .wb-cc-open {
  padding: 0;
  background: none;
  border: 0;
  font-family: inherit;
  font-size: 14px;
  font-weight: 400;
  color: #4a5470;
  text-align: left;
  cursor: pointer;
}

.footer-col .wb-cc-open:hover { color: var(--blue); }

@media (max-width: 991px) {
  .wb-cc-inner {
    flex-direction: column;
    align-items: stretch;
    gap: 16px;
    padding: 18px 20px;
  }

  .wb-cc-actions { flex-wrap: wrap; }

  /* Equal width as well as equal weight, so neither reads as the default. */
  .wb-cc-btn { flex: 1 1 160px; text-align: center; }
  .wb-cc-manage { flex: 1 0 100%; text-align: center; margin-top: 4px; }
  .wb-cc-prefs { padding: 4px 20px 20px; }
}

/* The badge is bottom-right, the banner spans the bottom: they overlap, and
   the banner has to win. It is hidden rather than lifted because lifting it
   puts a floating card on top of a dialog the visitor has to answer. The
   banner is transient; the badge comes back the moment a choice is made. */
body.wb-cc-shown .app-qr { display: none; }

@media print {
  .wb-cc { display: none; }
}
