/* =====================================================================
   REQUEST FORM - brand-side section only.
   Everything else on this page (header, hero, phone, generated sections,
   footer) belongs to the shared template and lives in app.css. This file
   owns exactly one thing: the "Tell us about your household" section
   (.sec-form) at the bottom of index.html, plus the no-JavaScript
   fallback page request.php renders when a visitor has scripting off
   (.plain-page, see the bottom of this file).

   Loaded after app.css. Every rule below is scoped under .sec-form or
   .plain-page so nothing here can reach into the template's own layout.
   Every color, radius and shadow reads a var() from brand.css: there is
   no :root block here and no literal hex value. Two named exceptions to
   the "do not touch the template" rule are marked below.

   Token gaps found while porting, noted once here rather than repeated at
   every rule that uses the substitute:
     - No --alert/error token ships on this page on purpose: brand.css
       reserves red for allergy safety warnings inside the product only.
       Form validation errors use --warn / --warn-bg instead, the
       nearest existing semantic pair.
     - No --control-border token exists for input borders. --text-muted
       is the nearest token that still clears 3:1 against --bg.
   ===================================================================== */

/* ── the two deliberate exceptions to "do not restyle the template" ──── */

/* a. NannyRoster's wordmark paints "Roster" in Berry, which is not the
   template's single CTA accent (Harbor). This preserves the brand mark
   without touching .brand-name itself, which app.css owns. */
.brand-name em { color: var(--berry); }

/* b. .sec-form .btn intentionally left to inherit .btn/.btn-primary from
   app.css untouched. The only addition is full width on narrow screens,
   see the responsive block near the bottom of this file. */

/* ── section band ──────────────────────────────────────────────────────
   The section directly above this one (config.sections[3], "pricing")
   is the fourth generated section, an odd index, so app.js gives it the
   "band" class and app.css paints it var(--bg-2). This section is not
   banded, so it falls back to the page's own var(--bg) and the seam
   between the two stays visible, matching every other alternation on
   the page. */
/* This section has to match .sect or the seam above it reads as a different
   rhythm from every other boundary on the page, so its padding is set with
   .sect's in the "8. vertical space" block at the end of this file rather
   than here. */
.sec-form { background: var(--bg); }

.close-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 40px;
  align-items: start;
}
@media (min-width: 900px), (min-width: 700px) and (max-width: 899px) and (min-height: 521px) {
  .close-grid { grid-template-columns: 1fr 1fr; gap: clamp(32px, 4vw, 56px); }
}

.close-copy .lede {
  margin-top: 12px;
  color: var(--text-2);
  max-width: 46ch;
  /* balance, not the template's default pretty on <p>: this is a short
     paragraph and pretty alone left its last line about a quarter the
     width of the others at several test widths. */
  text-wrap: balance;
}

.reassure { margin-top: 24px; display: grid; gap: 12px; padding: 0; list-style: none; }
.reassure li { display: flex; gap: 10px; font-size: .97rem; color: var(--text-2); }
/* balance, not pretty. Each of these is two lines at phone widths, and
   pretty alone left the second line about a quarter the width of the first
   (75px against a 322px measure at 390). balance splits the two lines
   evenly instead, which is a re-break rather than a wider bind: no long
   non-breaking chunk is created, so nothing can overflow a 320px screen. */
.reassure li span { text-wrap: balance; }
.reassure svg {
  flex: none; margin-top: 4px;
  fill: none; stroke: var(--ok); stroke-width: 2.4;
  stroke-linecap: round; stroke-linejoin: round;
}

/* Below the 700px format line the two pictures collapse to one. Stacked
   columns have no leftover height to fill, so a second photograph would
   only be another screen of scrolling between the reassurance list and the
   form. The second image is dropped from layout there rather than
   shrunk. */
.close-photos { margin-top: 24px; display: grid; gap: 16px; }
.close-photos img:first-child { display: none; }

.close-photo {
  max-width: 100%;
  height: auto;
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}

/* ── the card ──────────────────────────────────────────────────────────
   Same pattern the template already uses for .rail-card inside an
   unbanded section: card and section share var(--bg), separated by a
   hairline border and a lifted shadow rather than a background shift. */
.form-card {
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: clamp(22px, 2.6vw, 32px);
  box-shadow: var(--shadow-lg);
}
.form-card h3 { margin-bottom: 6px; }
.form-card > p { color: var(--text-2); font-size: .95rem; text-wrap: balance; }

.field { margin-top: 16px; }
.field > label, .field-legend { display: block; font-size: .95rem; font-weight: 700; margin-bottom: 6px; }

.hint {
  display: block; font-size: .87rem; font-weight: 400;
  color: var(--text-muted); margin-top: 2px; text-wrap: pretty;
}

.sec-form input[type="text"],
.sec-form input[type="email"],
.sec-form input[type="tel"],
.sec-form input[type="number"],
.sec-form textarea,
.sec-form select {
  width: 100%;
  min-height: 44px;
  padding: 11px 13px;
  font: inherit;
  font-size: 16px;             /* 16px floor stops iOS zooming the page on focus */
  color: var(--text);
  background: var(--bg);
  /* --text-muted stands in for a control-border token brand.css does not
     ship: --line (the hairline color) measured under 3:1 against --bg. */
  border: 1px solid var(--text-muted);
  border-radius: var(--radius-sm);
}
.sec-form textarea { min-height: 96px; resize: vertical; line-height: 1.45; }

/* The plan select. Safari draws a native menulist at its own fixed 23px
   height and ignores min-height and padding on it, so the control measured
   304x23 on an iPhone: half a tap target. Turning the native appearance off
   is the only way to reach 44px in Safari, and it takes the native arrow
   with it, so the wrapper below redraws one out of two token-colored
   borders. No image, no literal color. */
.select-wrap { position: relative; }
.sec-form select {
  -webkit-appearance: none;
  appearance: none;
  height: 48px;
  padding-right: 42px;
}
.select-wrap::after {
  content: '';
  position: absolute; right: 17px; top: 50%;
  width: 9px; height: 9px; margin-top: -7px;
  border-right: 2px solid var(--text-muted);
  border-bottom: 2px solid var(--text-muted);
  transform: rotate(45deg);
  pointer-events: none;
}

.two { display: grid; grid-template-columns: 1fr; gap: 12px; }
@media (min-width: 480px) { .two { grid-template-columns: 1fr 1fr; } }

.kids { border: 0; padding: 0; margin: 24px 0 0; }
.kids legend { padding: 0; }

.kid-row {
  display: grid;
  grid-template-columns: 1fr 76px auto;
  gap: 8px;
  align-items: end;
  margin-top: 10px;
  max-width: 520px;
}
.kid-row label { font-size: .82rem; color: var(--text-2); display: block; margin-bottom: 4px; }
@media (max-width: 380px) {
  .kid-row { grid-template-columns: 1fr 68px auto; gap: 6px; }
}

.kid-del {
  min-width: 44px; min-height: 44px;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--bg); color: var(--text-2);
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  cursor: pointer;
}
.kid-del:hover { color: var(--warn); border-color: var(--warn); }
/* The rule above sets display, and any display declaration outranks the
   browser's own [hidden]{display:none}, which is why the first child row's
   delete button rendered as a 44x44 control while still being hidden from
   assistive technology. A visible control that says it is not there is a
   defect in both directions, so the attribute wins here explicitly. */
.kid-del[hidden] { display: none; }

.kid-add {
  margin-top: 12px;
  min-height: 44px;
  padding: 10px 16px;
  /* brand.css calls --bg-3 the chip rest-state and tile fill, which is
     exactly this job: a soft, non-solid tint behind the accent text. */
  background: var(--bg-3);
  color: var(--accent);
  border: 1px dashed var(--accent);
  border-radius: var(--radius-sm);
  font: inherit; font-size: .93rem; font-weight: 700;
  cursor: pointer;
}

/* Honeypot. Off screen rather than display:none, so a bot that fills
   every visible-looking field still trips it; aria-hidden on the
   wrapping div (in the markup) is what keeps a screen reader from ever
   announcing it. Ported verbatim in effect from the old file. */
.hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; white-space: nowrap; }

.err {
  display: block; font-size: .87rem; font-weight: 700;
  color: var(--warn); margin-top: 6px;
}
.err:empty { display: none; }
[aria-invalid="true"] { border-color: var(--warn); border-width: 2px; }

.form-actions { margin-top: 20px; }
.sec-form .btn { width: 100%; }
@media (min-width: 480px) { .sec-form .btn { width: auto; } }

.form-note { font-size: .85rem; color: var(--text-muted); margin: 16px 0 0; text-wrap: pretty; }
.form-msg {
  margin-top: 16px; padding: 12px; border-radius: var(--radius-sm);
  font-size: .97rem; font-weight: 700;
}
.form-msg[data-state="err"] { background: var(--warn-bg); color: var(--warn); }
.form-msg:empty { display: none; }

.done { text-align: center; padding: 24px 0; }
.done svg {
  margin: 0 auto 16px;
  fill: none; stroke: var(--ok); stroke-width: 2.2;
  stroke-linecap: round; stroke-linejoin: round;
}
.done h3 { margin-bottom: 6px; }
.done p { color: var(--text-2); margin: 0 auto; max-width: 34ch; }

/* =====================================================================
   HERO REWORK.
   Two requirements this block exists to satisfy:
     1. the cream page tone (the token itself is in brand.css) and a warm
        wash on the hero, instead of the hero sitting on flat color.
     2. on desktop the scenario chips and the Today/Instead card sit in
        the copy column BESIDE the phone, not stacked underneath it.
   Every rule below is brand-side. app.css and app.js are not edited.
   ===================================================================== */

/* The wash. This was previously painted on .stage::before at z-index -2
   and the same mechanism works here: .hero is position:relative with no
   z-index, so it opens no stacking context, and a negative-z child paints
   behind the hero's own box but in front of the page. Two color stops
   rather than the old three: ending ON the page color means the hero
   has no visible bottom edge to line up against the first section. */
.hero::before {
  content: ''; position: absolute; inset: 0; z-index: -2;
  background: linear-gradient(168deg, var(--bg-2) 0%, var(--bg) 58%);
}
/* An earlier version of this hero also carried a soft Harbor radial in the
   top right. It is deliberately NOT kept: rendered over this shorter hero it
   read as a gray smudge behind the phone rather than as depth, and
   neither reference design has a cool-toned glow. The gradient alone is
   what makes the page read warm. */

/* ---- eyebrows + Today/Instead card -----------------------------------
   Same shape and the same tokens this card has always used, moved onto
   this page's token names: --slate is --text-2, --hairline is --line,
   --card is the translucent white below. */
/* Berry is the eyebrow color on this brand, which is the one place the
   page does not use Harbor for emphasis. This is the established headline
   eyebrow value, not a new one. */
.hero-kicker {
  margin: 0 0 10px; font-size: 12.5px; font-weight: 700;
  letter-spacing: .08em; text-transform: uppercase;
  color: var(--berry); text-wrap: balance;
}
.hero-kicker:empty { display: none; }

/* ---- both buttons above the fold on a phone --------------------------
   Measured before this block, at 390x844 the hero stacked to a CTA bottom
   edge of 867px against an 844px screen: the second button, and part of
   the first, sat 23px under the fold. 393x852 was 15px under. 430x932 was
   already clear by 63px, and 375x667 was 87px under, which is a different
   order of problem (see the note at the end).

   The stack at 390x844, top to bottom, is what the numbers below come from:
     header 59, hero padding-top 12, kicker 39, kicker margin 10, h1 60,
     sub margin 10, sub 99, gap 14, phone 497, gap 14, buttons 53 = 867.

   Nothing here removes a piece of the hero, shrinks a word of it, or
   touches the phone. Every pixel comes out of empty space and one notch of
   leading:
     hero padding-top   12 -> 6     -6
     kicker margin      10 -> 5     -5
     sub margin         10 -> 5     -5
     .hero-in gap       14 -> 9    -10   (it is spent twice above the buttons)
     .hero-sub leading 1.55 -> 1.45 -6   (99px of four lines -> 93px)
   which lands the buttons at 834px: 10px of clear air at 844 and 18px at
   852, with 430x932 improved rather than spent (869 -> 836).

   THE PHONE IS DELIBERATELY NOT SHRUNK, and the reason is worth keeping.
   Capping --pw at 232 instead of 240 covered the fold with room to spare,
   and broke the hero pointer: the headline on the second demo screen wraps
   to a second line at any phone width of 234px or less, which drops the
   pill the arrow points at by 17px, straight under the caption card. The
   arrow then overlaps the caption card, which is a clear failure at a
   390px wide screen. 235px is the cliff edge, so there is no safe trim worth
   having, and the demo screens are the argument this page makes. Leading
   and empty space cost the hero nothing by comparison.

   375x667 is honestly still short: it ends at 721px against a 667px screen,
   improved from 87px under to 54px under but not fixed. The phone is not
   what is costing it there (the template's own height solve has already cut
   it to 323px at that height); the sub-headline is, at 99px plus its
   margin. The template drops the sub below 640px of height for precisely
   this reason, and moving that threshold up to about 700 would clear the
   fold at 667 outright. That removes a sentence from a real and common
   device rather than moving space around, so it is not done here. */
@media (max-width: 699px), (min-width: 700px) and (max-width: 899px) and (max-height: 520px) {
  .hero { padding-top: 6px; }
  .hero-in { gap: 9px; }
  .hero-kicker { margin-bottom: 5px; }
  .hero-sub { margin-top: 5px; line-height: 1.45; }
}

/* ---- the replaces strip ----------------------------------------------
   The muted line js/site.js inserts directly after .hero-cta. It is
   supporting copy, not a claim: same 12.5px muted register as the perks
   line below it, so it sits under the buttons without competing with them
   or with the phone beside them.

   The wrap mechanism is in js/site.js: every space inside a comma clause is
   already non-breaking by the time this rule sees the text, so the browser
   has five legal break points and all of them are at a comma. balance then
   evens out how the clauses are shared between the lines. Neither half does
   the job alone. Binding without balance leaves greedy wraps that pack the
   first line and drop one clause on the last; balance without binding is
   free to break inside a clause and strand a word.

   Two lines apart on purpose:
     line-height is 1.6, tighter than the perks line's 1.75, so the two
     stacked muted lines read as two separate statements rather than one
     gray block.
     No max-width on the stacked hero, where the sentence gets the wrap's
     full width and takes as few lines as that allows. A 46ch cap like
     .strip-hint's would have forced three lines on a phone. From the 700px
     format line up the cap is not what limits it either: the strip is a
     named row of the hero grid there and the capped copy column sets its
     width.
   ---------------------------------------------------------------------- */
.replaces {
  width: 100%; margin: 4px 0 0; margin-inline: auto;
  font-size: 12.5px; line-height: 1.6; color: var(--text-muted);
  text-wrap: balance;
}

.strip-hint {
  width: 100%; max-width: 46ch; margin: 10px 0 0; text-align: left;
  font-size: 12.5px; font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--text-2); text-wrap: balance;
}
.reveal { width: 100%; max-width: 46ch; text-align: left; }
.rev {
  display: none; margin: 0; padding: 18px 20px;
  background: rgba(255, 255, 255, .72);
  border: 1px solid var(--line); border-left: 3px solid var(--berry);
  border-radius: 0 var(--radius) var(--radius) 0;
}
.rev.on { display: grid; grid-template-columns: auto 1fr; gap: 10px 18px; }
.rev dt {
  font-size: 11.5px; font-weight: 700; letter-spacing: .09em;
  text-transform: uppercase; color: var(--text-2);
  padding-top: 4px; white-space: nowrap;
}
.rev dt.fix { color: var(--berry); }
.rev dd { margin: 0; font-size: 15.5px; color: var(--text); text-wrap: pretty; }
.rev dd.now { color: var(--text-2); }
.rev dd b { font-weight: 650; }

/* Narrow: the two labels stack over their lines. An auto column plus a
   16 character label leaves the copy roughly 30 characters wide at 360,
   which strands a word on nearly every line. From the 700px format line up
   the card sits in the hero's copy column, which is 435px at its narrowest,
   and the label column still leaves the copy about 44 characters. */
@media (max-width: 699px), (min-width: 700px) and (max-width: 899px) and (max-height: 520px) {
  .rev.on { grid-template-columns: 1fr; gap: 4px; }
  .rev dt { padding-top: 12px; }
  .rev dt:first-child { padding-top: 0; }
}

/* ---- desktop: the chips and the card go BESIDE the phone --------------
   The template's desktop hero is copy | phone, with .stage holding the
   phone AND the rail, so the rail lands under the phone and the phone
   stands next to an empty half column. Avoiding that empty column is the
   whole point of this override: the chips and the Today/Instead card
   belong in the copy column instead.

   display: contents dissolves .stage above the format line and only
   there, which promotes .iphone and .rail to grid items of .hero-in so
   they can be placed in different columns. The DOM is left alone on
   purpose: below the line the rail must stay inside .stage, because app.js
   measures the beam from the stage box out to the active chip, and the
   rotation observer watches .iphone inside it.

   .beam is display: none in app.css by default, but app.css turns it back
   on at 899 and below. Between 700 and 899 that would put a visible .beam
   inside a dissolved .stage, where it becomes an unplaced grid item of
   .hero-in and opens a row of its own, so the compressed hero block at the
   end of this file holds it off across exactly that range.
   ---------------------------------------------------------------------- */
@media (min-width: 900px), (min-width: 700px) and (max-width: 899px) and (min-height: 521px) {
  /* Column width, measured against the reference rather than guessed.
     The reference design runs its headline, its chips and its card at one
     shared width (483px at 1440, 491px at 1280) and leaves about 140px
     between that column and the phone. The template instead gives the
     copy column every pixel left over, which at 1280 put the headline on
     an 846px measure while the chips underneath stopped at 468px, and
     dropped a 432px hole between the two halves of the hero. Capping the
     column and centering the pair closes that hole and gives the whole
     left side one right edge. */
  .hero-in {
    grid-template-columns: minmax(0, 600px) auto;
    column-gap: clamp(40px, 5vw, 96px);
    justify-content: center;
    /* One more row than the template's hero: "replaces" sits between the
       buttons and the perks. It is named rather than left to auto-placement
       because column two of every row here is already "stage", so an
       unplaced item would open an implicit ninth row and land under the
       phone instead of under the buttons. */
    grid-template-rows: 1fr auto auto auto auto auto auto auto 1fr;
    grid-template-areas:
      ".        stage"
      "copy     stage"
      "cta      stage"
      "replaces stage"
      "perks    stage"
      "hint     stage"
      "rail     stage"
      "reveal   stage"
      ".        stage";
    row-gap: 12px;
  }
  /* Left-aligned with the headline above, and free to use the whole capped
     column: at 600px the sentence balances onto two lines of roughly 65 and
     52 characters, which is the widest it ever gets to be. */
  .replaces { grid-area: replaces; margin-inline: 0; max-width: none; }
  .stage { display: contents; }
  .iphone { grid-area: stage; align-self: center; justify-self: center; }
  /* The divider, the chips and the card all fill the capped column, so
     the three of them share one left edge AND one right edge with the
     headline above. app.css holds the rail at min(468px, 100%), which
     would have stopped it short of the other two. */
  .strip-hint {
    grid-area: hint; max-width: none; margin: 16px 0 0; padding-top: 20px;
    border-top: 1px solid var(--line);
  }
  .rail   { grid-area: rail; width: 100%; }
  .reveal { grid-area: reveal; max-width: none; }

  /* Chips. Both reference designs draw these as bordered cards with an
     active underline, not as filled pills. The pill is the generic
     template default and it matches neither reference. Desktop only.
     Below the format line the chip stays the 44px icon button beside the
     phone, which is the part of the simulation that has to be
     preserved. */
  .chip {
    position: relative; overflow: hidden;
    justify-content: flex-start; gap: 9px;
    min-height: 44px; padding: 10px 13px; border-radius: 12px;
    font-size: .86rem; font-weight: 650; text-align: left;
    background: var(--card); border: 1.5px solid var(--line);
    color: var(--text-2);
  }
  .chip:hover { border-color: var(--accent); color: var(--accent); }
  .chip.on {
    background: var(--card); border-color: var(--berry);
    color: var(--text); box-shadow: none;
  }
  .chip svg { color: var(--text-2); }
  .chip.on svg { color: var(--berry); }
  /* The underline IS the acknowledgement here, so the template's scale
     pop is turned off: a 1.16 scale on a 150px card reads as a jolt,
     and it pushed the card past the rail's own box on the way. */
  .chip.on.landed { animation: none; }
  .chip::after {
    content: ''; position: absolute; left: 0; bottom: 0;
    height: 3px; width: 0; background: var(--berry);
    transition: width .3s ease;
  }
  .chip.on::after { width: 100%; }

  /* The notification's tail points at the tiles it came from.

     This override is desktop-only because the geometry is desktop-only.
     The grid above puts the scenario rail in the LEFT column and the phone
     in the right one, so at this width the tiles are to the left of the
     card and the template's right-pointing tail aims at nothing. Below
     the format line the rail sits under the phone and app.js draws a
     measured beam out of the card's RIGHT edge, through the channel beside
     the phone and back to the active chip. The tail agrees with that beam
     there, so narrow widths are deliberately left alone.

     On a square rotated 45 degrees, the corner that faces left is the one
     formed by the left and bottom edges, so those are the two that get lit
     and the original pair are switched off. Position, size, transform and
     fill are the template's own and are not restated. */
  .note::after {
    right: auto;
    left: -.34em;
    border-right: 0;
    border-top: 0;
    border-left: 1px solid rgba(255, 255, 255, .13);
    border-bottom: 1px solid rgba(255, 255, 255, .13);
  }
}

/* =====================================================================
   PHOTOGRAPHS.
   js/site.js puts a <figure class="sec-photo"> into three of the generated
   sections. All of the shaping is here. Nothing in this block touches
   app.css or app.js; the template simply has no image pattern, so the
   brand side supplies one.
   ===================================================================== */
.sec-photo { margin: 0; }
.sec-photo img {
  display: block; width: 100%; height: 100%;
  object-fit: cover; border-radius: var(--radius);
  box-shadow: var(--shadow);
}

/* Band. A wide, shallow crop under the heading of the "how" section, which
   is the one shape that does not fight the four scrolling rail cards below
   it. The aspect ratio is doing the work: the source file is 1200x804 and
   dropped whole it would add most of a screen of height to the section.

   Do not make these ratios shallower. A 21/6 band renders 1160x331 at 1440, a 3.50:1 window on a 1.49:1 photograph, which kept
   the middle 42% of the frame vertically and cut the heads off BOTH people.
   This is the one picture on the page of a parent and a caregiver actually
   meeting, so their faces are the whole point of it.

   The ratios below were solved against the source rather than guessed. The
   visible slice of the original is 1200/AR tall out of 804, so the top edge
   of the crop lands at (804 - 1200/AR) * P, where P is the object-position
   percentage. The man's hair starts at y=58 and the caregiver's chin ends
   at about y=340, so the crop has to start above 58 and end below 340 at
   every ratio here:
     16/9  -> 674 tall, top edge  13, bottom edge 687
     21/10 -> 571 tall, top edge  23, bottom edge 594
     11/5  -> 545 tall, top edge  26, bottom edge 571
     13/5  -> 462 tall, top edge  34, bottom edge 496   (landscape phone)
   One object-position value clears both faces at all four, so there is one
   value and not four. */
.sec-photo-band { margin: 4px 0 26px; }
.sec-photo-band img { aspect-ratio: 16 / 9; object-position: 50% 10%; }
@media (min-width: 700px)  { .sec-photo-band img { aspect-ratio: 21 / 10; } }
@media (min-width: 1200px) { .sec-photo-band img { aspect-ratio: 11 / 5; } }

/* Side. Above the content on phones, second column beside it from the
   700px format line up. The photograph's column is 34% of the row at every
   width from 700 to 1920, so it narrows with the window rather than
   stepping, and the tiles section widens that share to a half from 1000
   (see the compressed section block at the end of this file). */
.sec-photo-side { margin: 26px 0 0; }
.sec-photo-side img { aspect-ratio: 16 / 10; }

/* A phone held sideways is under 400px tall, and the stacked 16/10 crop
   measured 503px there: one photograph taller than the whole screen, which
   is the same wasted-screen problem as an empty rectangle. Wider crops put
   both back under a single screen (230px and 306px at 844 wide). */
/* A 21/6 ratio here crops both faces out of frame, the same failure the
   shallower desktop ratios had. Keep the aspect tall enough to retain full
   heads. 13/5 costs about 80px more height on a landscape phone (309px
   against 230px at 844 wide, still well inside one screen) and keeps two
   faces that the shallower crop threw away. */
@media (orientation: landscape) and (max-height: 560px) {
  .sec-photo-band img { aspect-ratio: 13 / 5; }
  .sec-photo-side img { aspect-ratio: 21 / 8; }
}

@media (min-width: 1000px), (min-width: 700px) and (max-width: 999px) and (min-height: 521px) {
  .sect.photo-side .wrap {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(260px, 34%);
    column-gap: clamp(32px, 4vw, 64px);
    align-items: start;
  }
  .sect.photo-side .sect-head { grid-column: 1 / -1; }
  /* Row 2 column 2 explicitly, so the pattern content auto-places into row
     2 column 1 whether it emits one child (chip-grid, tl) or several. */
  .sect.photo-side .sec-photo-side {
    position: relative; grid-column: 2; grid-row: 2;
    margin: 0; align-self: stretch; min-height: 200px;
  }
  /* Absolute, and that is the whole trick. Left in flow the image offers
     its own intrinsic ratio as the row's height (369px for a 1280x858 file
     at this column width), which is taller than the content beside it and
     reopens the same gap underneath the content that this is closing. Taken
     out of flow it contributes no height at all, so the content column sets
     the row and the photograph fills exactly what the content made. */
  .sect.photo-side .sec-photo-side img {
    position: absolute; inset: 0; aspect-ratio: auto;
  }

}

/* Half and half for the tiles, which is a measurement rather than a
   preference: eight tiles four-across is two rows and about 160px of
   content, which is too short to sit beside a photograph without stranding
   space under it. Two-across is four rows and about 260px, which matches.

   A half of the row is 314px of text at 700, and two tiles in 314px put
   "Announcements" past the edge of its own tile, so below 1000 this share
   is reached by a line rather than by this step. That line is in the
   compressed section block at the end of this file: a third of the row at
   700, this half at 1000. */
@media (min-width: 1000px) {
  #what.photo-side .wrap { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
}

/* =====================================================================
   BALANCE FIXES.
   ===================================================================== */

/* Eight tiles in the template's repeat(auto-fit, minmax(158px, 1fr)) chose
   five columns at 1280 and above, which left the last row three tiles wide
   and a blank tail beside them. Fixed counts that divide eight evenly are
   the only arrangement with no stranded row: 2 x 4, then 4 x 2.

   The four-across run stops at 699 rather than continuing to 999, because
   from 700 up the tiles share the row with the photograph and there is no
   room for four. That leaves one step where the count RISES as the window
   narrows, at the 699/700 line, and it is left there deliberately: below
   700 the page is the stacked layout that is already signed off, and the
   compressed section block at the end of this file is not allowed to reach
   into it. */
#what .chip-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (min-width: 560px) {
  #what .chip-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
@media (min-width: 1000px) {
  /* Back to two across from here up, because from 1000px the tiles share
     the row with the photograph. Four rows of two divides eight evenly, and
     it is also what gives the content column the height the picture needs
     to sit beside. */
  /* The template centers this grid with margin:0 auto and caps it at 900px.
     Auto margins on a stretched grid item switch it to fit-content, which
     is why the tiles sat 402px wide in a 551px column with a hole between
     them and the photograph. Zeroing the inline margin restores the
     stretch. */
  #what .chip-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    max-width: none; margin-inline: 0;
  }
}

/* The request section's left column ends long before the form beside it
   does, because a nine-field form is taller than four sentences and a
   picture. That surplus is roughly 330px at desktop widths and it has to
   go somewhere.

   Spending it as air was the previous answer: the column filled the row and
   the leftover was split into two padded gaps, one above the reassurance
   list and one above the photograph, about 130px and 147px at 1440. Both
   were under a picture rather than between paragraphs, which is exactly
   where a gap reads as a hole rather than as breathing room.

   Spending it on ONE picture is worse. Stretching the 1150x858 file to fill
   the column put it in a 552x1116 box, a 0.49:1 window on a 1.34:1 frame:
   37% of the picture kept, upscaled 1.30x, the book cropped out and the
   caregiver's head cut off at the forehead. A max-height ceiling stopped the
   upscale but handed the leftover straight back as the gaps above.

   Two pictures spend it as content. The pair fills the column on equal
   rows, so neither is asked to hold a crop the frame cannot carry, and the
   surplus becomes a second photograph instead of two voids. Both are
   object-fit: cover, so growth is always a re-crop and never a stretch, and
   the full height of each frame is kept. Only width is trimmed.

   object-position is per-frame, set by opening the renders rather than by
   arithmetic:
     hallway  45% 55%. The stroller sits left and the dog sits right, so the
              subject spans most of the frame. Slightly left of center keeps
              the stroller's front wheel in, and slightly low keeps the floor
              under the dog rather than empty wall above it.
     couch    55% 50%. The subject sits right of center: the toddler starts
              near x=420 and the book ends near x=860 of 1150, and a 55%
              window keeps both inside the crop at every column width here.

   position:sticky is gone with the gap that justified it. A column that
   fills its row is taller than the viewport, so it can never pin, and
   leaving the declaration in would have been a comment that lied. */
@media (min-width: 900px), (min-width: 700px) and (max-width: 899px) and (min-height: 521px) {
  .close-grid { align-items: stretch; }
  .close-copy { display: flex; flex-direction: column; }
  .reassure { margin-top: 30px; gap: 14px; }
  .close-photos {
    flex: 1 1 auto;
    min-height: 0;
    margin-top: 30px;
    /* minmax(0,1fr), not 1fr. A bare 1fr keeps an auto minimum, and the
       automatic minimum of an image track is the file's own height, which
       would push the rows past the column instead of dividing it. */
    grid-template-rows: minmax(0, 1fr) minmax(0, 1fr);
  }
  .close-photos img:first-child { display: block; }
  .close-photo {
    width: 100%;
    height: 100%;
    min-height: 0;
    object-fit: cover;
  }
  .close-photos img:first-child { object-position: 45% 55%; }
  .close-photos img:last-child { object-position: 55% 50%; }
}

/* =====================================================================
   QUALITY AND PLACEMENT.
   Nothing in this block changes a color, a font family or the type scale.
   It changes how much room things get and how big they are to touch.

   TEMPLATE DEVIATIONS declared here, all of them brand-side overrides of
   values that live in app.css. app.css itself is not edited and stays byte
   identical to the shared site-template template/ folder (referred to by
   name, not by absolute path: this file is world readable once uploaded):
     1. vertical rhythm, one proportion across the whole page: .sect
        padding 74 -> 36 at >=700px and 52 -> 32 below (app.css lines 420
        and 261), .sect-head margin-bottom 26 -> 20 and 26 -> 17 below
        (app.css line 262), .sect-cta margin-top 26 -> 18 (app.css line
        327) and .hero padding-bottom 56 -> 42 at >=700px (app.css line
        382). Whitespace only; no type size and no copy changed.
     2. the rail pattern in #how: a scroll container of four cards plus a
        photograph above it -> one rotating band          (app.css line 269)
     3. .brand vertical padding: 0 -> 7px                 (app.css line 40)
     4. .nav-toggle box: 42px -> 44px                     (app.css line 49)
     5. .foot-links a: inline text -> 44x44 minimum box   (app.css line 335)
     6. .stat-grid columns on #pricing: 2 then 4 -> 1 then 3 (app.css 319/425)
     7. .p-stat span in the phone mock: .62em -> .86em     (app.css line 174)
     8. .nav a: inline text -> 44px minimum box           (app.css line 46)
     9. .hero-cta .btn on a short landscape screen: a fixed
        44% share of a narrow column -> content width      (app.css line 87)
    10. .tl max-width: 640px -> 704px                      (app.css line 288)
    11. hero spacing below 700px, so both buttons clear the fold on a
        phone: .hero padding-top 12 -> 6 (app.css line 359), .hero-in gap
        14 -> 9 (app.css line 73), .hero-sub margin-top 10 -> 5 and
        leading 1.55 -> 1.45 (app.css line 78). The phone itself is left
        at the template's size on purpose, see that block for why.
    12. the how-it-works band's own spacing below 700px: eyebrow margin
        9 -> 4, band row-gap 14 -> 10, card padding 20 -> 14 and card
        row-gap 9 -> 6 (app.css lines 269, 411 and 425). Whitespace inside
        the band only; the section's own padding and the space under its
        heading are set with every other section's, in item 1.
    13. #how .sect-head max-width: 60ch -> 72ch (app.css line 262), so this
        section's heading and subline each set on one line at desktop
        widths instead of breaking mid-phrase.
    14. the width the desktop format starts at: 900 -> 700 for the hero and
        the menu (app.css lines 358 and 378), and 1000 -> 700 for the
        sections that carry a side photograph. app.css declares the menu,
        the two-column hero and the desktop chip rail from 900px up and a
        stacked layout at 899 and below; this file used to put the side
        photographs at 1000. Both reach down to 700 here. See "13. the
        compressed desktop band" at the end of this file for every figure,
        including which of the two ranges each rule is confined to.
   ===================================================================== */

/* ── 1. section rhythm ────────────────────────────────────────────────
   The template pads every .sect 74px top and bottom above 900px, so each
   seam between two sections is 148px of nothing, five seams deep. That is
   far too much empty vertical space between sections. The value that
   replaces it is set once, at the end of this file, in the block headed
   "8. vertical space": it is the same figure for .sect and for the
   brand-side .sec-form, so every seam on the page is one measurement. */

/* ── 2. how-it-works: one rotating band instead of a photo and a rail ──
   The template's rail pattern put four cards in a row (or a swipeable snap
   rail on a phone) and the brand side put a full-width photograph above
   them. Measured, that section ran 1142px at 1440 and 784px at 390, and
   557px of the desktop figure was the photograph alone. Four short
   scenarios were costing more than a screen and a half.

   js/site.js folds all of it into one horizontal band: the same four
   .rail-card articles, stacked in a single grid cell so exactly one is
   visible at a time, with the photograph beside them. The card copy, the
   card shape and the "Today:" line underneath are untouched; only the
   container changed.

   The photograph is the piece that does the work. Absolutely positioned
   inside its own column, it contributes no height of its own and simply
   fills whatever the cards beside it make, which is the same trick
   .sect.photo-side already uses further down this file. So a picture that
   used to add 557px now adds nothing, and it still crops taller than the
   11/5 band it replaces (about 2.0:1 at 1440), so both faces are kept.

   Below 700px the photograph is not shown at all. That is the one real
   loss here and it is deliberate: at 390 a crop shallow enough to keep two
   faces is still 135px, which is a third of the height budget for the
   whole section, and the picture is what made this section too tall. The four cards are the content; the picture was
   the ornament, and phones keep the content.

   700 and not the 720 this was first measured at, so the band turns two
   columns on at the same width as every other section on the page. A 36%
   column is 238px at 700, which is inside the range the crop was chosen
   for.

   min-height at 700px and up is what stops the band collapsing to the
   height of three lines of body copy on a wide screen, which would leave
   the photograph a letterbox strip. 240px is the measured floor at which
   the photograph still reads as a photograph. */
/* Text that only a screen reader or a voice-control user needs. Clipped to
   a single pixel rather than hidden, because display:none and
   visibility:hidden both take it out of the accessibility tree, which is
   the one place it is meant to exist. */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ── 2a. the heading over the band gets a measure that holds it ───────
   The template caps every .sect-head at 60ch, which resolves to 605px.
   This section's heading needs 645px to set on one line and its subline
   needs 605px, so at 1440 the heading broke after "in every" and the
   subline broke after "The rest is", two ragged lines each on a page with
   1160px of column available. 72ch is 726px: past both natural widths with
   room for a fallback font, and still short of the 860px the narrowest
   desktop offers, so the heading never runs the full width of the page.

   Below roughly 650px of viewport the subline genuinely cannot fit and
   text-wrap: balance splits it evenly rather than stranding its tail. */
#how .sect-head { max-width: 72ch; }

/* The band's own geometry, in one place so the peek, its fade and the lane
   they sit in can never drift apart.

     --hb-strip  the lane on the right the next card shows through
     --hb-gap    the air between the active card and the card behind it
     --hb-fade-*  where the next card starts and finishes dissolving

   Every one of these is a percentage on purpose. A fixed strip is a
   different fraction of a card at every screen size: 92px was 12% of the
   card at 1440 and 23% of it at 740, so the peek stopped being a hint and
   started being half a second card. As percentages the ratio is one number
   everywhere. The card takes 87% of the column, so the lane behind it is
   12.5% of a card wide, and the fade puts paint on the first 9% of that
   card and nothing after. Both figures sit inside the 5 to 15% the peek is
   meant to hold, at 320px and at 1920px alike: enough to read as a card
   waiting its turn, not enough to compete with the one being read. */
.hb {
  --hb-strip: 13%;
  --hb-gap: 2.4%;
  --hb-fade-a: 3%;
  --hb-fade-b: 9%;
  --hb-btn: 34px;
  display: grid;
  gap: 12px;
  grid-template-areas: "slides" "dots";
  grid-template-rows: auto auto;
}
/* overflow-x: clip and not hidden, and not a clip-path on the card itself.
   A full-width card translated into the strip runs off the right of the
   page, and clip-path stops it painting there but does not stop it adding
   horizontal scroll. clip does both, it creates no scroll box of its own,
   and it is the one overflow value that may sit beside visible on the other
   axis, so the cards keep their shadows above and below. The clip margin is
   the widest shadow any card in here casts, so nothing is cut off a card
   that is fully on screen. Nothing of the card behind is lost either: its
   mask has already reached zero alpha two pixels inside this edge. */
.hb-slides {
  display: grid;
  grid-template-areas: "slide";
  position: relative;
  overflow-x: clip;
  overflow-clip-margin: 26px;
}
.hb-slide {
  grid-area: slide;
  position: relative;
  flex: none;
  /* the card gives up the strip so the next one has somewhere to sit */
  width: calc(100% - var(--hb-strip));
  max-width: none;
  scroll-snap-align: none;
  z-index: 0;
  opacity: 0;
  pointer-events: none;
  transform-origin: left center;
  transform: scale(.98);
  transition: opacity .34s ease,
              transform .45s cubic-bezier(.22, .61, .36, 1);
}
.hb-slide.on {
  z-index: 2;
  opacity: 1;
  pointer-events: auto;
  transform: none;
}

/* The card behind. Three things make it read as depth rather than as a
   card with its right-hand side sliced off:

   1. It is scaled down and sits at reduced opacity, so it is behind.
   2. Its right-hand side is dissolved by a mask rather than cut. The mask
      runs at 93deg, not 90, so the dissolve is a soft diagonal and there
      is no vertical edge anywhere in it. It reaches zero alpha a clear
      margin inside the column at every width, so the container's own clip
      never has anything visible left to cut.
   3. Its left edge is its own rounded card border, arriving from behind
      the active card, which is what a stack of cards actually looks like.

   It also slides. A card enters from here, so becoming the card in front
   is one continuous move left and up to full size rather than a swap, and
   the card that has just been read fades where it stands instead of
   traveling the other way across it. */
.hb-slide.next {
  z-index: 1;
  opacity: .62;
  pointer-events: auto;
  cursor: pointer;
  transform: translateX(calc(100% + var(--hb-gap))) scale(.945);
  -webkit-mask-image: linear-gradient(93deg, #000 0,
                      #000 var(--hb-fade-a), transparent var(--hb-fade-b));
          mask-image: linear-gradient(93deg, #000 0,
                      #000 var(--hb-fade-a), transparent var(--hb-fade-b));
}
/* the band is a panel, not a card in a row of cards: nothing to lift */
@media (hover: hover) {
  .hb-slide:hover { transform: none; box-shadow: var(--shadow), var(--lip); }
  .hb-slide.next:hover {
    opacity: .82;
    transform: translateX(calc(100% + var(--hb-gap))) scale(.945);
  }
}
@media (prefers-reduced-motion: reduce) {
  .hb-slide { transition: none; }
}

/* ---- the progress line ------------------------------------------------
   A rule across the bottom of the card being read that fills over exactly
   the time that card has left, and starts again at zero the moment the
   band advances.

   The color is var(--accent), the brand's Harbor blue. It is this site's
   established active-state color and it is already what paints the
   selected control below the band, so the line and the indicator agree
   with each other instead of introducing a second idea of "active". There
   is no red anywhere in this palette and this is not a warning.

   The animation is also the clock. The script advances the band when this
   finishes rather than on a timer of its own, so what is on screen and
   what the band is about to do can never disagree: pausing the line pauses
   the band, and there is no elapsed time to lose and re-guess on hover. */
.hb-prog {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 3px;
  overflow: hidden;
  border-radius: 0 0 calc(var(--radius) - 1px) calc(var(--radius) - 1px);
  background: rgba(30, 77, 107, .13);
  opacity: 0;
  transition: opacity .2s ease;
}
.hb-slide.on .hb-prog { opacity: 1; }
.hb-prog i {
  display: block;
  width: 100%; height: 100%;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: left center;
}
.hb-slide.on .hb-prog i { animation: hb-fill 7000ms linear forwards; }
.hb.paused .hb-slide.on .hb-prog i { animation-play-state: paused; }
/* Once a reader has taken the band over by hand it will not advance again,
   so a line promising that it will would be a lie. */
.hb.held .hb-slide.on .hb-prog { opacity: 0; }
@keyframes hb-fill { from { transform: scaleX(0); } to { transform: scaleX(1); } }
@media (prefers-reduced-motion: reduce) {
  /* no autoplay for this reader, so there is no duration to draw */
  .hb-prog { display: none; }
}

/* ---- the next control -------------------------------------------------
   Sits centered on the sliver of the card behind, which is the one place
   on the band where a control means "go that way" without a label. The
   peeking card is clickable in its own right; this is the part of that
   affordance a reader can see. */
.hb-next {
  position: absolute;
  top: 50%;
  right: calc((var(--hb-strip) - var(--hb-gap)) / 2 - var(--hb-btn) / 2);
  z-index: 3;
  width: var(--hb-btn); height: var(--hb-btn);
  display: flex; align-items: center; justify-content: center;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 50%;
  background: #FFFDF8;
  color: var(--accent);
  box-shadow: 0 4px 14px -4px rgba(74, 54, 38, .34);
  cursor: pointer;
  transform: translateY(-50%);
  transition: background .2s ease, color .2s ease, box-shadow .2s ease;
}
.hb-next svg { width: 17px; height: 17px; }
/* the visible circle is 34px on a phone, so the button grows a transparent
   ::after to a full 44px finger without widening the sliver it sits on */
.hb-next::after { content: ""; position: absolute; inset: -5px; }
@media (hover: hover) {
  .hb-next:hover {
    background: var(--accent); color: var(--on-accent);
    box-shadow: 0 6px 18px -4px rgba(30, 77, 107, .48);
  }
}
.hb-next:focus-visible { outline: 2px solid var(--accent-dk); outline-offset: 3px; }

.hb-photo { grid-area: photo; margin: 0; position: relative; display: none; }
.hb-photo img { position: absolute; inset: 0; aspect-ratio: auto; }

/* ---- the controls under the band --------------------------------------
   These were four 8px pills. They were the right size for a position
   indicator and the wrong size for a control: nobody looked at them and
   thought they could be pressed, and an invisible 44px hit area does not
   change what a thing looks like.

   They are buttons now, and they are shaped like the buttons everywhere
   else on this page: a real border, the same card face, the same lifted
   shadow, a 44px box and a pressed state. From 1040px up each one carries
   the name of the card it selects, which is the width measured to hold all
   four on a single row; below that the row would break three and one, so
   the labels stay in the accessibility tree and a numeral shows instead. */
.hb-dots {
  grid-area: dots;
  display: flex;
  gap: 8px;
  justify-content: center;
  align-items: center;
}
.hb-dot {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  padding: 0 13px;
  font: inherit;
  font-size: .82rem;
  font-weight: 700;
  line-height: 1;
  color: var(--text-2);
  background-image: linear-gradient(148deg, #FFFDF8 0%, #FDFAF3 54%, #FBF6ED 100%);
  border: 1px solid var(--line);
  border-radius: 99px;
  box-shadow: var(--shadow), var(--lip);
  cursor: pointer;
  white-space: nowrap;
  transition: background-color .2s ease, color .2s ease,
              border-color .2s ease, transform .2s ease;
}
.hb-dot.on {
  background-image: none;
  background-color: var(--accent);
  border-color: var(--accent-dk);
  color: var(--on-accent);
}
@media (hover: hover) {
  .hb-dot:hover { border-color: var(--accent); color: var(--accent-dk); }
  .hb-dot.on:hover { color: var(--on-accent); }
}
.hb-dot:active { transform: translateY(1px); }
.hb-dot:focus-visible { outline: 2px solid var(--accent-dk); outline-offset: 3px; }
/* The card's name. Present for a screen reader and for voice control at
   every width; painted only where all four fit on one row. */
.hb-dot-label {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
@media (prefers-reduced-motion: reduce) {
  .hb-dot { transition: none; }
  .hb-dot:active { transform: none; }
}

@media (min-width: 700px) {
  .hb {
    /* the photograph spans the card row only, not the dot row, so its
       bottom edge lands on the card's bottom edge instead of hanging 24px
       below it */
    grid-template-areas: "photo slides" ".     dots";
    grid-template-columns: minmax(0, 36%) minmax(0, 1fr);
    grid-template-rows: 1fr auto;
    column-gap: 26px;
    min-height: 240px;
    --hb-btn: 44px;
  }
  .hb-photo { display: block; }
  .hb-dots { justify-content: flex-start; }
  .hb-next svg { width: 20px; height: 20px; }
  .hb-next::after { content: none; }
}

@media (min-width: 1040px) {
  .hb-dot b { display: none; }
  .hb-dot-label {
    position: static;
    width: auto; height: auto;
    margin: 0;
    overflow: visible;
    clip-path: none;
  }
}

/* ── 3. card footer tags share a baseline ─────────────────────────────
   .meta is pushed to the bottom of its card by margin-top:auto, so the four
   tags always ended flush at the bottom but STARTED at two different
   heights: one-line tags at y=302 and two-line tags at y=285, which reads
   as a ragged row. Reserving two lines means a one-line tag occupies the
   same box a two-line tag does and every tag begins on the same baseline.
   2lh is the exact figure; the px value is the fallback for anything that
   does not know the unit. */
#how .rail-card .meta { min-height: 38px; min-height: 2lh; }

/* ── 2b. the band's own chrome on a phone ─────────────────────────────
   Turning the rail into a band took the section from 784px to 512px at
   390, but a full accounting of what was left showed the container was
   still spending far more on itself than on the writing. Measured at 390,
   every pixel of the section with no remainder:

     section padding-top                     42.0
     the eyebrow above the heading           19.3
     its margin below                         9.0
     heading, two lines at 24/27.84          55.7
     the section sub's margin above          11.0
     the section sub, two lines              50.6
     .sect-head margin below                 22.0
     the tallest slide                      234.3
     .hb row-gap                             14.0
     the dot row                             12.0
     section padding-bottom                  42.0
                                            -----
     511.8

   Only 116.6 of that is the body copy in the tallest card. The rest is
   whitespace and furniture, and the six values below are the whitespace
   half of it: paddings, margins and gaps only. Nothing gets smaller,
   nothing is hidden, and not one word is cut.

     section padding    42 -> 28   -28
     .sect-head margin  22 -> 12   -10
     .hb row-gap        14 -> 10    -4
     card padding       20 -> 14   -12
     card row-gap        9 ->  6    -6
     eyebrow margin      9 ->  4    -5

   511.8 -> 446.8 at 390, so the section is 43% shorter than the 784px it
   started at, with every sentence intact. Two further cuts were measured
   and rejected: dropping the heading to 21px buys 7px for a smaller
   heading, and hiding the eyebrow buys 23px by deleting a line of
   writing. Neither one reaches the target either, so neither is worth
   what it costs.

   Why the tallest slide governs the height and not the visible one: all
   four slides share a single grid cell and the inactive ones are
   visibility:hidden, which still occupies layout. That is deliberate. It
   is what stops the band changing height as it rotates, and a band that
   jumps every seven seconds would be a worse fault than the pixels it
   would save. */
@media (max-width: 699px), (min-width: 700px) and (max-width: 899px) and (max-height: 520px) {
  /* Section padding and the space under the heading were on this list and
     are not any more. Both are now cut for every section at once in the
     "8. vertical space" block at the end of this file, to within a few
     pixels of the figures below, and one section pulled tighter than its
     neighbours is exactly the uneven rhythm that block exists to prevent.
     What is left here is internal to the band and to nothing else. */
  #how .kicker { margin-bottom: 4px; }
  #how .hb { row-gap: 10px; }
  #how .rail-card { padding-top: 14px; padding-bottom: 14px; row-gap: 6px; }
}

/* ── 4. tap targets ───────────────────────────────────────────────────
   Everything a finger is meant to hit reaches 44px in its smaller
   dimension. Padding does the work in every case: not one font-size below
   changes, so the type scale is untouched.

   The footer link min-width is a later addition. The other three footer links are
   already 45.7, 67.8 and 52.5px wide; only "Apply" at 37.8px missed, and
   only on the horizontal. A min-width reaches 44px on that one link and
   leaves the other three at their natural width, so the gaps either side
   of the three separators do not move. Padding-inline would have widened
   all four and shifted the whole row.

   The header nav links are a later addition on the same principle. They are
   only on screen from the 700px format line up, and a 1024x768 tablet in
   landscape is a touch screen, so a finger was aiming at four 23.6px tall
   anchors. Making each one a
   centered 44px box adds nothing to the header: .brand already stands 44px and
   the header button stands 46px, so the tallest thing in the row does not
   change and the bar keeps the height it has. The 44px is a floor and it is
   not one of the figures the compressed band shrinks: only the type size and
   the horizontal gaps come down there. */
.site-head .brand { padding-block: 7px; }          /* 30px -> 44px */
.site-head .nav a {
  display: inline-flex; align-items: center;
  min-height: 44px;                                 /* 23.6px -> 44px */
}
.site-head .nav-toggle { width: 44px; height: 44px; }  /* 42px -> 44px */
.foot-links a {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 44px;                                 /* 17px -> 44px */
  min-width: 44px;                                  /* "Apply" 37.8px -> 44px */
}

/* ── 5. small text on phones ──────────────────────────────────────────
   Raised, and scoped below the 700px format line so desktop typography is
   exactly what it was. Each of these is text somebody has to READ:
     .hint       13.92px -> 15.04px  (what a field wants)
     .kid-row label 13.12px -> 15.04px  (First name / Age)
     .err        13.92px -> 15.04px  (why a field was rejected)
     .form-note  13.60px -> 15.04px  (what the data is used for)
     .foot-legal 12.80px -> 15.04px  (the invite-only line)
   Deliberately NOT raised, because these are small-caps kickers whose size
   is the design rhythm rather than a reading size, and enlarging them turns
   a quiet label into a heading: .hero-kicker and .strip-hint (12.5px), the
   .rev dt Today/Instead labels (11.5px), the template's own .kicker and the
   .rail-card .meta tag. .stat span (13.44px) is also left alone: it is the
   caption bolted to a 2.1rem price, and matching it to body size flattens
   the one piece of hierarchy that tile has. */
@media (max-width: 699px), (min-width: 700px) and (max-width: 899px) and (max-height: 520px) {
  .sec-form .hint,
  .sec-form .err,
  .sec-form .kid-row label,
  .form-note,
  .site-foot .foot-legal { font-size: .94rem; }
}

/* ── 5b. re-breaking, not re-binding ──────────────────────────────────
   Three blocks ended on a stub of a line. The fix is text-wrap: balance
   everywhere rather than a wider non-breaking bind, for the reason the
   bindTails() note in js/site.js gives: a long unbreakable chunk is what
   overflows a 320px screen. balance moves the break point, it never glues
   words together, so nothing here can spill.

     .tl-items p       the Privacy bodies. "Invite only" ended on a 47px
                       tail against a 588px measure at 1280 and up.
     .sect-head p      the section subs, the pricing one especially: it
                       ended on a 125px tail against 578px.
     .foot-legal       raised from 12.8px in the block above, which pushed
                       a line that used to fit on one into two, the second
                       of them two words long. Balancing it is the cost of
                       making it readable, and it is the right trade.
     .form-lede        the form intro, which ended on "the first try."
                       alone. This is a NEW class, added in index.html,
                       because the stylesheet's existing .form-card > p
                       rule matches nothing: that paragraph is a child of
                       the form element, not of .form-card. Left as found
                       rather than repointed, since repointing it would
                       change the paragraph's size and color, and this
                       pass is placement and quality only. Known
                       limitation, left in place deliberately. */
.tl-items p,
.sect-head p,
.site-foot .foot-legal,
.form-lede { text-wrap: balance; }

/* ── 5b-i. the Privacy bodies get a measure wide enough to hold them ───
   The two items under "Who gets in" are the same shape of claim and should
   read as a matched pair, but they did not: "Your portal holds your family.
   It is not a marketplace and your information is never sold." needs 590px
   and sat on one line, while "There is no public sign-up. A caregiver gets
   in when you invite them, and out when you say so." needs 640px and fell
   to two. One line beside two lines is the mismatch, and the copy was not
   the thing that was wrong.

   The template caps .tl at 640px, and .tl-items indents its rail 24px, so
   the body measure stopped at 616px and stayed there no matter how wide the
   window got. 616 is 24px short of 640, which is why the sentence broke.

   704px here is 680px of measure: 40px of slack over the 640px the sentence
   needs, which is 6.3%. The slack is the point. 640px was measured with the
   typeface that resolved on one machine; a fallback face somewhere else
   renders the same sentence a few per cent wider, and a max-width tuned to
   648px would break the line again on the first machine that substituted a
   font. 6.3% absorbs that.

   No media query, because it does not need one. A max-width only binds when
   the column behind it is wider, so below about 744px the .wrap is already
   narrower than 704 and this rule changes nothing at all. The widths it
   actually reaches are the ones where the old 640 was leaving usable column
   unused, and that includes a phone held sideways: 844x390 was rendering
   the same mismatched one-line-beside-two-lines pair, and now does not.

   What this does NOT fix, stated plainly. From 700px the section becomes
   two columns and the photograph takes 34% of them, so the text column, not
   this max-width, is what limits the measure. Both items therefore still
   run two lines from 1000 to 1031 (which is uniform: they are both two
   lines, and 1024 lands here), and "Invite only" is still the only one of
   the two on two lines from 1032 to 1119. From 1120 the text column clears
   644px and both sit on one line, all the way to 1920.

   Closing that 1032 to 1119 band was investigated and rejected twice.
   Pushing the two-column breakpoint up to 1120 stacks the photograph above
   the text at 1024, which adds roughly 600px of height to two sections at
   the single most common laptop width, in a round whose whole purpose was
   removing height. Squeezing the photograph column to 26% and the gutter to
   32px buys the measure at 1024 but leaves a 256px picture beside a 696px
   column, which is a lopsided section traded for one line break. An 88px
   band of window widths where one of two sentences runs long is the smaller
   cost of the three. */
.tl { max-width: 704px; }

/* ── 5c. the how-it-works card bodies ─────────────────────────────────
   Same treatment, same reasoning. Widening the card from
   275px to 573px (block 2 above) did not touch the two intermediate widths
   that were measured, because both were already two-up and already three
   lines: at 1179 "The end of the day" ended on a 53px stub against a 465px
   line, and at 1100 "A sitter cancels" ended on 111px against 413px.
   Balancing evens the three lines out instead of letting the last one fall
   where it lands. Not a bind: no pair of words is glued, so nothing here
   can push past a 320px screen.

   Scoped to the 700px format line and up, and that floor is measured rather
   than tidy. Below it the cards are the template's 296px snap rail and the
   bodies run five to eight lines. Balanced across that many lines the
   browser moved the break in "A new sitter" and left "shift." alone on the
   last line at 320 and at 360, an orphan that was not there before:
   the orphan sweep went from 0 to 2 on those two widths and nowhere else in
   the 320 to 1920 range. The band layout above the line gains from
   balancing and reports no orphan at any swept width, so the rule stops
   where the harm starts. Phones keep exactly the wrapping they already
   passed on. */
@media (min-width: 900px), (min-width: 700px) and (max-width: 899px) and (min-height: 521px) {
  #how .rail-card p { text-wrap: balance; }
}

/* ── 6. the price tiles hold prices only ──────────────────────────────
   "14 days free" was the fourth cell of a four-cell price grid, so a
   duration sat in the row reading as a fourth plan next to $7.99, $19.99
   and $149. The trial is stated once now, as the first of the four terms
   under the button, and this row holds dollar amounts only.
   That leaves three tiles, and three does not divide the template's two-up
   or four-up grids without stranding one on its own row, so the count is
   set here: one column on a phone, three across from 560px, which is the
   width at which a $19.99 at 25.6px still fits a third of the row. */
#pricing .stat-grid { grid-template-columns: minmax(0, 1fr); max-width: 420px; }
@media (min-width: 560px) {
  #pricing .stat-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    max-width: 720px;
  }
}

/* ── 6b. the pricing terms are content, not fine print ────────────────
   The four items under the pricing button are the only place the billing
   mechanics are written down. app.css sets .perks at 12.5px, which is the
   right size for the hero's four-word perks and the wrong size for the
   only statement of when a card gets charged. Sized here at .98rem, one
   step under the .sect-head p body size, so the line reads as part of the
   section instead of a disclaimer under it. The color is untouched:
   --text-muted and --text-2 are the same Slate, so there was never a
   color to change, only a size.

   Scoped to #pricing. The hero's own perks line keeps app.css's 12.5px,
   because four two-word phrases at 15.7px would crowd the headline.

   LAYOUT. Longer phrases at a bigger size cannot use app.css's inline
   line at every width, so the column count is set explicitly and it is
   always a divisor of four:

     under 560px   one per row. Each phrase fits its row on a single line
                   at 320px, so no row can end on a word by itself.
     560 to 1023   two per row, two rows. Both rows are full.
     1024 and up   one inline line, which is app.css's reference design
                   and the shape the section was drawn around. The four
                   phrases measure about 890px at this size against a
                   984px measure at 1024, so the line fits before the
                   first breakpoint that uses it and only gains room
                   above it.

   The id beats app.css's .perks span:nth-child() rules on specificity, so
   the 2 + 2 grid and its justify-self pair are overridden here rather than
   fought with source order. The separators are hidden in both grid states:
   a dot at the end of a centered grid cell reads as a typo, and the gap
   does the same work. They return with the inline line, where they are
   what stops it reading as a run-on sentence. */
#pricing .perks {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  justify-content: center;
  justify-items: center;
  row-gap: 6px;
  column-gap: 30px;
  max-width: none;
  font-size: .98rem;
  line-height: 1.5;
  text-wrap: pretty;
}
#pricing .perks span { justify-self: center; }
#pricing .perks i { display: none; }

@media (min-width: 560px) {
  #pricing .perks { grid-template-columns: repeat(2, auto); }
}

@media (min-width: 1024px) {
  #pricing .perks {
    display: block;
    margin-inline: auto;
    text-align: center;
  }
  #pricing .perks i { display: inline; }
}

/* ── 7. the phone mock's stat labels ──────────────────────────────────
   Everything inside .iphone-screen is sized in em off one root that is a
   fraction of the phone's own width, so the mock cannot distort. At 390
   the phone is 240px wide, that root computes to 11.64px, and .62em put
   "Shifts", "Open" and "Hours" at 7.22px. Sharp on a 2x screen and still
   too small to read on the real phone the mock is imitating. .86em puts
   them at 10.01px, which is the same em the value above them already
   uses, so nothing new enters the mock's type scale: this reuses a size
   the card is already built around rather than inventing an eighth one.
   The value keeps the hierarchy through weight and color, which it
   already had: bold on --ink over regular on --text-muted.

   The tile absorbs it. .p-stat is padded in em off the same root, so the
   three stats grew from 63x37 to 63x41, four pixels on a 468px mock. The
   card is not near bursting, checked by opening the render. */
.p-stat span { font-size: .86em; }

/* ── 8. the hero buttons on a short landscape screen ──────────────────
   On a phone held sideways the hero becomes two columns, and the copy
   column lands at 312px. The template splits that column between the two
   buttons at 44% each, which is 151px, and the primary label needs 162px
   to hold one line, so it broke and left its last word alone in the
   button. Not one word of either label changes here: the buttons are told
   to take the width their own text needs, and to fill the column when
   there is only room for one of them per row, which is the same stacking
   the design already uses on a narrow portrait phone.

   The 880px ceiling is measured, not tidy. From 896px up the same column
   is wide enough to hold both buttons side by side at their natural
   width, and the template's proportional split already reads correctly
   there, so the override stops before it. */
@media (max-width: 880px) and (max-height: 520px) and (orientation: landscape) {
  .hero-cta .btn { flex: 1 1 auto; min-width: max-content; max-width: none; }
}

/* The short-landscape hero in app.css is its own three-row grid, and every
   cell of its second column is already "stage". An unplaced .replaces would
   therefore open an implicit fourth row and land beside the phone rather
   than under the buttons, so it is named here the same way the desktop grid
   names it.

   The 899px ceiling is what keeps this off the desktop grid. app.css's
   landscape query has no width limit, so at 932x430 it and the min-width:900
   block above both match; the block above is the one that governs there and
   already has its own "replaces" row. Without this ceiling, this rule would
   come later in the file and quietly win. */
@media (max-width: 899px) and (max-height: 520px) and (orientation: landscape) {
  .hero-in {
    grid-template-areas: "copy stage" "cta stage" "replaces stage" "perks stage";
  }
  .replaces { grid-area: replaces; margin-inline: 0; }
}

/* =====================================================================
   PLAIN-PAGE FALLBACK
   request.php renders <body class="plain-page"> with no other markup
   when it answers a non-JS POST (js !== "1"). It links this same
   stylesheet, so it must stand on its own without app.css: no wrap,
   no header, no footer, just a readable centered page on brand tokens.
   ===================================================================== */
.plain-page {
  margin: 0;
  min-height: 100vh;
  display: flex;
  align-items: center;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  line-height: 1.55;
}
.plain-page main { max-width: 46ch; margin: 0 auto; padding: 12vh 5vw; }
.plain-page h1 { font-size: clamp(1.6rem, 5vw, 2.1rem); text-wrap: balance; margin: 0 0 16px; }
.plain-page p { color: var(--text-2); text-wrap: pretty; margin: 0 0 12px; }
.plain-page li { margin-bottom: 8px; color: var(--text-2); }
.plain-page a { color: var(--accent); font-weight: 700; }


/* =====================================================================
   WARMTH AND DEPTH.

   The page was reading cold and clinical. Not because the base tone was
   wrong in kind: the cream in brand.css is #FCFAF7 and the reference
   design this brand is measured against sits at #FDFBF7, one point apart.
   Two things were actually wrong.

   First, that cream is only 3, 5 and 8 points off pure white. On a screen
   it is white. The reference gets away with it because it never shows that
   value flat: it is always under a gradient, a photograph or a glow.

   Second, and larger: every surface here was a FLAT fill. Page flat, band
   flat, card flat, and the card was painted the SAME value as the page
   behind it, so a card was not an object sitting on a page, it was an
   outline drawn on one. A page with no light in it reads as a form, not as
   a home.

   What this block does, in order of how much it contributes:
     1. Deepens the page to a real cream, #FAF5EC, four times further off
        white than before, and rebuilds the whole surface ladder around it
        so that cards are now LIGHTER than the page and lift off it.
     2. Puts a photograph of a family room in late afternoon light behind
        the hero, under a heavy cream scrim, which is the reference's own
        hero technique.
     3. Lays a fine film grain over the entire page, which is what turns a
        smooth gradient into something that reads as paper and daylight
        rather than as a UI panel. The reference does not do this. It is
        the answer to "add texture" that does not cost a craft-fair look.
     4. Turns every shadow from cool slate to warm umber, and every
        decorative hairline from neutral gray to warm tan.
     5. Replaces flat section fills with shallow gradients, so a seam
        between two sections reads as one surface catching light unevenly
        rather than as two panels butted together.

   ON THE ONE TECHNIQUE THAT WAS TRIED AND REMOVED.
   A first pass also carried amber radial glows, on the hero and off the
   top edge of every section, copied from the reference's ambient glow. In
   place they were invisible: washed out against a cream page in a way they
   are not against the reference's darker accent. They were also the only
   thing on the page that failed contrast, because a section's standfirst
   sits exactly where the glow was strongest. They bought nothing and cost
   the entire contrast budget, so every one of them is gone and that budget
   was spent deepening the base instead, where it shows.

   CONTRAST. Every value below was measured before it shipped. The floor is
   4.5:1 against Slate (--text-2 and --text-muted, both #5A6B75), the
   lightest text on the page and therefore the binding constraint on all of
   it. Measured, worst first:
     band bottom       #F1EAD9   4.61:1
     hero, body copy over the darkest pixel of the photograph
                       #F0EADF   4.62:1   (scrim floor .90, which is why
                                           the scrim does not go lower)
     band top          #F4EDDD   4.74:1
     plain section top #F7F1E3   4.91:1
     page base         #FAF5EC   5.10:1
     card face bottom  #FBF6ED   5.14:1
   Berry, the eyebrow color, is the second constraint and clears at 4.67:1
   on the deepest surface it ever touches.

   All brand-side. app.css and app.js stay byte identical.
   ===================================================================== */

/* ── 1. the warm surface ladder ───────────────────────────────────────
   --bg is the page. #FCFAF7 to #FAF5EC: still unmistakably a light page,
   now unmistakably a cream one rather than a white one with a rumour of
   cream in it.

   --card is raised to a warm near-white. The template paints most cards
   var(--bg), not var(--card), which is the bug that made cards invisible;
   section 5 below fixes that at the card rules themselves. --card is set
   here anyway so anything that does read it lands in the right place.

   --line is the hairline around cards. It was #E4E0DA, a neutral warm-gray
   only 1.26:1 against the page, so card edges dissolved. #E3DACB is the
   same lightness with the blue channel pulled down: a warm edge, and the
   boundary lifts to 1.33:1. Safe to move because the form controls do NOT
   use it. They were already switched to --text-muted higher up in this
   file, for exactly the contrast reason recorded there.

   The shadows were rgba(22,35,43), which is --ink, a cool slate blue. A
   blue-gray shadow under a cream card is a large part of what made this
   page feel clinical. rgba(74,54,38) is a warm umber at the same visual
   weight, and it is the single cheapest warm move on the page. */
:root {
  --bg:        #FAF5EC;
  --card:      #FFFDF8;
  --line:      #E3DACB;
  --shadow:    0 1px 2px rgba(74, 54, 38, .07),
               0 6px 18px rgba(74, 54, 38, .09);
  --shadow-lg: 0 24px 60px -22px rgba(74, 54, 38, .30),
               0 8px 22px rgba(74, 54, 38, .10);
  /* The lit top edge is deliberately NOT folded into --shadow. --shadow is
     also worn by .sec-photo img, and an inset highlight over a photograph
     draws a white hairline across the top of the picture. It is added only
     by the card rules in section 5, where there is a card face for it to
     catch on. */
  --lip:       inset 0 1px 0 rgba(255, 255, 255, .70);
}

/* --bg-3 is not touched. brand.css records that a deeper warm value put
   Slate at 4.41:1 on it, under the floor, and that note still holds: it is
   the fill behind the small muted labels in the phone mock. */

/* ── 2. film grain over the whole page ────────────────────────────────
   The texture. An SVG fractalNoise turbulence, desaturated, tiled at 200px
   and held at 4% opacity. It is generated inside this stylesheet, so it
   costs no request and no bytes beyond this rule.

   position: absolute and not fixed. Body is the containing block and body
   is as tall as the document, so one absolute layer covers the whole page.
   A fixed overlay would cover only the viewport, would repaint on every
   scroll frame, and would be captured wrongly by a full-page screenshot.

   z-index 1 puts it over the cards as well as the sections, which is the
   point: grain sits on the whole photograph, not on the sky only. At 4% it
   is invisible on text and clearly present on a flat fill. pointer-events
   is none, so it never eats a click. It does not move, so there is nothing
   here for reduced motion to suppress. */
body { position: relative; }
body::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  opacity: .04;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.72' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23g)'/%3E%3C/svg%3E");
  background-size: 200px 200px;
}

/* ── 3. the hero ──────────────────────────────────────────────────────
   This replaces the two-stop gradient defined earlier in this file. Same
   element, same z-index, same mechanism, three layers instead of one.

   Top to bottom: a cream scrim, then a photograph of a family room in late
   afternoon light. The scrim runs 96% opaque on the left, where the
   headline and the body copy live, and thins to 90% on the right, where
   the phone stands. That spread is small enough that no seam shows and
   wide enough that the room reads as depth behind the device. .90 is a
   floor, not a preference: it is the point at which Slate body copy over
   the darkest pixel of the photograph still measures 4.62:1.

   The photograph is blurred and compressed hard on purpose. It is never
   looked at directly, only through the scrim, so it is 58KB and holds no
   detail that could compete with the headline. It is also the poster frame
   for the video slot at the bottom of this file, which is why it is
   declared here in CSS rather than inline in the markup. */
.hero::before {
  background-color: var(--bg);
  background-image:
    linear-gradient(102deg,
      rgba(250, 245, 236, .960) 0%,
      rgba(250, 245, 236, .950) 34%,
      rgba(249, 243, 232, .920) 68%,
      rgba(248, 241, 228, .900) 100%),
    url('../assets/photos/hero-room.jpg');
  background-size: auto, cover;
  background-position: center, 62% 46%;
  background-repeat: no-repeat;
}

/* Below the 700px format line the hero stacks, so the copy runs the full
   width and can land anywhere over the frame. The scrim goes flat and near
   opaque there and the photograph is held back to a suggestion of warmth.
   Depth is not worth a headline that has to fight a window frame behind
   it. */
@media (max-width: 699px), (min-width: 700px) and (max-width: 899px) and (max-height: 520px) {
  .hero::before {
    background-image:
      linear-gradient(176deg,
        rgba(250, 245, 236, .945) 0%,
        rgba(250, 245, 236, .960) 55%,
        rgba(250, 245, 236, .985) 100%),
      url('../assets/photos/hero-room.jpg');
    background-position: center, 58% 40%;
  }
}

/* ── 4. section rhythm, gradients instead of fills ────────────────────
   The template alternates two flat fills. Two flat fills next to each
   other read as two panels. Each band instead gets a shallow vertical
   gradient and a hairline at the seam, so the eye reads one continuous
   surface catching light unevenly, which is what a wall in a room does.

   The banded sections are "what you get" and "pricing". The unbanded ones
   get a much fainter version of the same idea, so that even the sections
   that are nominally page color are not dead flat. Both gradients resolve
   onto an opaque stop, so nothing depends on what is painted behind. */
.sect { position: relative; }

.band {
  background-image: linear-gradient(180deg, #F4EDDD 0%, #F1EAD9 100%);
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}

.sect:not(.band) {
  background-image: linear-gradient(180deg, #F7F1E3 0%, var(--bg) 64%);
}

/* The request section is brand-side and sits outside .sect, so it is
   painted to match the unbanded sections rather than left on a flat fill. */
.sec-form {
  position: relative;
  background-image: linear-gradient(180deg, #F7F1E3 0%, var(--bg) 64%);
  border-top: 1px solid var(--line);
}

/* ── 5. card faces ────────────────────────────────────────────────────
   The template paints every card var(--bg), the same value as the page
   behind it, and separates the two with a hairline only. That is the
   single biggest reason this page read flat.

   Cards are now lighter than the page and carry a shallow diagonal
   gradient, so each has a lit top-left corner and a shaded bottom-right
   one. That is the same light the warm shadow in section 1 is already
   implying, and the two together are what makes a card sit ON the page
   instead of being drawn into it. --lip adds the hairline of white along
   the top edge that a lit object actually has. */
.rail-card,
.stat,
.chip-tile,
.form-card,
.kid-del {
  background-image: linear-gradient(148deg, #FFFDF8 0%, #FDFAF3 54%, #FBF6ED 100%);
  border-color: var(--line);
}
.rail-card, .stat, .chip-tile { box-shadow: var(--shadow), var(--lip); }
.form-card { box-shadow: var(--shadow-lg), var(--lip); }

/* The reveal card in the hero is translucent over the photograph, which is
   the right treatment there. It only needs to be warm rather than white. */
.rev { background: rgba(255, 253, 248, .82); }

/* ── 6. the header ────────────────────────────────────────────────────
   The header is sticky and was painted flat var(--bg), so the first 59px
   of the page was the coldest, flattest strip on it, and it stayed on
   screen the whole way down. It now sits on a warm near-white that is
   slightly lighter than the page, which is the correct relationship for a
   bar floating above content, with a blur behind it so the page tints it
   as it scrolls past and a warm hairline instead of a gray one.

   Some engines decline to paint backdrop-filter. The background color is
   deliberately given a high alpha so the bar is opaque and correct on its
   own, and the blur is an enhancement on top rather than the thing the
   legibility depends on. */
.site-head {
  background: rgba(252, 248, 240, .88);
  -webkit-backdrop-filter: blur(12px) saturate(140%);
  backdrop-filter: blur(12px) saturate(140%);
  border-bottom: 1px solid var(--line);
}
.mobile-nav {
  background: rgba(252, 248, 240, .97);
  border-top-color: var(--line);
}

/* ── 7. the footer ────────────────────────────────────────────────────
   --ink is a cool slate blue, so the page was ending on a cold slab after
   all the warmth above it. --ink is a core brand value and is not changed.
   A single warm gradient laid over the top edge instead lets the footer
   arrive out of the cream rather than cutting against it. Every color in
   the footer is unchanged, and white on this is 13.8:1. */
.site-foot {
  background-image: linear-gradient(180deg, #23313A 0%, var(--ink) 46%);
  border-top: 1px solid rgba(232, 196, 138, .22);
}

/* ── 8. vertical space ────────────────────────────────────────────────
   "Antiseptic" is partly a color problem and partly an emptiness problem.
   Now that each section carries a gradient and a seam hairline of its own,
   the rhythm is legible at less height: the gap is doing visible work
   instead of being a blank strip.

   The measured problem was the run-up to a section heading, and the worst
   of it was the one above "How it works": the hero closes on 56px, the
   section opens on 48px and the heading block then holds another 26px
   before its first card, so 130px of the page separated the last line of
   the hero from the first line of that section. Every other seam on the
   page was 122px of the same thing.

   One proportion, applied everywhere, so no section is tightened on its
   own and the page keeps a single rhythm:

     section padding, desktop     48 -> 36   (-25%)
     section padding, phone       42 -> 32   (-24%)
     hero bottom padding          56 -> 42   (-25%)
     under a section heading      26 -> 20   (-23%)
     under a heading, phone       22 -> 17   (-23%)
     above a section's own button 22 -> 18   (-18%)

   That takes the worst seam from 130px to 98px and the ordinary ones from
   122px to 92px, without touching a font size, a line height or a card.
   36px is short of the template's own phone value in the same design, and
   at 36 the seam hairline and the gradient shift still read as a boundary
   with clear air either side. Under about 30 they stop doing so, which is
   the floor this deliberately stays well above: the goal was tighter, not
   cramped. */
@media (min-width: 900px), (min-width: 700px) and (max-width: 899px) and (min-height: 521px) {
  .sect { padding: 36px 0; }
  .sec-form { padding: 36px 0; }
  .sect-head { margin-bottom: 20px; }
  .hero { padding-bottom: 42px; }
}
@media (max-width: 699px), (min-width: 700px) and (max-width: 899px) and (max-height: 520px) {
  .sect { padding: 32px 0; }
  .sec-form { padding: 32px 0; }
  .sect-head { margin-bottom: 17px; }
}
.sect-cta { margin-top: 18px; }

/* ── 9. warmth on the things that move ────────────────────────────────
   The primary button carried the same cool slate shadow as everything
   else. Harbor is a blue, so a colored halo around it would read cold;
   the warmth comes from the shadow under it instead.

   The card lift is new. Its transition is declared here rather than
   inherited, and it is suppressed for a reader who has asked for less
   motion. The shadow is kept in that case, so the card keeps its depth
   without traveling. */
.btn-primary { box-shadow: 0 2px 10px rgba(74, 54, 38, .18); }
.btn-primary:hover { box-shadow: 0 8px 22px rgba(74, 54, 38, .24); }

.rail-card, .stat {
  transition: transform .2s ease, box-shadow .2s ease;
}
@media (hover: hover) {
  .rail-card:hover, .stat:hover {
    transform: translateY(-2px);
    box-shadow: 0 14px 34px -10px rgba(74, 54, 38, .20), var(--lip);
  }
}
@media (prefers-reduced-motion: reduce) {
  .rail-card, .stat { transition: none; }
  .rail-card:hover, .stat:hover { transform: none; }
}

/* ── 10. the photographs, settled into the page ───────────────────────
   The four photographs were already the warmest thing on the site and were
   carrying that alone, which is part of why they read as pasted on: a
   golden-hour picture with a hard edge floating on a clinical panel. They
   already take --shadow, which is now warm, so only the edge is left. A
   warm hairline at the value the cards use puts the picture and the card
   in the same material.

   .sec-photo is built by js/site.js into three of the generated sections;
   .close-photo is the fourth, in the request section this file owns.
   Shaping and aspect ratios stay where they are, further up. */
.sec-photo img, .close-photo { border: 1px solid var(--line); }

/* ── 11. text wrapping baseline ───────────────────────────────────────
   The template and the blocks above already balance most of the page by
   name. This is the safety net for anything not named: every heading
   balances, every paragraph, list item and definition avoids a last line
   with one word alone on it. The width sweep is the proof; this rule is
   only the default that makes that sweep come back clean. */
h1, h2, h3, h4, .sect-head h2, .hero-kicker { text-wrap: balance; }
p, li, dd, .meta, .perks, .form-note { text-wrap: pretty; }

/* ── 12. the hero video ───────────────────────────────────────────────
   A slow, warm b-roll loop plays behind the hero scrim. The element is
   the <video class="hero-video" autoplay muted loop playsinline> block in
   index.html, and its source is assets/video/hero-loop.mp4.

   The poster frame is the same photograph and the same crop as the CSS
   layer underneath it, so the two cannot disagree: they are the same
   file. A browser that blocks autoplay, a slow connection, and a reader
   who has asked for less motion therefore all land on that photograph
   rather than on a different design.

   The rules below are the complete styling for that element. The markup
   and these rules are a pair, so anything that removes one has to remove
   the other. */
.hero-video {
  position: absolute;
  inset: 0;
  z-index: -3;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 62% 46%;
  pointer-events: none;
}

/* With the video present, the CSS photograph layer in section 3 would sit
   on top of it and hide it, so the scrim is re-declared without that layer.
   The alpha stops are identical: the scrim is what keeps the copy legible
   and it does not get to change just because the thing behind it moved. */
.hero:has(.hero-video)::before {
  background-image:
    linear-gradient(102deg,
      rgba(250, 245, 236, .960) 0%,
      rgba(250, 245, 236, .945) 40%,
      rgba(249, 243, 232, .700) 60%,
      rgba(248, 241, 228, .380) 80%,
      rgba(248, 241, 228, .180) 100%);
  background-color: transparent;
}

/* Motion is never required. A reader who has asked for less of it keeps the
   poster frame, which is the static design, and the video is removed from
   the page rather than merely paused. Below the 700px format line it is
   dropped too: a stacked hero gets the flat near opaque scrim declared in
   section 3 above, so a video behind it would be invisible bytes on a
   mobile connection.

   From 700 up the hero is two columns and the scrim is the 102deg one,
   which runs down to .180 alpha at its right-hand end. That is the end the
   phone stands at, so the footage reads there at every width in the
   compressed band as well as above it. Checked by looking at the rendered
   hero at 720 and at 820, not by reading these rules. */
@media (prefers-reduced-motion: reduce) {
  .hero-video { display: none; }
}
@media (max-width: 699px), (min-width: 700px) and (max-width: 899px) and (max-height: 520px) {
  .hero-video { display: none; }
}

/* =====================================================================
   THE HERO DEMO: RIBBON.

   The day, or the week, runs as one horizontal band inside the phone.
   Colored stretches are shifts, meals and naps; photo beads are the
   moments a caregiver posted; and one bead is always bloomed open into a
   full photograph underneath the band. js/site.js builds the markup from
   config.demo. app.css and app.js are not edited: the template still owns
   the frame, the rail, the notification and the beam.

   THE TENSION THIS BLOCK EXISTS TO RESOLVE.
   A ribbon is a horizontal idea and the hero phone is a narrow vertical
   frame rendered small. Measured, the screen is 246x533 at 1440 and
   220x477 at 390, so a band drawn at the size it wants to be would arrive
   as an illegible sliver. Three decisions follow from that:

     1. Every size below is in em, off the one root app.css sets on
        .iphone-screen (calc(var(--pw)*.04851): 13px at 1440, 11.64px at
        390). Measured in that unit the usable screen is 16.9em wide and
        32.8em tall at BOTH widths, so the mock cannot distort and one set
        of numbers is correct everywhere. This is the template's own
        convention, not a new one.
     2. The band is given generous parts rather than accurate ones. Beads
        are 4.15em, which is 54px at 1440 and 48px at 390: large enough
        that a face reads. Segment type is .72em, close to the size .p-row
        already uses, which is the smallest type anywhere in this mock.
     3. The photograph is not IN the band. A 54px bead cannot carry the
        argument on its own, so the bloom below it takes every pixel the
        fixed rows do not, and always shows one moment full width. That is
        what makes a stranger see children rather than a chart, and it is
        what makes a FROZEN frame work: the picture, the headline and the
        caregiver line are all still there with nothing moving.

   CONTRAST. White on each segment fill, worst first: slate #5A6B75 5.06:1,
   meadow #3A7263 4.93:1, amber #8A6212 5.36:1, berry #A34A6B 5.61:1,
   harbor #1E4D6B 8.49:1. The only type over a photograph is the tag pill,
   whose scrim is 78% ink; over a blown-out white pixel, the worst case,
   that composites to #4E5860 and white on it measures 6.9:1. Body copy is
   Slate on the ribbon's own cream, lighter than the #F1EAD9 already
   measured at 4.61:1 elsewhere in this file.
   ===================================================================== */

/* The screen the template hands over. Its padding is the template's, and
   it is worth naming: padding-inline is exactly 1em of the screen root, so
   a negative 1em margin bleeds a child to the glass edge. */
.p-body:has(.rib) { gap: 0; }
.rib {
  flex: 1 1 auto; min-height: 0;
  display: flex; flex-direction: column; gap: .5em;
  margin: -3.385em -1em -4.769em;      /* out to the glass, under the chrome */
  padding: 3.385em 1em 4.769em;        /* the template's own insets, restored */
  background: linear-gradient(180deg, #FFFBF3 0%, #FAF3E7 62%, #F7EEE0 100%);
  font-variant-numeric: tabular-nums;
}

/* ---- the headline block ---------------------------------------------- */
.rib-hi {
  font-size: 1.32em; font-weight: 800; line-height: 1.14;
  letter-spacing: -.025em; color: var(--text); text-wrap: balance;
}
.rib-hi em { font-style: normal; color: var(--berry); }
.rib-sm {
  margin-top: .35em; font-size: .8em; line-height: 1.35;
  color: var(--text-2); text-wrap: pretty;
}

/* ---- the live pill ---------------------------------------------------- */
.rib-pill {
  align-self: flex-start; max-width: 100%;
  display: flex; align-items: center; gap: .5em;
  padding: .5em .85em; border-radius: 99px;
  background: var(--card); box-shadow: var(--shadow), var(--lip);
  font-size: .82em; font-weight: 700; color: var(--text);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.rib-pill .rib-num { font-weight: 800; letter-spacing: -.02em; }
.rib-dot {
  flex: 0 0 auto; width: .62em; height: .62em; border-radius: 50%;
  background: currentColor;
}
/* The dot and the counting figure are the pill's tone; the sentence around
   them stays ink, because a whole line of color reads as a warning. */
.rib-pill.rib-harbor .rib-dot, .rib-pill.rib-harbor .rib-num { color: var(--accent); }
.rib-pill.rib-meadow .rib-dot, .rib-pill.rib-meadow .rib-num { color: var(--ok); }
.rib-pill.rib-amber  .rib-dot, .rib-pill.rib-amber  .rib-num { color: var(--warn); }
.rib-pill.rib-berry  .rib-dot, .rib-pill.rib-berry  .rib-num { color: var(--berry); }

/* ---- the band --------------------------------------------------------- */
.rib-band {
  position: relative; flex: 0 0 auto;
  height: 5.5em; margin-inline: -1em; overflow: hidden;
  --rib-run: 0px;                      /* measured in js/site.js */
}
.rib-track {
  position: absolute; inset: 0 auto 0 0;
  display: flex; align-items: center; padding-inline: 1em;
  animation: ribScrub 26s ease-in-out infinite alternate;
}
/* Alternate rather than loop: the band never runs off its own end into
   empty space, and it is traveling at every moment a screen grab could be
   taken. --rib-run is the real overflow, so a short band barely moves and a
   long one shows all of itself. */
@keyframes ribScrub {
  from { transform: translateX(0); }
  to   { transform: translateX(calc(-1 * var(--rib-run))); }
}
.rib-seg {
  flex: 0 0 auto; margin-right: .34em;
  height: 2.35em; display: flex; align-items: center; padding-inline: .7em;
  border-radius: 99px; white-space: nowrap;
  font-size: .72em; font-weight: 800; letter-spacing: .01em; color: #fff;
}
.rib-seg.rib-harbor { background: var(--accent); }
.rib-seg.rib-meadow { background: var(--ok); }
.rib-seg.rib-amber  { background: var(--warn); }
.rib-seg.rib-berry  { background: var(--berry); }
.rib-seg.rib-slate  { background: var(--text-2); }
/* The one stretch the screen is about. A ring rather than a blink: it is
   still visible when everything is frozen. */
.rib-seg.rib-flag { box-shadow: 0 0 0 .17em var(--card), 0 0 0 .32em currentColor; }
.rib-bead {
  flex: 0 0 auto; margin-right: .34em; width: 4.15em; height: 4.15em;
  border-radius: 1.28em; overflow: hidden;
  border: .17em solid var(--card);
  box-shadow: 0 .3em .8em rgba(74, 54, 38, .26);
  transform: translateY(-.12em);
}
.rib-bead img { display: block; width: 100%; height: 100%; object-fit: cover; }
/* The playhead: the band moves and this stays put, which is the whole
   gesture of the paradigm. It is drawn as a caliper rather than as a line
   through the middle, because a through-line at this size cuts a segment
   label in half and reads as a rendering fault rather than as a marker.
   Both arms live in the gutters above and below the beads, so it never
   crosses a word or a face. */
.rib-play {
  position: absolute; top: 0; bottom: 0; left: 62%; width: .17em;
  z-index: 3; background: none;
}
.rib-play::before, .rib-play::after {
  content: ''; position: absolute; left: 0; width: 100%;
  border-radius: 99px; background: var(--berry);
}
.rib-play::before { top: 0; height: .95em; }
.rib-play::after  { bottom: 0; height: .7em; }
/* Both ends fade into the ribbon's own gradient rather than being cut off,
   so the band reads as continuing past the frame. The stops match the
   background at the band's own height. */
.rib-band::before, .rib-band::after {
  content: ''; position: absolute; top: 0; bottom: 0; width: 1.3em;
  z-index: 2; pointer-events: none;
}
.rib-band::before { left: 0;  background: linear-gradient(to right, #FBF5EA, rgba(251, 245, 234, 0)); }
.rib-band::after  { right: 0; background: linear-gradient(to left,  #F9F1E4, rgba(249, 241, 228, 0)); }

.rib-axis {
  display: flex; justify-content: space-between;
  font-size: .68em; font-weight: 700; letter-spacing: .04em;
  color: var(--text-2); opacity: .78;
}

/* ---- the bloom: one bead, opened ------------------------------------- */
.rib-bloom {
  flex: 1 1 auto; min-height: 0; margin: 0;
  display: flex; flex-direction: column;
  background: var(--card); border-radius: 1.1em; overflow: hidden;
  box-shadow: var(--shadow), var(--lip);
}
.rib-shot { position: relative; flex: 1 1 auto; min-height: 5.5em; }
.rib-shot img { display: block; width: 100%; height: 100%; object-fit: cover; }
.rib-tag {
  position: absolute; top: .55em; left: .55em;
  padding: .3em .62em; border-radius: 99px;
  background: rgba(22, 35, 43, .78); color: #fff;
  font-size: .66em; font-weight: 800; letter-spacing: .05em;
}
.rib-bloom figcaption { flex: 0 0 auto; padding: .6em .75em .7em; }
.rib-bloom figcaption b {
  display: block; font-size: .84em; font-weight: 700; line-height: 1.3;
  color: var(--text); text-wrap: pretty;
}
.rib-bloom figcaption span {
  display: block; margin-top: .3em; font-size: .68em; color: var(--text-2);
}
@keyframes ribPop {
  from { opacity: 0; transform: translateY(.9em) scale(.965); }
  60%  { opacity: 1; }
  to   { opacity: 1; transform: none; }
}
.rib-bloom.pop { animation: ribPop .95s cubic-bezier(.16, 1.1, .3, 1); }

/* ---- who is on shift -------------------------------------------------- */
.rib-who {
  flex: 0 0 auto; display: flex; align-items: center; gap: .55em;
  padding: .55em .7em; border-radius: 1.1em;
  background: var(--card); box-shadow: var(--shadow), var(--lip);
}
.rib-av {
  flex: 0 0 auto; width: 2.3em; height: 2.3em; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: linear-gradient(140deg, var(--accent-lt), var(--accent-dk));
  color: #fff; font-size: .8em; font-weight: 800; letter-spacing: .02em;
}
.rib-whom { flex: 1 1 auto; min-width: 0; }
.rib-whom b {
  display: block; font-size: .82em; font-weight: 800; color: var(--text);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.rib-whom > span {
  display: block; font-size: .68em; color: var(--text-2);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.rib-badge {
  flex: 0 0 auto; padding: .38em .62em; border-radius: 99px;
  font-size: .64em; font-weight: 800; letter-spacing: .06em;
  text-transform: uppercase; white-space: nowrap;
}
.rib-badge.rib-ok { background: var(--ok-bg); color: var(--ok); }

/* ---- frozen ----------------------------------------------------------
   Everything that moves is switched off, and nothing that carries meaning
   moves on its own. The band rests at its start, where a photo bead is
   already in frame; the flagged stretch keeps its ring; the bloom keeps
   its photograph; the counting figures keep the value they were built
   with. js/site.js stops the clock and the rotation on the same query, so
   the two never disagree. */
@media (prefers-reduced-motion: reduce) {
  .rib-track { animation: none; }
  .rib-bloom.pop { animation: none; }
}

/* ---- the notification, re-anchored ------------------------------------
   app.css pins .note a fixed distance from the TOP of the frame, which was
   correct against the template's text screens. Against the ribbon it landed
   exactly on the bloom's caption and hid it.

   Every placement inside a 32.8em screen covers SOMETHING, so the question
   is only what, and it is answered by where the people are. Measured across
   all eight photographs, faces sit in the upper third of the frame in six of
   them, so a card at the TOP of the picture is the worst possible place for
   it and a card at the BOTTOM is the best. Anchoring to the bottom of the
   device rather than the top also lets the card grow upward when a
   scenario's copy runs to an extra line, so no length of note can reach the
   caption underneath. The photograph's lower edge is .692 of --pw off the
   bottom of the frame at every width, because the whole mock is sized off
   that one number, and .707 leaves the caption clear of it.

   The other half of this is config.demo.photos[n].pos, which gives every
   picture its own focal point so the subject is composed into the clear
   half above the card rather than left where the crop happened to put it.

   Only the anchor changes here. The desktop left-pointing tail higher up in
   this file is untouched and still points at the scenario tiles, and the
   mobile beam is measured from the card's live rect by app.js, so it follows
   this card wherever it is put.

   This rule is now the FALLBACK, not the placement. js/site.js writes the
   card's real position as inline left/top/width from the screen's own
   note:{x,y,w}, which wins over everything here. What is left below still
   matters: it is where the card sits for the instant before the first
   measurement, and wherever the ribbon is not on screen. */
.note { top: auto; bottom: calc(var(--pw) * .707); }

/* ---- the screen's own status chips -----------------------------------
   One short line per room on the house tour, and one name per step of the
   backup cascade. Deliberately a single non-wrapping row: three chips that
   were allowed to wrap would strand the third alone on a second line at a
   narrow width, which is the orphan rule this project holds everywhere
   else. They shrink and clip at the frame edge instead, the same way the
   band already does, and the row is scrolled by nothing so a screen grab
   always shows the same thing. */
.rib-chips {
  flex: 0 0 auto; display: flex; gap: .34em;
  overflow: hidden; white-space: nowrap;
}
.rib-chip {
  flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis;
  padding: .42em .7em; border-radius: 99px;
  font-size: .68em; font-weight: 800; letter-spacing: .02em; color: #fff;
}
.rib-chip.rib-harbor { background: var(--accent); }
.rib-chip.rib-meadow { background: var(--ok); }
.rib-chip.rib-amber  { background: var(--warn); }
.rib-chip.rib-berry  { background: var(--berry); }
.rib-chip.rib-slate  { background: var(--text-2); }

/* ---- the arrow -------------------------------------------------------
   One SVG per phone, a SIBLING of the notification card inside .iphone.
   Never a child of the card: the template empties the card on every
   rotation, so anything inside it is destroyed four seconds later.

   Its viewBox is set to the phone box's live pixel size, so one SVG unit
   is one CSS pixel and the arrowhead cannot be stretched by the frame's
   aspect ratio. Every coordinate in the path is written by js/site.js from
   measured rects.

   Two strokes on one path, not a line plus a separate head. The lower one
   is a cream halo, which is what lets the same arrow stay legible whether
   it is crossing the ribbon's pale background or the dark corner of a
   photograph. Contrast, worst case: ink #16232B on the palest cream in the
   ribbon gradient, 14.9:1; the cream halo against the darkest pixel of the
   photographs measures better than 9:1. Nothing here animates, so a frozen
   frame under reduced motion is the same drawing. */
.rib-ptr {
  position: absolute; inset: 0; width: 100%; height: 100%;
  z-index: 8; pointer-events: none; overflow: visible;
}
.rib-ptr path { fill: none; stroke-linecap: round; stroke-linejoin: round; }
.rib-ptr .ptr-halo {
  stroke: rgba(255, 251, 243, .96);
  stroke-width: calc(var(--ptr-w, 2px) + 3.2px);
}
.rib-ptr .ptr-line { stroke: var(--ink); stroke-width: var(--ptr-w, 2px); }

/* ---- the thing the arrow lands on ------------------------------------
   An arrow alone still asks the eye to guess where the line stops. The
   marked element wears the same ring the band's flagged segment already
   uses, so the answer is legible even in a thumbnail, and the pairing is
   unmistakable: one ring per screen, one arrow into it. The ring is drawn
   outside the box by box-shadow, so nothing reflows and no target changes
   size when it becomes the target. The caption is a block rather than a
   pill, so it gets the radius it lacks. */
.rib [data-point] {
  box-shadow: 0 0 0 .17em var(--card), 0 0 0 .32em var(--ink);
}
.rib-bloom figcaption[data-point] {
  margin: 0 .5em .5em; border-radius: .6em;
}

/* The template's card is 94 to 97 percent opaque, which is invisible over
   the flat panels it was drawn for and very visible here: on the ribbon it
   sits over type and photographs, and a caption ghosting through the card
   above it reads as a rendering fault rather than as depth. Same two
   colors as the template, same angle, no alpha. Literal values because
   these two are the template's own card colors and have no token in
   brand.css; nothing else in this file reuses them. Ribbon only, so the
   template's default screens keep the glass. */
.iphone:has(.rib) .note {
  background: linear-gradient(150deg, #243349, #16202E);
}

/* The template's own tail is a rotated square on the card's edge that
   points sideways at the scenario rail. Against the ribbon that is a
   SECOND arrow pointing at a DIFFERENT target, three inches from the one
   above, and the two contradict each other. Switched off only where the
   ribbon is present, so the tail keeps governing the template's default
   screen types. app.css is not edited. */
.iphone:has(.rib) .note::after { content: none; }


/* ===== footer link grid ==============================================
   The footer carries eight links now, four navigation and four policy.
   The four it held before were chosen because they sit on a single row at
   every width; eight cannot do that. Measured across the 88 widths this
   project checks, eight links with no grid strand "Accessibility" alone on
   the last row, at 4+3+1 below 560px and 7+1 above it.

   Two columns, then four from 560px. Eight divides by both, so the last row
   is never left holding one link at any width. The separator character is
   hidden because the grid already separates the items and the dot lands in
   the wrong place at the end of a row.

   This adds width only. The 44x44 minimum box on .foot-links a set earlier
   in this file is untouched and still applies. app.css is not edited. */
.foot-links {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0 12px;
  max-width: 26rem;
  margin: 16px auto 12px;
  justify-content: center;
}
@media (min-width: 560px) {
  .foot-links { grid-template-columns: repeat(4, minmax(0, 1fr)); max-width: 46rem; }
}
.foot-links span { display: block; min-width: 0; }
.foot-links a { width: 100%; }
.foot-links i { display: none; }

/* ===== skip link, visible on keyboard focus ==========================
   .sr hides the "skip to content" link in a clipped 1px box, and nothing
   restored it on focus, so a sighted person navigating by keyboard landed
   on a control they could not see. Screen readers were always fine: this is
   specifically the visible focus indicator WCAG AA asks for.

   It lives here rather than in css/legal.css because the same link is on
   every page including the homepage, and fixing only the policy pages would
   leave the rest of the site failing a standard the accessibility statement
   states publicly for the whole site.

   Overriding .sr from this file rather than editing where it is defined
   keeps app.css untouched. This wins on specificity, 0-2-0 against 0-1-0,
   so it applies whatever the load order. The clip-path reset is the right
   one: .sr hides with clip-path, not the legacy clip property, so resetting
   clip-path is what actually reveals it.

   Scoped to :focus-visible as well as :focus so a mouse click does not
   flash the link into view. */
.sr:focus,
.sr:focus-visible {
  position: fixed;
  top: 12px;
  left: 12px;
  width: auto;
  height: auto;
  padding: 12px 18px;
  clip-path: none;
  overflow: visible;
  z-index: 1000;
  background: var(--bg, #FAF8F5);
  color: var(--text, #1B1B1B);
  border: 2px solid var(--accent, #1B1B1B);
  border-radius: 8px;
  font-weight: 700;
  text-decoration: none;
}

/* =====================================================================
   13. THE COMPRESSED DESKTOP BAND.

   The owner's complaint was that a plainly desktop-sized window drops to
   the phone layout far too early and then wastes most of its width. The
   answer here is not a third layout. It is the SAME desktop format,
   reaching further down and drawn smaller to fit: side by side stays side
   by side, the menu stays a menu, and only sizes come down.

   Two ranges, because two different subsystems were breaking at two
   different widths, and only one of them was broken everywhere:

     700 to 899   the hero and the menu. app.css hands them their desktop
                  form at 900 and their stacked form at 899, so this is the
                  only range where they need anything. This block SIZES
                  them; the layout itself comes from the blocks above, all
                  of which now reach down to 700.
     700 to 999   the sections that carry a side photograph. Those were
                  stacking until 1000, which is why 900 to 999 rendered a
                  desktop header and hero over mobile-stacked sections.

   Every rule below is confined by BOTH edges of its range. Nothing here is
   left to be canceled by a rule at a higher width, because that is
   exactly how a change escapes upward into widths that were already
   right: at 900 the phone is already at its full 268px, and at 1000 and
   above every measured box is what it was.

   Each clamp() is a straight line through two measured ends: the narrow
   one at 700, and the value the page already renders at the top of that
   rule's range. So the numbers meet at the seam rather than stepping over
   it. Verified by measurement at 899 against 900, and at 999 against 1000.

   The min-height guard is what keeps a phone held sideways out of this.
   app.css has its own short-landscape hero, a three-row grid with its own
   phone width, and 844x390 and 932x430 are swept viewports that already
   pass on it. 521px is the first height above that layout's own 520px
   ceiling, so both of them land exactly where they landed before.
   ===================================================================== */

/* ---- 13a. the hero and the menu, 700 to 899 only --------------------
   Capped at 899 on purpose and not left open-ended. From 900 up the hero
   is already finished: app.css stops overriding --pw at 899, so the phone
   is at its full 268px from 900, and every figure in this block would only
   have made a correct hero smaller.
   -------------------------------------------------------------------- */
@media (min-width: 700px) and (max-width: 899px) and (min-height: 521px) {

  /* ---- header: a menu, never a toggle -------------------------------
     Measured at 700 wide, inside a 660px row: the brand 134px, two head-in
     gaps 20px, four links and three gaps 342px, the button 144px. That is
     640px in a 660px row. The links are held on one line so a shrinking
     flex row can never break a label in half, and the 44px minimum box on
     .nav a in section 4 is not one of the figures that shrinks. Each
     figure runs from its 700px value to the app.css value at 900. */
  .nav { display: flex; gap: clamp(12px, 7vw - 37px, 26px); }
  .nav a {
    font-size: clamp(12.8px, 1.2vw + 4.4px, 15.2px);
    white-space: nowrap;
  }
  .head-cta {
    display: inline-flex;
    font-size: clamp(13.76px, 1.12vw + 5.92px, 16px);
    padding-inline: clamp(14px, 4vw - 14px, 22px);
  }
  .nav-toggle, .mobile-nav { display: none; }
  .head-in { gap: clamp(10px, 2vw - 4px, 14px); }

  /* ---- hero: the phone gives way first ------------------------------
     At the template's 268px the phone leaves a 352px copy column at 700,
     which is too narrow for the headline and pushes the chip labels out
     of their own tiles. 185px leaves 435px, which holds the headline on
     two lines and the chips on their row. The line runs 185px at 700 to
     268px at 900, which is the value app.css already renders from 900 up,
     so the phone does not change size at the seam. */
  :root { --pw: clamp(185px, 41.5vw - 105.5px, 268px); }
  .hero { padding-top: clamp(28px, 8vw - 28px, 44px); }
  .hero-in { display: grid; align-items: center; text-align: left; }
  .hero-block { grid-area: copy; min-width: 0; }
  /* app.css declares this expression only from 900 up, and below that the
     headline falls back to a phone scale that tops out at 2.1rem. Restated
     rather than re-tuned: 4.2vw is under 2.1rem below 800, so the two
     agree, and from 800 to 899 it is the same line the page already draws
     at 900. */
  .hero-block h1 { font-size: clamp(2.1rem, 4.2vw, 2.92rem); }
  /* app.css drops the sub-headline under 640px of height so both hero
     buttons clear the fold on a phone. A two-column hero is taller than a
     fold at any height, so that trade buys nothing here and the sentence
     stays. */
  .hero-sub {
    display: block; margin-inline: 0;
    font-size: 1.06rem; max-width: 46ch;
  }
  .hero-cta { grid-area: cta; justify-content: flex-start; }
  .hero-cta .btn { flex: 0 0 auto; }
  .perks { grid-area: perks; margin-inline: 0; text-align: left; }
  /* app.css turns the beam back on at 899 and below. .stage is
     display:contents from 700 up, so a visible .beam would become an
     unplaced grid item of .hero-in and open a row of its own. */
  .beam { display: none; }

  /* ---- the scenario chips -------------------------------------------
     Six chips two columns wide in a six-column grid place three to a row
     without a single cell being named. The card styling is set in the
     desktop hero block above; only sizes are here, and each of them runs
     to the app.css value at 900.

     Labels wrap and are balanced when they do. "Someone calls out" needs
     172px on one line and the tile is 140px wide at 700, so the
     template's nowrap would run the label straight out of its own tile.
     All six chips share a grid row, so a chip that takes two lines takes
     the row's height with it and the row stays even.

     The side padding stops at 12px rather than the 13px used from 900 up.
     At the top of the band the longest label lands within a fraction of a
     pixel of its tile's inner width, and at 13px it tips over and takes a
     second line for the last few pixels before 900, which drops the whole
     row twenty pixels the instant the window is nudged. The extra pixel
     each side settles it, and a pixel of padding is not something the eye
     can find at the seam. */
  .rail { display: grid; grid-template-columns: repeat(6, 1fr); gap: 7px; }
  .chip {
    grid-column: span 2;
    width: auto; height: auto;
    font-size: clamp(12.16px, .8vw + 6.56px, 13.76px);
    padding: clamp(8px, 1vw + 1px, 10px) clamp(10px, 1.5vw - .5px, 12px);
    gap: clamp(7px, 1vw, 9px);
  }
  .chip svg { width: 18px; height: 18px; }
  .chip-label {
    display: block; min-width: 0;
    white-space: normal; text-wrap: balance;
  }
}

/* ---- 13b. the sections that carry a photograph, 700 to 999 ----------
   This range is the one the sweep found arguing with itself. From 900 to
   999 the header and the hero were already the desktop ones while the
   tiles and the timeline below them were still stacked with their pictures
   dropped underneath, so the top of the page and the middle of it read as
   two different sites. Both halves are the desktop arrangement here.

   Capped at 999 because 1000 is where the half-and-half rule for the tiles
   already lives, so this ends exactly where the existing rule begins and
   no new edge is invented.
   -------------------------------------------------------------------- */
@media (min-width: 700px) and (max-width: 999px) and (min-height: 521px) {

  /* The 260px floor on the photograph's track was written for widths where
     34% of the row is the wider of the two, which is true from about 805
     up. Below that a flat floor would push the picture past a third of the
     row exactly where the row has least to give: 34% is 224px at 700, and
     a 260px floor would take 36px out of the text beside it. The floor is
     dropped across this range and the share stays 34% throughout, which is
     the same share every side photograph takes at every width above it. */
  .sect.photo-side .wrap {
    grid-template-columns: minmax(0, 1fr) minmax(0, 34%);
  }

  /* The tiles section is the one place a photograph's share of the row
     moves. From 1000 up it takes half, which is what four rows of two
     tiles need beside them. Half of a 660px row leaves 314px of content,
     and two tiles in 314px put "Announcements" past the edge of its own
     tile, so here the share is a line and not a step: 224px at 700, which
     is the same 34% every other side photograph takes, up to 460px at
     1000, which is what the half-and-half rule already resolves to
     there. */
  #what.photo-side .wrap {
    grid-template-columns:
      minmax(0, 1fr) minmax(0, clamp(224px, 78.67vw - 326.7px, 460px));
  }

  /* Two across, for the same reason it is two across from 1000 up: the
     tiles share the row with the photograph here too, and four rows of two
     is the content height the picture needs to sit beside. The template
     centers this grid with margin:0 auto and caps it at 900px, and auto
     margins on a stretched grid item switch it to fit-content, which is
     what left the tiles short of their own column with a hole between them
     and the photograph. Zeroing the inline margin restores the stretch. */
  #what .chip-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    max-width: none; margin-inline: 0;
  }
}
