/* ---------------------------------------------------------------------------
   app.css — behaviour layer for the Casa replica.

   assets/css/casa.css is Casa's own compiled stylesheet (their Tailwind build
   plus CSS modules) and carries the entire design system. Nothing in there is
   modified.

   This file only re-creates the handful of states that the real site applies
   from JavaScript at runtime, so the static copy behaves identically.

   NOTE: every pixel length written in this file is re-declared, scaled, by
   assets/css/scale.css. Change one here and re-run
   `npx tsx scripts/landing-v3-scale.ts`, or the generated override keeps
   serving the old number.

   AND EVERY PALETTE COLOUR HERE IS WRITTEN AS `var(--casa-<token>, <literal>)`,
   not as the literal. The hex is Casa's original, kept as the fallback so this
   file still says which token it meant; the value that paints comes from
   theme-bridge.css's `:root`, which is Casa's palette with the brand override in
   `scripts/landing-v3-brand.ts` applied. A literal written bare here is a
   section that stays behind when the brand moves — which is how the page ends up
   in two colour schemes at once, most of it retinted and the hand-written
   sections still olive. Alpha variants use `color-mix(… , transparent)` for the
   same reason, and the two `:hover` lifts are mixed FROM the token rather than
   picked beside it.
   --------------------------------------------------------------------------- */

/* The page's size ---------------------------------------------------------
   Casa's build has `--spacing: 1px`, so every size on this page — type,
   gutters, card padding, icon boxes, max-widths, radii — is a raw pixel
   literal in a utility class. There is no base unit to turn down, and the
   usual shortcuts do not survive contact with this layout: `zoom` scales the
   content but not the viewport it is measured against, so the page renders
   short of the right edge; `transform: scale()` leaves the taller page's
   scroll height behind.

   So scale.css re-declares every length-bearing rule in casa.css and app.css
   with its pixel literals multiplied by this number. Viewport units,
   breakpoints and border widths are deliberately left out of it — the hero
   stays exactly one screen tall, three columns still arrive at 800px, and a
   hairline stays a hairline.

   Three values rather than one because the crowding is not the same at every
   width: a phone is short of room and loses least by shrinking, a large
   desktop had the most air to begin with. Turn these; nothing else. */
:root { --v3-scale: .94; }
@media (min-width: 800px)  { :root { --v3-scale: .92; } }
@media (min-width: 1200px) { :root { --v3-scale: .90; } }

/* Autopilot carousel ------------------------------------------------------ */
.scaling-carousel-container { touch-action: pan-y; }
.scaling-carousel-container.is-snapping { transition: transform 600ms cubic-bezier(0.25, 1, 0.5, 1); }
.carousel-item.is-snapping { transition: transform 600ms cubic-bezier(0.25, 1, 0.5, 1); }
.scaling-carousel__slide.is-snapping { transition: transform 600ms cubic-bezier(0.25, 1, 0.5, 1); }

/* Pagination bullets: the active bullet widens from 10px to 60px.
   Casa drives the fill width from rAF; the width swap is a class transition. */
.pagination-bullet { transition: width 450ms cubic-bezier(0.25, 1, 0.5, 1); }

/* Rotating text ("Casa can you …") ---------------------------------------- */
.rotating-text-track { transition: transform 650ms cubic-bezier(0.25, 1, 0.5, 1); }

/* Home Inventory tabs ------------------------------------------------------ */
.tab-panel-media { transition: opacity 650ms cubic-bezier(0.25, 1, 0.5, 1); }
.tab-panel-text  { transition: opacity 650ms cubic-bezier(0.33, 1, 0.68, 1); }
/* The underline itself is Casa's own node; only the FLIP transition is added,
   and that is applied inline by app.js. */

/* FAQ accordion ------------------------------------------------------------ */
.faq-panel { transition: height 450ms cubic-bezier(0.25, 1, 0.5, 1), opacity 450ms cubic-bezier(0.25, 1, 0.5, 1); }
.faq-icon  { transition: transform 450ms cubic-bezier(0.25, 1, 0.5, 1); }
.faq-panel a { text-decoration: underline; text-underline-offset: 2px; }

/* Header dropdown ---------------------------------------------------------- */
.header-dropdown { transition: opacity 275ms cubic-bezier(0.25, 1, 0.5, 1), clip-path 275ms cubic-bezier(0.25, 1, 0.5, 1); }

/* Footer logo photo fill --------------------------------------------------- */
.footer-logo-fill { transition: opacity 900ms cubic-bezier(0.25, 1, 0.5, 1); }

/* Lead sorter -------------------------------------------------------------
   The stacked cards in "Every draft, held for you" used to be one flat webp of
   Casa's own maintenance tasks. They are real elements now so the pile can
   actually sort itself — which is the claim the section is making.

   Geometry and state only. Every colour and radius below is a casa.css token
   (off-white-01/02/04, green-02/04, rounded-24), so the cards read as the same
   furniture as the benefit cards further down rather than as a widget that
   wandered in. The two states differ by TRANSFORM alone — same five nodes,
   never two drawings that have to be kept in step.

   The card type scales with the CARD, not the viewport: the box is a fixed
   ratio whatever the column does, so container units are the only honest
   choice.

   ONE RHYTHM, and it is the fix for what shipped before it. The ranked slots
   used to be five hand-written `--sy` values in the markup while the card's
   height came from its own contents — so the two could disagree, and they did:
   the slot pitch was 13.2cqh and the card was 16.8cqh, which put every card a
   fifth of its height under the next one. What that looks like is not "a
   pile": it is five rows with their bottom corners sliced off, no gap between
   them, and the last one tucked under the button. The card's height is now
   declared, both states are derived from it, and a slot too small for its card
   is no longer expressible. */
[data-sorter] {
  container-type: size;
  /* The box owns its own ratio rather than carrying `aspect-5/6` in the markup,
     because it is not one ratio. On a phone the box is as wide as the screen
     and portrait is the only way five rows fit; beside a column of copy it was
     626px of box against 380px of text, which is the section reading tall for
     no reason. Square there. */
  aspect-ratio: 5 / 6;
  /* The three numbers the ranked state is made of. */
  --row: 11.4cqh;
  --row-gap: 2.2cqh;
  --stack-top: 16.5cqh;
  /* …and the two the pile is. Tighter pitch, so it reads as a heap of paper
     rather than a list that has been nudged. */
  --pile-top: 28cqh;
  --pile-pitch: 7.5cqh;
}
@media (min-width: 1000px) { [data-sorter] { aspect-ratio: 1 / 1; } }
.sorter-stack { position: absolute; inset: 0; z-index: 1; }

.sorter-card {
  position: absolute;
  left: 7cqw; right: 7cqw;
  top: 0;
  height: var(--row);
  display: flex; align-items: center; gap: 3.5cqw;
  padding: 0 4cqw;
  background: #fff;
  /* Proportional, because the card is. A fixed 24px corner is Casa's rounded-24
     on a 105px card and a pill on the 46px one the same box produces in a
     narrow column — the radius has to know how tall the thing is. */
  border-radius: calc(var(--row) * .23);
  box-shadow: 0 1px 2px rgba(0,0,0,.04), 0 12px 30px rgba(0,0,0,.08);
  /* Both states, from --i alone. --my is still measured FROM the ranked slot,
     so the pile follows the list rather than being kept in step by hand. */
  --sy: calc(var(--stack-top) + var(--i) * (var(--row) + var(--row-gap)));
  --my: calc(var(--pile-top) + var(--i) * var(--pile-pitch) - var(--sy));
  transform: translate(var(--mx), calc(var(--sy) + var(--my))) rotate(var(--mr));
  transition: transform 760ms cubic-bezier(.22,.61,.36,1), opacity 420ms ease;
  transition-delay: calc(var(--i) * 70ms);
  will-change: transform;
}
/* Later cards sit on top in the pile and underneath once ranked, so the stack
   reads as a pile one way and a list the other. */
.sorter-card:nth-child(1) { z-index: 5 } .sorter-card:nth-child(2) { z-index: 4 }
.sorter-card:nth-child(3) { z-index: 3 } .sorter-card:nth-child(4) { z-index: 2 }
.sorter-card:nth-child(5) { z-index: 1 }

[data-sorter][data-sorted="true"] .sorter-card { transform: translate(0, var(--sy)) rotate(0deg); }
/* Not a rejection — it is the one the product declines to text, and saying so
   quietly is the point. */
[data-sorter][data-sorted="true"] .sorter-card[data-skip] { opacity: .45; }

/* Sized off the ROW, not the box width — the row is the thing it has to sit
   inside, and keying it to cqw is how it used to grow past its own card. */
.sorter-card__av {
  height: 62%; width: auto; aspect-ratio: 1; flex: none;
  border-radius: 24%;                       /* the same corner as the card */
  display: grid; place-items: center;
  font-size: clamp(10px, 2.6cqw, 14px); font-weight: 500; letter-spacing: 0;
}
.sorter-card__who { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
/* text-label-medium / text-body-small, at the scale the card can carry. */
.sorter-card__who b {
  font-size: clamp(13px, 3.1cqw, 17px); font-weight: 500;
  line-height: 125%; letter-spacing: 0;
}
.sorter-card__who i {
  font-style: normal; font-size: clamp(10px, 2.3cqw, 13px);
  line-height: 140%; letter-spacing: .02em; color: #8e8f84;   /* off-white-04 */
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* The rank only exists after he has ranked them. */
.sorter-card__rank {
  flex: none; height: 38%; width: auto; aspect-ratio: 1;
  border-radius: 999px; display: grid; place-items: center;
  background: var(--casa-green-02, #f0f3a2); color: var(--casa-green-04, #444835);      /* green-02 on green-04 */
  font-size: clamp(10px, 2.3cqw, 13px); font-weight: 500;
  opacity: 0; transform: scale(.5);
  transition: opacity 300ms ease, transform 300ms cubic-bezier(.22,.61,.36,1);
  transition-delay: calc(340ms + var(--i) * 70ms);
}
[data-sorter][data-sorted="true"] .sorter-card__rank { opacity: 1; transform: none; }
[data-sorter][data-sorted="true"] .sorter-card[data-skip] .sorter-card__rank {
  background: #ebebe4; color: #8e8f84;      /* off-white-02 / -04 */
}

/* The count, in the eyebrow chip the rest of the page uses. */
.sorter-pill {
  position: absolute; left: 50%; top: 5cqh; transform: translateX(-50%);
  z-index: 6; display: inline-flex; align-items: center; gap: 8px;
  padding: 10px 16px; border-radius: 6px;   /* rounded-6, like .text-eyebrow chips */
  background: var(--casa-green-01, #fbfbe3); color: var(--casa-green-04, #444835);      /* green-01 / green-04 */
  font-size: 12px; font-weight: 500; letter-spacing: .02em; text-transform: uppercase;
  white-space: nowrap;
}
.sorter-pill__dot { width: 6px; height: 6px; border-radius: 999px; background: currentColor; opacity: .5; }

/* Always visible, and it is the only control in the box — the state it puts you
   in is the whole point of the section, so hiding it after one press would make
   the pile a thing you saw once. It toggles instead. */
.sorter-go {
  position: absolute; left: 50%; bottom: 5cqh; transform: translateX(-50%);
  z-index: 6; height: 48px; padding: 0 24px;
  display: inline-flex; align-items: center; gap: 9px;
  border: 0; border-radius: 999px;
  background: var(--casa-green-04, #444835); color: #f8f8f1;      /* green-04 / off-white-01 */
  font: inherit; font-size: 14px; font-weight: 500; letter-spacing: .02em;
  cursor: pointer; white-space: nowrap;
  transition: background-color 275ms cubic-bezier(.25,1,.5,1), color 275ms cubic-bezier(.25,1,.5,1);
}
.sorter-go:hover { background: color-mix(in oklab, var(--casa-green-04, #444835) 88%, var(--casa-off-white-01, #f8f8f1)); }
.sorter-go__ico { transition: transform 620ms cubic-bezier(.22,.61,.36,1); }
/* Once ranked it becomes the way back, and looks like the page's outline button. */
[data-sorter][data-sorted="true"] .sorter-go {
  background: #ebebe4; color: var(--casa-green-04, #444835);      /* off-white-02 */
}
[data-sorter][data-sorted="true"] .sorter-go:hover { background: #c7c7bd; }
[data-sorter][data-sorted="true"] .sorter-go__ico { transform: rotate(-180deg); }

/* Somebody who cannot see the motion still gets both states, just instantly. */
@media (prefers-reduced-motion: reduce) {
  .sorter-card, .sorter-card__rank, .sorter-go__ico { transition-duration: 1ms; transition-delay: 0ms; }
}

/* Hero thread -------------------------------------------------------------
   A three-beat exchange under the headline, built out of the .im-* kit above
   so it is a real iMessage bubble rather than a brand-coloured lozenge — same
   17pt on 21.5pt, same 18pt corner, same tail. It sits on a video, which is
   exactly why the tail had to become a mask.

   Pure CSS. A JS loop would be three lines of state for something with none,
   and it would need tearing down when the hero leaves the viewport. */
.hero-thread {
  display: flex; flex-direction: column; gap: 3px;
  width: min(360px, 82vw); margin-top: 6px;
}
.hero-thread .im-row { animation: heroBeat 11s cubic-bezier(.22,.61,.36,1) infinite; opacity: 0; }
.hero-thread .im-row { animation-delay: calc(var(--n) * 1.35s); }
.hero-thread .im-row.turn { margin-top: 7px; }
/* Slightly translucent so the painting behind still reads, without giving up
   the grey — a fully opaque chip on a hero looks pasted on. */
.hero-thread .im-b--in { background-color: rgba(233,233,235,.94); }

/* Appear, hold most of the cycle, leave together. The hold is what makes it
   readable — a bubble that fades the moment it lands is decoration. */
@keyframes heroBeat {
  0%   { opacity: 0; transform: translateY(9px) scale(.94); }
  6%   { opacity: 1; transform: none; }
  78%  { opacity: 1; transform: none; }
  86%  { opacity: 0; transform: translateY(-5px) scale(.98); }
  100% { opacity: 0; transform: translateY(9px) scale(.94); }
}
@media (prefers-reduced-motion: reduce) {
  .hero-thread .im-row { animation: none; opacity: 1; }
}
/* The hero is tight on a short laptop; the motif is the first thing to go. */
@media (max-height: 700px), (max-width: 480px) { .hero-thread { display: none; } }

/* iMessage kit ------------------------------------------------------------
   One implementation of the bubble, used by the hero motif and the copilot
   thread. Everything here is Apple's geometry rather than this brand's — 17pt
   body on 21.5pt, an 18pt corner, a 262pt max width — because a bubble that
   splits the difference with the design system reads as neither.

   THE TAIL IS A MASK, and that is the load-bearing part. The usual CSS trick
   draws it as a coloured square with one rounded corner plus a
   BACKGROUND-COLOURED square notched over its outer half — which needs to know
   what is behind it, so it cannot go on a video, a photograph or a gradient.
   The same silhouette as an SVG <mask> — white outer, black cutout, true
   subtraction — composites over anything. (evenodd is NOT a substitute: it is
   XOR, so the cutout's overhang survives as a sliver down the bubble's side.) */
.im-b {
  position: relative;
  max-width: 262px;
  padding: 8.5px 13.5px 9.5px;
  border-radius: 18px;
  font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", Arial, sans-serif;
  font-size: 17px; line-height: 21.5px; letter-spacing: -.022em;
  text-align: left; word-break: break-word;
  -webkit-font-smoothing: antialiased;
}
.im-b::after {
  content: ""; position: absolute; bottom: 0;
  width: 16px; height: 15px;
  background-color: inherit;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-size: 100% 100%;   mask-size: 100% 100%;
}
.im-b--in  { background-color: #E9E9EB; color: #000; }
.im-b--out { background-color: #248BF5; color: #fff; }
.im-b--in::after  { left: -7px;  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 15'%3E%3Cmask id='m' maskUnits='userSpaceOnUse' x='0' y='0' width='16' height='15'%3E%3Cpath fill='%23fff' d='M2,0 H16 V4 A11,11 0 0 1 5,15 H2 Z'/%3E%3Cpath fill='%23000' d='M0,0 H7 V8 A7,7 0 0 1 0,15 H0 Z'/%3E%3C/mask%3E%3Crect width='16' height='15' fill='%23fff' mask='url(%23m)'/%3E%3C/svg%3E");  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 15'%3E%3Cmask id='m' maskUnits='userSpaceOnUse' x='0' y='0' width='16' height='15'%3E%3Cpath fill='%23fff' d='M2,0 H16 V4 A11,11 0 0 1 5,15 H2 Z'/%3E%3Cpath fill='%23000' d='M0,0 H7 V8 A7,7 0 0 1 0,15 H0 Z'/%3E%3C/mask%3E%3Crect width='16' height='15' fill='%23fff' mask='url(%23m)'/%3E%3C/svg%3E"); }
.im-b--out::after { right: -7px; -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 15'%3E%3Cmask id='m' maskUnits='userSpaceOnUse' x='0' y='0' width='16' height='15'%3E%3Cpath fill='%23fff' d='M14,0 H0 V4 A11,11 0 0 0 11,15 H14 Z'/%3E%3Cpath fill='%23000' d='M16,0 H9 V8 A7,7 0 0 0 16,15 H16 Z'/%3E%3C/mask%3E%3Crect width='16' height='15' fill='%23fff' mask='url(%23m)'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 15'%3E%3Cmask id='m' maskUnits='userSpaceOnUse' x='0' y='0' width='16' height='15'%3E%3Cpath fill='%23fff' d='M14,0 H0 V4 A11,11 0 0 0 11,15 H14 Z'/%3E%3Cpath fill='%23000' d='M16,0 H9 V8 A7,7 0 0 0 16,15 H16 Z'/%3E%3C/mask%3E%3Crect width='16' height='15' fill='%23fff' mask='url(%23m)'/%3E%3C/svg%3E"); }

/* A run of bubbles from one side is tight; a change of speaker is not. */
.im-thread { display: flex; flex-direction: column; align-items: flex-start; gap: 3px; }
.im-row { display: flex; width: 100%; }
.im-row--out { justify-content: flex-end; }
.im-row + .im-row.turn { margin-top: 6px; }

/* ONLY THE LAST BUBBLE OF A RUN GETS A TAIL. iOS gives a tail to the final
   message from a speaker and leaves the ones above it plain, which is why a
   run reads as one utterance broken up rather than as three separate texts.
   `.turn` marks a change of speaker, so a row whose NEXT sibling is not a turn
   is mid-run. The typing row is excluded — it is not a message, and counting it
   would strip the tail off the last real bubble every time it appeared. */
.im-row:has(+ .im-row:not(.turn):not([data-typing])) .im-b::after { display: none; }

/* The date marker, in iOS's own secondaryLabel. */
.im-meta {
  align-self: center; padding: 2px 0 6px;
  font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", Arial, sans-serif;
  font-size: 12.5px; line-height: 15.5px; letter-spacing: -.01em; color: #8E8E93;
}
.im-meta b { font-weight: 600; }

/* Three dots in the bubble iOS gives them, with the detached double tail. */
.im-typing {
  position: relative; display: inline-flex; align-items: center; gap: 5px;
  background: #E9E9EB; border-radius: 17px; padding: 11px 15px;
}
.im-typing i {
  width: 8.5px; height: 8.5px; border-radius: 999px; background: #9E9EA3;
  animation: imDot 1.15s infinite ease-in-out;
}
.im-typing i:nth-child(2) { animation-delay: .18s }
.im-typing i:nth-child(3) { animation-delay: .36s }
.im-typing::before, .im-typing::after {
  content: ""; position: absolute; border-radius: 999px; background: #E9E9EB;
}
.im-typing::before { left: -1px; bottom: -1.5px; width: 10px; height: 10px; }
.im-typing::after  { left: -6px; bottom: -6px;   width: 5px;  height: 5px; }
@keyframes imDot {
  0%, 60%, 100% { opacity: .38; transform: translateY(0) }
  30%           { opacity: 1;   transform: translateY(-8%) }
}
@media (prefers-reduced-motion: reduce) { .im-typing i { animation: none; opacity: .55 } }

/* Copilot thread ----------------------------------------------------------
   The section's picture used to be a flat webp of Casa's own support chat
   ("my laundry washer is showing error OE"). It is a real thread now, in the
   .im-* kit, showing the thing the heading claims: a permission granted in one
   sentence, work happening without you, and an escalation that carries a FACT
   the lead said rather than a status line.

   Bubbles are laid out bottom-up so the thread grows off the composer edge the
   way a real one does — a thread that fills from the top reads as a transcript. */
[data-copilot] { container-type: inline-size; }
.cp-thread {
  position: absolute; left: 7cqw; right: 7cqw; bottom: 7cqw;
  /* No gap: a collapsed row must contribute NOTHING, and flex gap is applied
     between items regardless of their height. The spacing rides on --h. */
  gap: 0;
}
.cp-thread .im-b { font-size: clamp(12px, 3.15cqw, 17px); line-height: 1.29; max-width: 84%; }
/*
  A message ARRIVES: the row grows from zero height and everything above it
  rises, because the thread is anchored to its bottom edge. That rise is the
  whole tell — a bubble that merely fades in where it was always going to sit
  reads as a slide, not as a conversation.

  The height is measured in JS and written to --h rather than animated to
  `auto` (which does not interpolate) or clamped with max-height (which makes
  the easing arrive early and then wait). Overflow stays VISIBLE throughout: the
  tail hangs 7px outside the bubble, so anything that clips would clip it off.
*/
.cp-thread .im-row {
  height: 0;
  overflow: visible;
  transition: height .46s cubic-bezier(.22,.61,.36,1);
}
.cp-thread .im-row > .im-b,
.cp-thread .im-row > .im-typing {
  opacity: 0;
  transform: translateY(8px) scale(.94);
  transform-origin: left bottom;
  transition: opacity .26s ease, transform .4s cubic-bezier(.22,.61,.36,1);
}
.cp-thread .im-row--out > .im-b { transform-origin: right bottom; }
.cp-thread .im-row.is-in { height: var(--h, auto); }
.cp-thread .im-row.is-in > .im-b,
.cp-thread .im-row.is-in > .im-typing { opacity: 1; transform: none; }
/* The typing row is measured the same way but is shown and hidden by the
   driver rather than by the sequence, so it carries its own flag. */
.cp-thread .im-row[data-typing] { transition: height .3s cubic-bezier(.22,.61,.36,1); }
.cp-typing-row .im-typing { transform: scale(clamp(.68, .062cqw + .55, 1)); transform-origin: left bottom; }

/* Says which mode you are in, in the eyebrow chip the page already uses. */
.cp-head {
  position: absolute; left: 7cqw; top: 6cqw; z-index: 2;
  display: inline-flex; align-items: center; gap: 8px;
  padding: 9px 14px; border-radius: 6px;
  background: var(--casa-green-01, #fbfbe3); color: var(--casa-green-04, #444835);
  font-size: 12px; font-weight: 500; letter-spacing: .02em; text-transform: uppercase;
}
.cp-dot { width: 6px; height: 6px; border-radius: 999px; background: currentColor; animation: cpPulse 2s ease-in-out infinite; }
@keyframes cpPulse { 0%,100% { opacity: .35 } 50% { opacity: 1 } }
@media (prefers-reduced-motion: reduce) { .cp-dot { animation: none; opacity: .7 } }

/* Tab rule on the top edge ------------------------------------------------
   casa.css is a COMPILED Tailwind build: only the utilities Casa's own pages
   actually used are in it. `border-b-2` is there because the engine tabs used
   it; `border-t-2` is not, so moving the rule to the top edge silently dropped
   the line and the active bar with it. Declared here rather than reverting,
   with the same shape the compiled `.border-b-2` has. */
.border-t-2 { border-top-style: var(--tw-border-style); border-top-width: 2px; }

/* Engine tab panels -------------------------------------------------------
   Six of the seven were Casa's own interior photography, which said nothing
   about what the tab above them names. They are product views now — every one
   shows the thing its icon claims, using the same tokens as the rest of the
   page (off-white-01/02/04, green, clay, blue, rounded-32) and the .im-* kit
   for anything that is actually a message.

   Fixed 16:9 like the photo they replaced, so switching tabs never reflows the
   section. Type scales with the CARD, not the viewport, for the same reason the
   lead sorter does: the box is a fixed ratio whatever the column is doing. */
.pv { container-type: size; background: #f8f8f1; }
.pv-pad {
  position: absolute; inset: 0;
  padding: 7cqh 6cqw;
  display: flex; flex-direction: column; justify-content: center;
  gap: 1.6cqh;
  font-size: clamp(12px, 2.35cqw, 19px);
  /* The section is text-center; a panel is a laid-out thing, not a caption. */
  text-align: left;
}
.pv-rows { gap: 1.6cqh; }

/* Each row is a surface, so the verdict chips read as labels ON something
   rather than as three coloured words floating beside three sentences. */
.pv-row {
  display: flex; align-items: center; gap: 1.8cqw; min-width: 0;
  background: #fff; border-radius: 14px; padding: 2.6cqh 2.4cqw;
}
.pv-row--dead { opacity: .55; }
.pv-line { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pv-line em { font-style: normal; color: #8e8f84; }

/* The three verdicts, in three families the design system already has — except OK
   reads from `positive` rather than from the brand, because "it went out" is a
   state. On an olive brand the two were the same colour by coincidence, and the
   coincidence broke the first time the brand moved: at brick the tint landed
   next to `clay-01` and OK and BLOCKED stopped being two chips. `positive` is a
   colour the brand cannot move, which is the property this row needs. Same call
   as `.ft-verdict--supp`. */
.pv-chip {
  flex: none; padding: .55em .9em; border-radius: 6px;
  font-size: .8em; font-weight: 500; letter-spacing: .02em; text-transform: uppercase;
  background: #ebebe4; color: #8e8f84;
}
.pv-chip--ok    { background: color-mix(in oklab, var(--casa-positive, #637525) 18%, var(--casa-off-white-01, #f8f8f1)); color: var(--casa-positive, #637525); }
.pv-chip--hold  { background: #ffe39d; color: #c07907; }
.pv-chip--block { background: #ffe3c9; color: #7e2e1a; }
.pv-tag { flex: none; font-size: .78em; color: #8e8f84; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }

.pv-foot { margin-top: 1.2cqh; padding-inline: .4cqw; font-size: .82em; line-height: 1.45; color: #8e8f84; }
.pv-eyebrow { font-size: .78em; letter-spacing: .02em; text-transform: uppercase; color: #8e8f84; }
.pv-note { font-size: .84em; color: #8e8f84; line-height: 1.4; }

/* Taste: a sentence on the left, what it turned into on the right. */
.pv-split { flex-direction: row; align-items: center; gap: 3cqw; }
.pv-half { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1.4cqh; }
.pv-arrow { flex: none; font-size: 1.6em; color: #c7c7bd; }
.pv-said { font-size: 1em !important; line-height: 1.35 !important; align-self: flex-start; }
.pv-pills { display: flex; flex-wrap: wrap; gap: .5em; }
.pv-pills b {
  padding: .5em .9em; border-radius: 999px; font-weight: 500;
  background: #fff; border: 1px solid #ebebe4;
}

/* Proximity + watches share the big-number treatment. */
.pv-metric { display: flex; align-items: baseline; gap: .6em; }
.pv-metric b { font-size: 2.4em; font-weight: 500; letter-spacing: -.02em; line-height: 1; }
.pv-metric span { color: #8e8f84; }
.pv-price { display: flex; align-items: center; justify-content: space-between; gap: 2cqw;
  background: #fff; border-radius: 16px; padding: 2.4cqh 2.4cqw; }
.pv-half2 { display: flex; flex-direction: column; gap: .25em; min-width: 0; }
.pv-half2 b { font-weight: 500; }
.pv-half2 i { font-style: normal; font-size: .84em; color: #8e8f84; }
.pv-nums { display: flex; align-items: baseline; gap: .6em; flex: none; }
.pv-nums s { color: #c7c7bd; }
.pv-nums b { font-size: 1.5em; font-weight: 500; letter-spacing: -.02em; }
.pv-nums em { font-style: normal; color: var(--casa-green-04, #444835); background: var(--casa-green-02, #f0f3a2); border-radius: 6px; padding: .3em .6em; font-size: .82em; }

/* Groups: who is in the room, and a reply that names one of them. */
.pv-who { display: flex; align-items: center; gap: .5em; }
.pv-av { width: 2.2em; height: 2.2em; border-radius: 999px; display: grid; place-items: center;
  font-size: .86em; font-weight: 500; }
.pv-who .pv-note { margin-left: .4em; }
.pv-quote { display: block; font-style: normal; font-size: .88em; color: #6a6a70;
  border-left: 2px solid #c7c7bd; padding-left: .7em; margin-bottom: .45em; }

/* Event log: the spine, as rows. */
.pv-log { gap: 0; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: clamp(9px, 1.8cqw, 14px); }
.pv-log-head, .pv-log-row {
  display: grid; grid-template-columns: 1.5fr 1.2fr .8fr; gap: 1.5cqw;
  padding: 1.5cqh 0; border-bottom: 1px solid #ebebe4;
}
.pv-log-head { color: #8e8f84; font-size: .82em; letter-spacing: .04em; text-transform: uppercase; }
.pv-log-row:last-of-type { border-bottom: 0; }
.pv-log-row span:nth-child(2), .pv-log-row span:nth-child(3) { color: #8e8f84; }
.pv-log .pv-foot { font-family: var(--font-sans, inherit); }

/* The .im-* bubbles inside a panel scale with the card like everything else. */
.pv .im-b { font-size: clamp(12px, 2.35cqw, 19px); line-height: 1.35; max-width: 88%; }
.pv .im-row { display: flex; }

/* FAQ ---------------------------------------------------------------------
   The questions used to be a single centred column with no heading, which is
   the shape of a support page rather than of the last thing somebody reads
   before deciding. The accordion items themselves are untouched — initAccordion
   keys off the h3 text and requires each `.rounded-40` to have exactly one
   child, a <button> — so only the frame around them changed. */
.faq-split {
  display: grid;
  grid-template-columns: minmax(280px, .8fr) minmax(0, 1.2fr);
  gap: clamp(32px, 5vw, 80px);
  align-items: start;
}
.faq-aside {
  display: flex; flex-direction: column; align-items: flex-start; gap: 20px;
  max-width: 420px;
  /* Sticky, so the heading is still there on the fourth question. */
  position: sticky; top: 120px;
}
.faq-list { display: flex; flex-direction: column; gap: 10px; }
/* The row lifts on hover so it reads as pressable — the chevron alone did not. */
.faq-list > .rounded-40 { transition: background-color .275s cubic-bezier(.25,1,.5,1); }
.faq-list > .rounded-40:hover { background-color: #f0f0e8; }

@media (max-width: 900px) {
  .faq-split { grid-template-columns: 1fr; }
  .faq-aside { position: static; max-width: none; }
}

/* Pricing -----------------------------------------------------------------
   Three tiers where there used to be one number. Geometry only: every colour,
   radius and type size below is either a casa.css utility applied in the
   markup or the literal that utility resolves to (off-white-01/02/03/04,
   green-01/02/04, rounded-40), so a plan card is the same furniture as a
   benefit card rather than a pricing widget that wandered in.

   The featured tier is green-01 rather than a bigger box, because scaling one
   card up is what breaks the row's baseline on the two widths where the
   feature lists are different lengths. */
.plans {
  width: 100%; max-width: 1040px; margin: 0 auto;
  display: grid; gap: 20px;
}
@media (min-width: 768px) { .plans { grid-template-columns: repeat(3, 1fr); gap: 24px; } }

.plan {
  position: relative;
  display: flex; flex-direction: column; gap: 24px;
  padding: 32px 28px;
  background: #f8f8f1;                       /* off-white-01 */
  border-radius: 40px;                       /* rounded-40 */
  transition: background-color 275ms cubic-bezier(.25,1,.5,1);
}
@media (min-width: 768px) { .plan { padding: 40px 32px; } }
.plan--featured {
  background: var(--casa-green-01, #fbfbe3);                       /* green-01 */
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--casa-green-04, #444835) 12%, transparent);
}

/* The chip sits ON the card's top edge, in the same lozenge the eyebrows use. */
.plan__badge {
  position: absolute; top: -13px; left: 32px;
  padding: 7px 12px; border-radius: 6px;
  background: var(--casa-green-02, #f0f3a2); color: var(--casa-green-04, #444835);       /* green-02 on green-04 */
  white-space: nowrap;
}

/* Baseline-aligned so "$189" and "/mo per agent" sit on one line of type. */
.plan__price { display: flex; align-items: flex-end; gap: 8px; margin-top: auto; }
.plan__amount {
  display: inline-flex; align-items: flex-start;
  font-family: var(--casa-font-heading, Georgia, serif);   /* same token .text-h* reads */
  font-size: 56px; line-height: .92; letter-spacing: -.02em; font-weight: 400;
}
@media (min-width: 768px) { .plan__amount { font-size: 64px; } }
.plan__currency { font-size: .48em; line-height: 1; margin-top: .18em; margin-right: .06em; }
.plan__per { line-height: 1.15; padding-bottom: 4px; }
.plan__bill { margin-top: -14px; }

/* The number is the only thing that changes on a toggle, so it is the only
   thing that moves — swapping the whole card would read as a page change. */
[data-price] { display: inline-block; transition: opacity 140ms ease, transform 140ms ease; }
[data-price].is-swapping { opacity: 0; transform: translateY(-6px); }

.plan__feats {
  display: flex; flex-direction: column; gap: 12px;
  margin: 0; padding: 20px 0 0; list-style: none;
  border-top: 1px solid color-mix(in srgb, var(--casa-green-04, #444835) 10%, transparent);
}
.plan__feat { display: flex; align-items: flex-start; gap: 10px; }
.plan__tick { flex: none; width: 12px; height: auto; margin-top: .45em; color: var(--casa-green-04, #444835); }

/* Monthly / Annual ---------------------------------------------------------
   A real segmented control: the knob is one element that slides, so the two
   states cannot drift apart the way two separately-styled buttons do. */
.plan-switch {
  position: relative; display: inline-flex; align-items: center;
  padding: 4px; border-radius: 999px; background: #ebebe4;   /* off-white-02 */
}
.plan-switch__knob {
  position: absolute; top: 4px; bottom: 4px; left: 4px;
  width: 50%;                                /* JS writes the measured width */
  border-radius: 999px; background: #fff;
  box-shadow: 0 1px 2px rgba(0,0,0,.06), 0 4px 12px rgba(0,0,0,.06);
  transform: translateX(0);
  transition: transform 420ms cubic-bezier(.22,.61,.36,1), width 420ms cubic-bezier(.22,.61,.36,1);
}
.plan-switch__opt {
  position: relative; z-index: 1;
  display: inline-flex; align-items: center; gap: 8px;
  height: 40px; padding: 0 20px; border: 0; background: none;
  font: inherit; font-weight: 500; color: #8e8f84;           /* off-white-04 */
  cursor: pointer; white-space: nowrap;
  transition: color 275ms cubic-bezier(.25,1,.5,1);
}
.plan-switch__opt[aria-pressed="true"] { color: #000; }
.plan-switch__save {
  padding: 4px 8px; border-radius: 6px;
  background: var(--casa-green-02, #f0f3a2); color: var(--casa-green-04, #444835);       /* green-02 on green-04 */
  font-size: 11px; font-weight: 500; letter-spacing: .02em; text-transform: uppercase;
}
@media (prefers-reduced-motion: reduce) {
  .plan-switch__knob, [data-price] { transition-duration: 1ms; }
}

/* The ladder --------------------------------------------------------------
   "From four hundred dead names to three meetings" is a claim about
   arithmetic, so it is drawn as arithmetic: three counts with the reason each
   one shrinks underneath it. The arrow turns a quarter on mobile because a row
   that stacks and keeps a sideways arrow reads as a broken layout. */
.ladder {
  display: grid; gap: 16px; align-items: stretch;
  grid-template-columns: 1fr;
  max-width: 450px; margin: 0 auto;
}
@media (min-width: 768px) {
  .ladder {
    grid-template-columns: 1fr auto 1fr auto 1fr;
    gap: 8px; max-width: 1040px;
  }
}
.ladder__step {
  display: flex; flex-direction: column; gap: 10px;
  padding: 32px 28px;
  background: #f8f8f1; border-radius: 40px;  /* off-white-01 / rounded-40 */
}
@media (min-width: 768px) { .ladder__step { padding: 40px 32px; } }
.ladder__n { color: #000; }
.ladder__arrow {
  display: grid; place-items: center; color: #c7c7bd;         /* off-white-03 */
  transform: rotate(90deg);
}
@media (min-width: 768px) { .ladder__arrow { transform: none; padding: 0 4px; } }
.ladder__chev { width: 18px; height: auto; }

/* text-h1 is 68px, and "From four hundred dead names" needs more than the 900px
   the other headings are capped at — under that cap the explicit break is
   overruled by a wrap and the line lands mid-phrase. */
.wide-head { max-width: 1160px; }

/* Closing ask + footer ----------------------------------------------------
   Rebuilt rather than restyled. The old footer gave two of its four columns to
   a newsletter form and a 1,200px wordmark, so the actual navigation — eight
   benefit pages and eight company/legal pages — was a dense block in the
   corner. Now the brand and the sign-up own one side, the links own the other,
   and the links are grouped by what they ARE (product, company, legal) rather
   than by what fitted.

   Colours are the same tokens as everywhere else: green-04 ground,
   off-white-01/03 ink, green-02 for the one accent. */
.ft-cta { padding-block: clamp(80px, 14vh, 180px) clamp(56px, 9vh, 110px); }

.ft-btn {
  height: 56px; padding: 0 32px; border-radius: 999px;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 18px; font-weight: 500; letter-spacing: .02em; white-space: nowrap;
  transition: background-color .275s cubic-bezier(.25,1,.5,1), color .275s cubic-bezier(.25,1,.5,1), border-color .275s;
}
.ft-btn--primary { background: var(--casa-green-04, #444835); color: #f8f8f1; }
.ft-btn--primary:hover { background: color-mix(in oklab, var(--casa-green-04, #444835) 88%, var(--casa-off-white-01, #f8f8f1)); }
.ft-btn--ghost { border: 1px solid #c7c7bd; color: #282828; }
.ft-btn--ghost:hover { background: #ebebe4; border-color: #ebebe4; }

/* The footer's own top gap, and it replaces one the DOM used to provide.
   The closing CTA and the footer both used to sit INSIDE the page's
   `py-l flex gap-y-l` wrapper, which gave them a 144px gap — and also left 144px
   of white BELOW the dark footer, because the wrapper's bottom padding is
   outside the footer's background. Both are now siblings of `main` (one `<main>`
   per document, footer outside it), so the gap has to be stated rather than
   inherited from a wrapper that no longer contains them. `--space-l` is Casa's
   own section rhythm and is already scaled by scale.css at the :root that
   declares it, so this is the same 144px it always was. */
.ft {
  background: var(--casa-green-04, #444835); color: #c7c7bd; margin-top: var(--space-l);
  /* "It worked" is a STATE, not the brand, and the two used to be the same
     colour only because the brand happened to be green. `positive` is the token
     for it and the brand palette deliberately leaves it alone; it is lifted
     towards paper here because #637525 on a dark ground is dark on dark. Used by
     the SUPPRESSED verdict and by the status dot below — one derived colour
     rather than two that drift. A green dot for "operational" also happens to be
     the only thing anybody reads a status dot as; that it now sits on a blue
     ground rather than an olive one is what makes it visible as a signal at
     all. */
  --v3-ok: color-mix(in oklab, var(--casa-positive, #637525) 35%, var(--casa-off-white-01, #f8f8f1));
}
.ft-inner { padding-block: clamp(56px, 8vh, 96px) 28px; }

/* The promises ------------------------------------------------------------
   The footer used to open on a blurb beside 430px of nothing, because a
   380px-capped brand block was sitting in a column sized for 600. This band
   spans the full width, so there is no hole to fill.

   It is also the only reassurance an agent gets after they have decided, and
   the three facts it carries are the three a licence cares about. Each one is
   labelled with the verdict the engine actually returns — a draft is HELD, a
   fair-housing line is BLOCKED, an opt-out is SUPPRESSED — so the footer
   teaches the same vocabulary the guardrail section does rather than inventing
   marketing words for it. The hairline over each is the tab rule from the
   engine section, reused. */
.ft-promises {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(24px, 4vw, 64px);
  padding-bottom: clamp(44px, 6.5vh, 76px);
}
.ft-promise {
  display: flex; flex-direction: column; align-items: flex-start; gap: 10px;
  padding-top: 18px; border-top: 1px solid rgba(248,248,241,.2);
}
.ft-promise b { font-size: 17px; font-weight: 500; line-height: 1.32; color: #f8f8f1; }
.ft-promise-body { font-size: 14.5px; line-height: 1.5; color: rgba(248,248,241,.6); }

/* The chip families from the engine panels, at the alpha a dark ground needs:
   #ffe39d and #ffe3c9 as solid fills are shouting on green-04.

   The three verdicts have to stay three, and one of them used to be a hostage.
   SUPPRESSED was the brand tint, which on an olive brand was a lime sitting well
   clear of BLOCKED's peach — and on the brick brand it was a pink, which is the
   same colour as BLOCKED. The distinction survived only as long as the brand
   stayed green. So it reads from `positive` instead: what a refusal honoured
   correctly always meant, and a colour the brand cannot move. */
.ft-verdict {
  padding: 5px 9px; border-radius: 6px;
  font-size: 11px; font-weight: 500; letter-spacing: .06em; text-transform: uppercase;
}
.ft-verdict--held    { background: rgba(255,227,157,.16); color: #ffe39d; }
.ft-verdict--blocked { background: rgba(255,150,110,.18); color: #ffb094; }
.ft-verdict--supp    { background: color-mix(in srgb, var(--v3-ok) 16%, transparent); color: var(--v3-ok); }

/* Brand + map -------------------------------------------------------------
   A fixed left column rather than a fraction, so the brand can never be given
   more room than it uses. */
.ft-top {
  display: grid;
  grid-template-columns: minmax(0, 340px) minmax(0, 1fr);
  gap: clamp(40px, 7vw, 120px);
  padding-bottom: clamp(36px, 5vh, 60px);
}
.ft-brand { display: flex; flex-direction: column; align-items: flex-start; gap: 24px; }
.ft-mark { height: 22px; width: auto; color: #f8f8f1; }
.ft-blurb { font-size: 17px; line-height: 1.45; color: #f8f8f1; }

.ft-sub { width: 100%; display: flex; flex-direction: column; gap: 10px; }
.ft-sub-label { font-size: 14px; letter-spacing: .02em; }
.ft-sub-row {
  display: flex; align-items: center; gap: 4px;
  border: 1px solid rgba(248,248,241,.32); border-radius: 999px; padding: 4px 4px 4px 18px;
  transition: border-color .2s ease;
}
.ft-sub-row:focus-within { border-color: rgba(248,248,241,.7); }
.ft-input {
  flex: 1; min-width: 0; height: 40px; background: transparent; border: 0; outline: 0;
  font: inherit; font-size: 14px; color: #f8f8f1;
}
.ft-input::placeholder { color: rgba(248,248,241,.45); }
.ft-sub-go {
  flex: none; height: 40px; padding: 0 20px; border: 0; border-radius: 999px;
  background: var(--casa-green-02, #f0f3a2); color: var(--casa-green-04, #444835); font: inherit; font-size: 14px; font-weight: 500;
  cursor: pointer; white-space: nowrap; transition: opacity .2s ease;
}
.ft-sub-go:hover { opacity: .88; }

/* FOUR columns of exactly FOUR. The eight benefit pages were one column
   against two of four, so the map was an L with an empty quadrant under it;
   splitting them by what the page is FOR ("Product" / "Running a list") makes
   every column the same height, which is the difference between a link map
   that was designed and one that was whatever fitted. */
/* Five columns, because the site is five groups: what it does, what running a
   list looks like, the pages that explain it, the company, and the legal set.
   Sixteen links used to fit in four; there are twenty-four now, and a link a
   crawler cannot find from the footer is a page nobody arrives at. */
.ft-cols { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: clamp(18px, 2vw, 32px); }
.ft-col { display: flex; flex-direction: column; gap: 14px; }
.ft-head {
  font-size: 12px; font-weight: 500; letter-spacing: .08em; text-transform: uppercase;
  color: rgba(248,248,241,.5);
}
.ft-list { display: flex; flex-direction: column; gap: 11px; }
.ft-link { font-size: 15px; color: #f8f8f1; transition: color .2s ease; }
.ft-link:hover { color: var(--casa-green-02, #f0f3a2); }

/* The signature -----------------------------------------------------------
   The wordmark at full container width, holding the page closed. Kept at a low
   alpha on purpose: at 200px tall in off-white it stops being a signature and
   starts being a headline, and the last word of this page belongs to the
   sentence above it, not to the brand. */
/* The wordmark cannot fill this width — "lewis" at container width is ~380px
   tall, which is a wall rather than a sign-off — so the row is anchored at the
   other end instead of left with a hole beside it. Back-to-top is real utility
   on a page this long, and it is the one control that belongs down here. */
.ft-sign {
  padding-top: clamp(10px, 2vh, 28px);
  display: flex; align-items: flex-end; justify-content: space-between; gap: 24px;
}
.ft-up {
  display: inline-flex; align-items: center; gap: 9px;
  padding-bottom: 10px; font-size: 14px; letter-spacing: .02em;
  color: rgba(248,248,241,.62); transition: color .2s ease;
}
.ft-up:hover { color: var(--casa-green-02, #f0f3a2); }
.ft-up svg { transition: transform .3s cubic-bezier(.22,.61,.36,1); }
.ft-up:hover svg { transform: translateY(-3px); }
.ft-word {
  display: block;
  font-size: clamp(78px, 13vw, 172px);
  font-weight: 600; letter-spacing: -.055em; line-height: .82;
  color: rgba(248,248,241,.15);
  /* Cap-height trim, so the sign-off sits ON the rule below it rather than
     floating above it in its own line-box. */
  text-box: trim-both cap alphabetic;
  user-select: none;
}

.ft-bottom {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 10px 28px;
  margin-top: clamp(20px, 3vh, 34px);
  padding-top: 22px; border-top: 1px solid rgba(248,248,241,.14);
  font-size: 13.5px; color: rgba(248,248,241,.6);
}
/* The AI disclosure is a real product behaviour (its own bubble, ahead of any
   work), so it belongs with the copyright rather than in a legal page nobody
   opens. */
.ft-note { color: rgba(248,248,241,.45); }
.ft-status { display: inline-flex; align-items: center; gap: 8px; }
.ft-status i { width: 7px; height: 7px; border-radius: 999px; background: var(--v3-ok); }

@media (max-width: 1280px) {
  .ft-cols { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 34px 24px; }
}
@media (max-width: 900px) {
  .ft-cols { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 32px 24px; }
}
@media (max-width: 900px) {
  .ft-promises { grid-template-columns: 1fr; gap: 26px; }
  .ft-top { grid-template-columns: 1fr; }
  .ft-brand { max-width: 460px; }
  .ft-bottom { justify-content: flex-start; }
  /* Two hundred pixels of watermark is a signature on a desktop and a wall on a
     phone, where it would be most of the screen. */
  .ft-word { color: rgba(248,248,241,.13); }
}
/* Two columns hold down to a 360px phone — every label fits, and sixteen
   links in a single file is 1,800px of footer to scroll past. */
@media (max-width: 360px) { .ft-cols { grid-template-columns: 1fr; } }


/* ===========================================================================
   The rest of the site
   ---------------------------------------------------------------------------
   Everything above this line styles `/` and `/demo`, which are hand-written.
   Everything below styles the forty-odd pages emitted by
   `scripts/landing-v3-pages.ts` — the benefit pages, the comparisons, the trust
   and legal pages, the use cases.

   Every colour here is `var(--casa-<name>, <literal>)` rather than the literal
   on its own, which is deliberately one step better than the footer above it:
   the value is identical until the mood board sets a variable, and then these
   rules move with the page instead of staying behind on the old palette. Sizes
   are px so `scale.css` reaches them — run `npx tsx scripts/landing-v3-scale.ts`
   after touching anything down here.
   =========================================================================== */

/* A keyboard user's first tab stop, and the one thing on the page that must be
   invisible until it has focus. It cannot be `display:none` — a hidden element
   is not focusable, so the shortcut would not exist at all. */
.v3-skip {
  position: fixed; top: 8px; left: 8px; z-index: 200;
  padding: 10px 16px; border-radius: 999px;
  background: var(--casa-off-black-02, #282828); color: var(--casa-white, #fff);
  font-size: 14px; font-weight: 500;
  transform: translateY(-200%);
  transition: transform 200ms cubic-bezier(.25,1,.5,1);
}
.v3-skip:focus-visible { transform: translateY(0); }

/* The top of a subpage, under a fixed header.
   Not a utility class, because the right one does not exist: Casa's build emits
   `pt-100` and `pt-160` but no `md:pt-*` above 24, so "100 on a phone, 160 on a
   desktop" is not expressible in their vocabulary. /demo carries
   `pt-100 md:pt-120` and the second half of that has quietly never done
   anything. */
.v3-page-top { padding-top: 104px; }
@media (min-width: 800px) { .v3-page-top { padding-top: 168px; } }

/* Long-form body copy -----------------------------------------------------
   The measure is the point. `site-grid--content` is ten of twelve columns,
   which on a desktop is about 1,100px — fine for three cards side by side and
   roughly twice the line length anybody reads comfortably. A benefit page is
   mostly sentences, so it gets a column. */
.v3-prose { display: flex; flex-direction: column; gap: 20px; max-width: 720px; }
.v3-prose > h2 { margin-top: 22px; }
.v3-prose > h2:first-child { margin-top: 0; }
.v3-prose > h3 { margin-top: 10px; }
/* Every link in generated body copy, wherever it sits.
   Scoped to `.v3-prose` first, which missed the three places a link also
   appears outside it — a table's footnote, a tile's blurb, a ladder note. An
   unstyled link in Casa's palette is body-coloured and has no underline, so it
   is not a link that is hard to see: it is a link that is invisible. */
.v3-prose a,
.v3-note a,
.v3-table + p a,
.v3-table-wrap ~ p a,
.v3-tile__body a,
.ladder__note a { text-decoration: underline; text-underline-offset: 2px; text-decoration-thickness: 1px; }
.v3-prose a:hover,
.v3-note a:hover,
.v3-table-wrap ~ p a:hover,
.v3-tile__body a:hover { color: var(--casa-off-white-04, #8e8f84); }

.v3-list { padding-left: 24px; gap: 8px; max-width: 700px; }
.v3-list::marker { color: var(--casa-off-white-03, #c7c7bd); }

/* A note is an aside, not a quote — the copy that qualifies the paragraph above
   it. The rule down the left is the whole treatment; a tinted box would read as
   a callout and pull the eye off the argument. */
.v3-note {
  border-left: 2px solid var(--casa-green-03, #acc064);
  padding: 2px 0 2px 18px;
  color: var(--casa-off-white-04, #8e8f84);
  max-width: 660px;
}

.v3-code {
  font-family: var(--font-mono);
  font-size: .9em;
  padding: 2px 6px; border-radius: 5px;
  background: var(--casa-off-white-02, #ebebe4);
}

/* Breadcrumbs -------------------------------------------------------------
   The visible half of the page's BreadcrumbList. The separator is generated
   rather than typed so it can be hidden from a screen reader, which otherwise
   reads "Home slash Benefits slash" out loud. */
.v3-crumbs ol { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.v3-crumbs li { display: flex; align-items: center; gap: 8px; color: var(--casa-off-white-04, #8e8f84); }
.v3-crumbs li + li::before { content: "/"; color: var(--casa-off-white-03, #c7c7bd); }
.v3-crumbs a { color: inherit; }
.v3-crumbs a:hover { color: var(--casa-off-black-02, #282828); text-decoration: underline; text-underline-offset: 2px; }

/* Comparison tables -------------------------------------------------------
   A real table, so it can be read a row at a time. The wrapper scrolls rather
   than the columns collapsing: a three-column comparison squeezed onto a 360px
   phone is three words per line and unreadable, and a table you can push
   sideways is at least still a table. `tabindex` makes that scroll reachable
   from a keyboard, which is the part that is usually forgotten. */
.v3-table-wrap { width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; }
.v3-table {
  width: 100%; min-width: 620px; border-collapse: separate; border-spacing: 0;
  background: var(--casa-off-white-01, #f8f8f1);
  border-radius: 24px; overflow: hidden;
  font-size: 16px; line-height: 1.45;
}
.v3-table th, .v3-table td {
  padding: 18px 22px; text-align: left; vertical-align: top;
  border-bottom: 1px solid var(--casa-off-white-02, #ebebe4);
}
.v3-table thead th {
  font-weight: 500; font-size: 14px; letter-spacing: .02em; text-transform: uppercase;
  color: var(--casa-off-white-04, #8e8f84);
  background: var(--casa-off-white-02, #ebebe4);
  border-bottom-color: var(--casa-off-white-03, #c7c7bd);
}
.v3-table tbody tr:last-child th, .v3-table tbody tr:last-child td { border-bottom: 0; }
.v3-table__rowhead { font-weight: 500; color: var(--casa-off-black-02, #282828); }
.v3-table tbody th.v3-table__rowhead { width: 34%; }
/* The Lewis column. Reading a comparison means finding one column and staying
   in it, and a tint does that in a way a bold heading does not. */
.v3-table tbody td:first-of-type { background: color-mix(in srgb, var(--casa-green-02, #f0f3a2) 28%, transparent); }
.v3-table thead th:nth-child(2) { background: var(--casa-green-02, #f0f3a2); color: var(--casa-green-04, #444835); }

/* Link tiles --------------------------------------------------------------
   The internal-linking surface. Four of these at the bottom of a page is the
   difference between forty pages and forty orphans. */
.v3-tiles { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; width: 100%; }
.v3-tile {
  display: flex; flex-direction: column; gap: 8px;
  height: 100%; padding: 26px 26px 22px;
  background: var(--casa-off-white-01, #f8f8f1);
  border-radius: 24px;
  transition: background 275ms cubic-bezier(.5,1,.89,1);
}
.v3-tile:hover { background: var(--casa-off-white-02, #ebebe4); }
.v3-tile__title { font-weight: 500; }
.v3-tile__body { flex: 1; }
.v3-tile__go { margin-top: 10px; color: var(--casa-off-white-04, #8e8f84); }
.v3-tile:hover .v3-tile__go { color: var(--casa-off-black-02, #282828); }
@media (max-width: 1000px) { .v3-tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 620px)  { .v3-tiles { grid-template-columns: 1fr; } }

/* An iMessage exchange on a page that is not the hero. The hero's `.hero-thread`
   is sized against a full-bleed photograph; this one sits in a column. */
.v3-thread { width: 100%; max-width: 460px; }

/* `pv` panels off the landing page ---------------------------------------
   There the five panels crossfade inside one box, so they all carry the same
   16:9 and have to. A single panel on a benefit page does not, and keeping the
   ratio would leave a band of empty green under three rows of text. */
/* `container-type: size` is size containment on BOTH axes, which means the
   panel's height cannot come from its contents — so dropping the aspect ratio
   and nothing else collapsed it to zero pixels tall, with three rows of markup
   inside it and no error anywhere. It has to become `inline-size` containment:
   the width is still queryable (the type scale is `2.35cqw` and that is the
   whole reason these panels read as one object at any column width), the height
   comes from the rows, and every `cqh` length underneath — which would otherwise
   resolve against the VIEWPORT once its container stops offering a block axis —
   is restated as a pixel value. Those pixels are scaled by scale.css like every
   other length here. */
.pv--auto { aspect-ratio: auto !important; container-type: inline-size; }
.pv--auto .pv-pad { position: static; padding: 30px 26px; gap: 12px; }
.pv--auto .pv-rows { gap: 12px; }
.pv--auto .pv-row { padding: 16px 18px; }
.pv--auto .pv-foot { margin-top: 10px; padding-inline: 0; }
.pv--auto .pv-half { gap: 10px; }
.pv--auto .pv-price { padding: 16px 18px; }
.pv--auto .pv-log-head, .pv--auto .pv-log-row { padding: 11px 0; }

/* The accordion, server-rendered ----------------------------------------
   `.faq-panel` on the landing page is created by app.js with its height and
   opacity set inline. Here the panel is in the HTML, which is the whole point
   — the answers are the most searchable prose on the site — so the closed state
   has to be expressible in CSS as well as inline, or the page flashes every
   answer open before the script runs. */
[data-faq] .faq-panel { height: 0; opacity: 0; }
[data-faq] .faq-panel[data-open="true"] { opacity: 1; }
