/* Vivvia landing — VH-D2 (2).
   THIS is the source. site/render.js only COPIES it to public/css/landing.css, so edit it here
   and re-render; edits to the copy survive exactly until the next `npm run build:site`.

   Extracted from the S1 design artifact (rev 13 / artifact version 14, VH-D2 (1) S3,
   https://claude.ai/code/artifact/cdcf14f2-842e-4113-a3c5-19b6eb094568). Three blocks of review
   scaffolding were dropped rather than hidden — .review (the sticky bar), .draft (the DRAFT FOR
   DESIGN REVIEW frames) and .note/body.notes (the design-notes panel) — because none of their
   markup ships either. Two substantive changes, both explained at the line that makes them:
   the review-bar viewport allowance, and the image paths. Everything else is byte-identical to
   the approved artifact, comments included: those comments record WHY each value is what it is,
   and they are the reason the next person does not re-guess --wash. */

:root{
  --phthalo:#0E3B4C; --phthalo-2:#12495E;
  --pine:#33503F;    --pine-2:#3E6149;
  --diox:#43356A;    --diox-2:#524080;
  --brass:#9A7B3C;  --bronze:#54431F;
  --ink:#071E27; --bone:#ECE6D9; --paper:#F4EFE4;
  --stone:#C6BCA8; --slate:#8A8375; --cad:#D33F27; --blueprint:#5BA7C4;
  --serif:'Cormorant Garamond',Georgia,'Times New Roman',serif;
  --sans:'Archivo',system-ui,-apple-system,'Segoe UI',sans-serif;
  --mono:'Space Mono',ui-monospace,'Courier New',monospace;
  --measure:62ch;
  --pad:clamp(22px,5vw,72px);
  --ground:var(--phthalo); --ground-2:var(--phthalo-2);
  --ground-rgb:14,59,76; --ground-2-rgb:18,73,94;
  --on-ground:var(--paper); --on-ground-dim:var(--stone);
}
*{box-sizing:border-box;}
body{margin:0;background:var(--ink);color:var(--paper);font-family:var(--sans);font-size:16px;line-height:1.6;-webkit-font-smoothing:antialiased;}
img{max-width:100%;}
:focus-visible{outline:2px solid var(--cad);outline-offset:3px;}
[hidden]{display:none!important;}
@media (prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important;scroll-behavior:auto!important;}}

/* ── deck: full-viewport folds, slide-style scroll ──────────────────────── */
.deck{scroll-snap-type:y mandatory;overflow-y:auto;height:100vh;height:100dvh;scroll-behavior:auto;}
/* Phone: folds are taller than the viewport, so panel-to-panel jumping would hide their
   bottoms entirely. Native scrolling, no snap, no gesture capture (the JS opts out too). */
@media(max-width:900px){
  .deck{scroll-snap-type:none;}
  .fold{scroll-snap-stop:normal;}
}
.fold{position:relative;overflow:hidden;scroll-snap-align:start;scroll-snap-stop:always;
  min-height:100vh;min-height:100dvh;display:grid;align-content:center;
  background:var(--ground);color:var(--on-ground);padding:clamp(52px,7vh,96px) var(--pad);}
.fold--pine{--ground:var(--pine);--ground-2:var(--pine-2);--ground-rgb:51,80,63;--ground-2-rgb:62,97,73;}
.fold--diox{--ground:var(--diox);--ground-2:var(--diox-2);--ground-rgb:67,53,106;--ground-2-rgb:82,64,128;}
/* Estates: BRONZE ground, BRASS light. Brass is Estates' signature hue, but it is a mid-tone
   metal -- paper text on #9A7B3C measures ~2.8:1, under AA, and a warm mid-tone over the cool
   steel photograph turns olive. So the hue becomes the highlight (the screen layer) and the
   ground drops to a bronze of the same family, at the luminance the other three grounds sit at. */
.fold--brass{--ground:var(--bronze);--ground-2:var(--brass);--ground-rgb:84,67,31;--ground-2-rgb:154,123,60;}
.fold::before{content:"";position:absolute;inset:0;z-index:1;
  background:radial-gradient(125% 90% at 50% -8%,var(--ground-2) 0%,var(--ground) 58%);opacity:.55;}
.fold.has-bg::before{opacity:0;}
.wrap{position:relative;z-index:4;max-width:1180px;margin:0 auto;width:100%;}

/* Photograph beds. Each carries its pillar's wash so the ground reads as the
   brand colour first and the image second.

   --wash and --lift are per-image and must be re-measured whenever a plate is swapped. The
   direction they move in is set by the plate's KEY, not by taste: a high-key photograph (Estates'
   whitewashed lime) needs a heavy wash and a light lift, because a multiply has little to bite on
   against white; a low-key one (Digital's graphite conduit) needs the opposite, because the same
   wash over an already-dark image returns a black rectangle. Guessing gives every fold the same
   haze, which is what the first pass on these three plates did. */
.bg{position:absolute;inset:0;z-index:0;background-size:cover;background-position:center;
  isolation:isolate;}
/* Layer A -- colour overlay. multiply tints the shadows, screen lifts the highlights into the
   same hue. A flat alpha fill cannot do this: over a warm photo it goes grey-brown, and over a
   near-white one it either vanishes or erases the picture. */
.bg::before,.bg::after{content:"";position:absolute;inset:0;}
.bg::before{background:rgb(var(--ground-rgb));mix-blend-mode:multiply;opacity:var(--wash,.82);}
.bg::after{background:rgb(var(--ground-2-rgb));mix-blend-mode:screen;opacity:var(--lift,.15);}

/* Layer B -- directional scrim. Opaque under the text column, gone by the far edge, so the
   photograph survives where nothing has to be read on top of it. */
.fold.has-bg::after{content:"";position:absolute;inset:0;z-index:2;pointer-events:none;
  background:linear-gradient(97deg,
    rgba(var(--ground-rgb),.96) 0%, rgba(var(--ground-rgb),.90) 27%,
    rgba(var(--ground-rgb),.56) 53%, rgba(var(--ground-rgb),.14) 79%,
    rgba(var(--ground-rgb),0) 100%);}
/* Hero reads centred on both axes, so its scrim is radial rather than directional. */
.fold.hero.has-bg::after{
  background:radial-gradient(ellipse 80% 74% at 50% 52%,
    rgba(var(--ground-rgb),.90) 0%, rgba(var(--ground-rgb),.70) 44%,
    rgba(var(--ground-rgb),.20) 77%, rgba(var(--ground-rgb),0) 100%);}
/* Global had a bespoke, denser curve here. It was cut for the PREVIOUS plate, which was bright
   and busy directly under the text column; the current one is a soft interior that needs no such
   help, and the override was making Global read heavier than Digital and Estates. Retired, so all
   three pillar folds share one curve -- which is what makes them read as one system. */
/* Contact puts the form on the RIGHT, where a left-to-right scrim is weakest -- so this one
   never falls below .42 and only opens up in the top corner. */
#contact.has-bg::after{
  background:linear-gradient(112deg,
    rgba(var(--ground-rgb),.96) 0%, rgba(var(--ground-rgb),.93) 52%,
    rgba(var(--ground-rgb),.66) 84%, rgba(var(--ground-rgb),.42) 100%);}
/* Below 900px every fold is a single column and the text spans the full width, so a
   directional scrim is the wrong instrument -- go vertical instead. */
@media(max-width:900px){
  .fold.has-bg::after,.fold.hero.has-bg::after,#contact.has-bg::after{
    background:linear-gradient(177deg,
      rgba(var(--ground-rgb),.88) 0%, rgba(var(--ground-rgb),.93) 45%,
      rgba(var(--ground-rgb),.96) 100%);}
}
.bg--hero{background-image:url("/img/vivvia_holding_hero_bg.jpg");}
/* hero: warm copper under a cold blue ground -- the widest temperature gap in the set */
.bg--hero{--wash:.84;--lift:.13;}
.bg--global{background-image:url("/img/vivvia_global_bg_3.jpg");background-position:center;}
/* global: a stone cloister corridor, empty, light falling across the floor right of centre.
   Warm stone under pine is a wide temperature gap, so the lift carries more than it did: pine-2
   screened back keeps the highlights green rather than letting them drift to khaki. */
.bg--global{--wash:.85;--lift:.11;}
.bg--digital{background-image:url("/img/vivvia_digital_bg_3.jpg");background-position:center;}
/* digital: exposed conduit and cable tray in one-point perspective -- the fold's own headline,
   "we run the operations underneath", found in a photograph. Near-monochrome graphite, which is
   the ideal case for a coloured ground: it takes dioxazine without a fight. Already dark, so the
   wash comes down hard; .85 over this would be a black rectangle. */
.bg--digital{--wash:.80;--lift:.10;}
/* estates: an Andalusian courtyard at dusk -- whitewashed lime, a stone arch, an olive tree
   right of centre. The only high-key plate of the three, and whitewash is exactly where bronze
   wants to live, so the wash stays moderate. The lift stays heavy because brass is this fold's
   signature and the highlights are the only place it can appear. */
.bg--estates{background-image:url("/img/vivvia_estates_bg_3.jpg");background-position:center;--wash:.89;--lift:.13;}
/* contact: a white geodesic lattice -- the mark's own triangular geometry, found in a photograph */
.bg--contact{background-image:url("/img/vivvia_contact_form_bg.jpg");background-position:center 30%;--wash:.88;--lift:.10;}

.lattice{position:absolute;z-index:3;color:var(--on-ground);opacity:.05;pointer-events:none;width:min(76vw,860px);height:auto;}
.l-hero{right:-19%;bottom:-44%;transform:rotate(-8deg);}
.l-pine{left:-25%;top:-32%;transform:rotate(14deg);}
.l-diox{right:-23%;top:-28%;transform:rotate(-19deg);}
.l-cta{left:-19%;bottom:-48%;transform:rotate(6deg);}
.l-brass{left:-22%;bottom:-41%;transform:rotate(-11deg);}

.eyebrow{display:flex;align-items:center;gap:11px;font-family:var(--mono);font-size:10.5px;
  letter-spacing:.2em;text-transform:uppercase;color:var(--on-ground-dim);margin:0 0 20px;}
.eyebrow::after{content:"";flex:1;height:1px;background:currentColor;opacity:.28;max-width:120px;}
.rule{width:54px;height:2px;background:var(--cad);border:0;margin:0 0 26px;}
h2{font-family:var(--serif);font-style:italic;font-weight:300;font-size:clamp(30px,4.4vw,52px);
  line-height:1.08;margin:0;text-wrap:balance;}
.lede{font-family:var(--serif);font-style:italic;font-weight:300;font-size:clamp(18px,2.2vw,26px);
  line-height:1.45;max-width:34ch;margin:22px auto 0;}
p{max-width:var(--measure);}
.body{color:var(--on-ground-dim);margin:24px 0 0;font-size:15.5px;}

/* ── hero: centred on both axes ─────────────────────────────────────────── */
.hero .wrap{display:grid;justify-items:center;text-align:center;}
.hero .eyebrow::after{display:none;}
.hero .lock{display:grid;justify-items:center;gap:clamp(18px,3vh,30px);}
.hero .mark{width:clamp(64px,9vw,116px);height:auto;color:var(--paper);}
.hero .wordmark{width:min(78vw,560px);height:auto;color:var(--paper);display:block;}
.hero .body{margin-left:auto;margin-right:auto;}
.scrollcue{margin-top:clamp(30px,5vh,54px);font-family:var(--mono);font-size:10px;
  letter-spacing:.2em;text-transform:uppercase;color:var(--stone);}
.scrollcue::after{content:"";display:block;width:1px;height:26px;margin:9px auto 0;
  background:linear-gradient(var(--stone),transparent);animation:drift 2.6s ease-in-out infinite;}
@keyframes drift{0%,100%{opacity:.3;transform:translateY(0)}50%{opacity:.9;transform:translateY(5px)}}

/* ── pillar folds ───────────────────────────────────────────────────────── */
.pillar{display:grid;gap:clamp(30px,4.5vw,64px);grid-template-columns:1.05fr .95fr;align-items:center;}
@media(max-width:900px){.pillar{grid-template-columns:1fr;}}

/* Lifted off the ground: a soft drop, plus a light that travels the border. */
/* A light that travels the STROKE. Registering the angle makes it animatable, so the
   gradient turns while the element stays still -- the previous build rotated the element
   itself at inset:-1px, which swung its corners outside the unclipped card and smeared
   cadmium around the outside once per cycle. */
@property --beam{syntax:"<angle>";initial-value:0deg;inherits:false;}
.glowwrap{position:relative;border-radius:3px;padding:1.6px;isolation:isolate;overflow:hidden;
  box-shadow:0 2px 9px rgba(0,0,0,.26);}
/* --beam-origin offsets where the bright tip SITS, independently of --beam, which is the angle it
   travels through. Both are needed because the entrance animation ends by handing the ring a
   START POINT at the bottom-right corner; without an origin every card's ring would begin at 12
   o'clock, which is not where the eye has just been left. 135deg
   is bottom-right for a card of roughly these proportions (0deg is 12 o'clock, clockwise), and it
   is a visual constant, not a derived one -- tune it by eye if the card's aspect changes a lot.
   Phase-only on an infinite rotation, so it changes nothing once the ring is turning. */
.glowwrap{--beam-origin:135deg;}
.glowwrap::before{content:"";position:absolute;inset:0;border-radius:inherit;z-index:0;
  background:conic-gradient(from calc(var(--beam,0deg) + var(--beam-origin,0deg)),
    rgba(236,230,217,.10) 0deg, rgba(236,230,217,.10) 270deg,
    rgba(236,230,217,.26) 312deg, rgba(236,230,217,.60) 342deg, rgba(236,230,217,1) 360deg);
  animation:beam 5.5s linear infinite;}
/* The interior is opaque, so what remains visible of ::before is the 1.6px ring and nothing
   else -- the glow cannot bloom inward across the card or outward into a halo. */
.glowwrap::after{content:"";position:absolute;inset:1.6px;border-radius:2px;z-index:1;background:var(--ground);}
@keyframes beam{to{--beam:360deg}}
/* Business Steps -- an ordered 1->4 process. The council asked for steps, not value props,
   and a numbered run reads as a sequence where a bulleted one reads as a menu. */
.steps{position:relative;z-index:2;list-style:none;margin:0;padding:0;display:grid;gap:1px;
  background:rgba(236,230,217,.13);border-radius:2px;overflow:hidden;counter-reset:s;}
.steps li{position:relative;background:var(--ground);padding:15px 20px;display:grid;gap:14px;
  grid-template-columns:auto 1fr;align-items:start;}
/* Descending, so each row sits BEHIND the one above it. That is what makes the accordion read as
   rows sliding out from under their predecessor rather than appearing beside it: row N+1 starts at
   translateY(-100%), hidden behind row N's opaque background, and slides down into place. */
.steps li:nth-child(1){z-index:4;}
.steps li:nth-child(2){z-index:3;}
.steps li:nth-child(3){z-index:2;}
.steps li:nth-child(4){z-index:1;}
.steps .num{font-family:var(--mono);font-size:10.5px;letter-spacing:.1em;color:var(--cad);
  padding-top:3px;}
.steps b{font-family:var(--sans);font-weight:600;font-size:14px;color:var(--on-ground);display:block;}
.steps span{font-size:13.5px;color:var(--on-ground-dim);line-height:1.55;display:block;margin-top:4px;}

/* Every pillar fold closes its left column on one bold line. Started as ad-hoc markup on
   Global ("You deal with us. We deal with the offices."); three folds using it makes it a
   component, and a component gets a class so the fourth cannot drift. */
.close{font-family:var(--sans);font-weight:600;font-size:15.5px;line-height:1.5;
  color:var(--on-ground);margin:26px 0 0;}
.close .sep{color:var(--on-ground-dim);font-weight:400;margin:0 .45em;}

/* The hero states the claim; the wordmark above it is the visual apex, and this is the <h1>
   the page has never had. */
.hero h1{font-family:var(--serif);font-style:italic;font-weight:300;
  font-size:clamp(28px,4vw,46px);line-height:1.1;letter-spacing:0;
  margin:clamp(22px,3.4vh,34px) 0 0;text-wrap:balance;}
/* The signature line was also Cormorant italic. Two italics stacked under the wordmark is one
   voice too many, so it steps down to the mono and reads as a mark rather than a second line. */
.sig{font-family:var(--mono);font-style:normal;font-weight:400;font-size:10px;
  letter-spacing:.2em;text-transform:uppercase;color:var(--stone);margin:26px auto 0;}
.sectors{display:flex;flex-wrap:wrap;gap:7px;margin-top:22px;align-items:center;}
.sector{font-family:var(--mono);font-size:10px;letter-spacing:.09em;text-transform:uppercase;
  border:1px solid rgba(236,230,217,.3);color:var(--on-ground-dim);padding:5px 10px;border-radius:2px;}
.sector--more{border-style:dashed;opacity:.8;}

/* ── contact ────────────────────────────────────────────────────────────── */
.cta{display:grid;gap:clamp(30px,4.5vw,60px);grid-template-columns:.9fr 1.1fr;align-items:center;}
@media(max-width:900px){.cta{grid-template-columns:1fr;}}
form{display:grid;gap:13px;}
.row{display:grid;gap:13px;grid-template-columns:1fr 1fr;}
@media(max-width:620px){.row{grid-template-columns:1fr;}}
label{display:grid;gap:6px;font-family:var(--mono);font-size:10px;letter-spacing:.15em;
  text-transform:uppercase;color:var(--on-ground-dim);}
input,select,textarea{font-family:var(--sans);font-size:15px;color:var(--paper);
  background:rgba(7,30,39,.38);border:1px solid rgba(236,230,217,.26);border-radius:2px;padding:10px 13px;width:100%;}
textarea{resize:vertical;min-height:88px;}
select{appearance:none;background-image:linear-gradient(45deg,transparent 50%,var(--stone) 50%),linear-gradient(135deg,var(--stone) 50%,transparent 50%);
  background-position:calc(100% - 19px) 51%,calc(100% - 13px) 51%;background-size:6px 6px;background-repeat:no-repeat;}
input::placeholder,textarea::placeholder{color:rgba(198,188,168,.5);}
.consent{display:grid;grid-template-columns:auto 1fr;gap:11px;align-items:start;
  font-family:var(--sans);font-size:13px;letter-spacing:0;text-transform:none;color:var(--on-ground-dim);line-height:1.55;}
.consent input{width:16px;height:16px;margin-top:2px;accent-color:var(--cad);}
.consent a{color:var(--paper);text-decoration:underline;text-underline-offset:2px;}
button.send{justify-self:start;font-family:var(--mono);font-size:11px;letter-spacing:.18em;text-transform:uppercase;
  background:var(--cad);color:#fff;border:1px solid var(--cad);padding:13px 30px;border-radius:2px;cursor:pointer;}
button.send:hover{background:transparent;color:var(--cad);}
.formnote{font-family:var(--mono);font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:var(--on-ground-dim);margin:0;}
.sent{border:1px solid var(--cad);border-radius:2px;padding:13px 16px;font-size:13.5px;color:var(--paper);background:rgba(211,63,39,.13);}
.contact-list{list-style:none;padding:0;margin:24px 0 0;display:grid;gap:13px;font-size:14px;color:var(--on-ground-dim);}
.contact-list b{display:block;font-family:var(--mono);font-size:10px;letter-spacing:.16em;
  text-transform:uppercase;color:var(--on-ground);margin-bottom:3px;}
/* Set only the '@' in Archivo, whose bowl stays open at small sizes. A no-op where the
   surrounding text is already Archivo, which is why it is applied to every address. */
.at{font-family:var(--sans);font-style:normal;font-weight:400;letter-spacing:0;
  font-size:1.12em;line-height:1;vertical-align:-.04em;}
a.mail{color:var(--paper);text-decoration:underline;text-underline-offset:3px;text-decoration-color:var(--cad);}
a.mail:hover{color:var(--cad);}

/* ── footer ─────────────────────────────────────────────────────────────── */
footer{background:var(--ink);color:var(--stone);padding:clamp(38px,6vh,62px) var(--pad);scroll-snap-align:start;}
.foot{max-width:1180px;margin:0 auto;display:grid;gap:26px;grid-template-columns:auto 1fr auto;align-items:center;}
@media(max-width:760px){.foot{grid-template-columns:1fr;}}
footer .mark{width:42px;height:auto;color:var(--stone);display:block;}
.foot-links{display:flex;flex-wrap:wrap;align-items:center;gap:7px 22px;font-family:var(--mono);
  font-size:10.5px;letter-spacing:.13em;text-transform:uppercase;}
.foot-links button,.foot-links a{background:none;border:0;color:var(--stone);font:inherit;letter-spacing:inherit;
  text-transform:inherit;cursor:pointer;padding:0;text-decoration:none;border-bottom:1px solid rgba(198,188,168,.35);}
.foot-links button:hover,.foot-links a:hover{color:var(--paper);border-bottom-color:var(--cad);}
.foot-meta{font-family:var(--mono);font-size:10px;letter-spacing:.1em;color:var(--slate);text-align:right;}
@media(max-width:760px){.foot-meta{text-align:left;}}

/* ── legal ──────────────────────────────────────────────────────────────── */
.legal{background:var(--paper);color:var(--ink);min-height:70vh;padding:clamp(42px,6vh,80px) var(--pad);}
.legal-wrap{max-width:760px;margin:0 auto;}
.legal h1{font-family:var(--serif);font-style:italic;font-weight:300;font-size:clamp(32px,5vw,52px);color:var(--phthalo);margin:0;}
.legal h2{font-family:var(--sans);font-weight:600;font-size:17px;color:var(--phthalo);margin:36px 0 10px;}
.legal p,.legal li{color:#33474f;font-size:15px;max-width:66ch;}
.legal ul{padding-left:19px;display:grid;gap:7px;}
.legal .meta{font-family:var(--mono);font-size:10px;letter-spacing:.14em;text-transform:uppercase;color:var(--slate);margin:0 0 24px;}
.erase{margin-top:32px;border:1px solid rgba(14,59,76,.3);border-left:3px solid var(--cad);
  border-radius:2px;padding:20px 22px;background:var(--bone);}
.erase h2{margin-top:0;}
table.ck{width:100%;border-collapse:collapse;font-size:13.5px;margin-top:14px;}
table.ck th{text-align:left;font-family:var(--mono);font-size:9.5px;letter-spacing:.13em;text-transform:uppercase;
  color:var(--slate);border-bottom:2px solid var(--phthalo);padding:8px 10px;}
table.ck td{padding:9px 10px;border-bottom:1px solid rgba(14,59,76,.15);color:#33474f;vertical-align:top;}
.tscroll{overflow-x:auto;}

/* ── cookie banner ──────────────────────────────────────────────────────── */
.cookie{position:fixed;left:14px;right:14px;bottom:14px;z-index:90;max-width:660px;margin:0 auto;
  background:var(--paper);color:var(--ink);border:1px solid rgba(7,30,39,.3);border-top:3px solid var(--cad);
  border-radius:2px;padding:20px 22px;box-shadow:0 18px 44px rgba(7,30,39,.45);display:grid;gap:14px;}
.cookie h3{font-family:var(--sans);font-weight:600;font-size:15px;color:var(--phthalo);margin:0;}
.cookie p{font-size:13.5px;color:#3d5159;margin:0;max-width:none;}
.cookie .btns{display:flex;flex-wrap:wrap;gap:9px;}
.cookie button{font-family:var(--mono);font-size:10.5px;letter-spacing:.14em;text-transform:uppercase;
  padding:11px 19px;border-radius:2px;cursor:pointer;border:1px solid var(--phthalo);}
.ck-accept,.ck-reject{background:var(--phthalo);color:var(--paper);}
.ck-config{background:transparent;color:var(--phthalo);}
.cookie button:hover{opacity:.86;}
.granular{display:grid;gap:9px;border-top:1px solid rgba(7,30,39,.16);padding-top:13px;}
.gran-row{display:grid;grid-template-columns:auto 1fr;gap:10px;align-items:start;font-size:13px;color:#3d5159;}
.gran-row input{width:16px;height:16px;margin-top:2px;accent-color:var(--phthalo);}
.gran-row b{display:block;color:var(--ink);font-size:13px;}

/* ── language switch ────────────────────────────────────────────────────────
   NOT from the artifact. Its EN/ES control was review scaffolding — a JS toggle over one
   document — and the build replaces it with two real URLs. But a bilingual site still needs a
   visible way across, so the footer link row carries the pair. Placed here rather than in the
   hero so the approved hero composition is untouched. The current language renders as plain
   text, not a link to itself. */
.lang-switch{display:flex;gap:0;align-items:center;}
.lang-switch .sep{opacity:.45;margin:0 7px;border:0;}
.lang-switch [aria-current="page"]{color:var(--paper);border-bottom-color:transparent;cursor:default;}

/* ── Business Steps entrance: laser cut, accordion, cascading type ──────────
   NOT from the artifact. Alter's spec, `vivhold.md` ln280-288, built against the live site as
   Decision 29 intended.

   The sequence, once per card, the first time its fold is reached:
     0.00s  a white beam traces row 1's perimeter from the top-left corner and back  (.3s)
     0.30s  row 1 pops up and fills with the fold's ground colour
     0.42s  row 2 slides down from behind row 1  |  row 1's text types in
     0.58s  row 3 from behind row 2              |  row 2's text types in
     0.74s  row 4 from behind row 3              |  row 3's text types in
     1.00s  the structure is done — row 4's text types in
        →   the ring fades in, starting from the bottom-right corner

   EVERYTHING HERE IS OPT-IN via .vv-armed, which only landing.js adds. A page without JS, or with
   prefers-reduced-motion, never gets the class and renders the finished card exactly as before —
   the text is real markup, present and readable, not something JS builds. */
/* THE CARD IS NEVER TRANSPARENT. The first build hid it by making the interior and the rows
   transparent, so the photograph showed through the card's footprint — and it read as a pale
   rectangle punched in the fold rather than as a card about to arrive. Alter's call, and he is
   right: a hole in the overlay looks like a rendering fault. The panel is opaque from the first
   frame; what animates is the CONTENT of each row. */
.glowwrap.vv-armed{box-shadow:none;}
.glowwrap.vv-armed::before{opacity:0;animation-play-state:paused;}
.glowwrap.vv-armed .steps{background:transparent;}
/* :not(.lasercut) is load-bearing, not tidiness. The laser SVG is a direct child of the same li as
   the row's content, so a bare `> *` matched it too — and the matching reveal rule below then
   un-hid the laser permanently at (0,4,1) against the (0,3,1) rule meant to hide it. That is the
   white outline Alter caught still sitting on row 1 after the animation finished. */
.glowwrap.vv-armed .steps li > :not(.lasercut){opacity:0;}

/* The cut. A rect path begins at its top-left and runs clockwise, so a dashoffset of 1 -> 0 is
   literally the beam leaving that corner and arriving back at it. pathLength="1" normalises the
   perimeter so the timing is identical whatever the row measures. */
.lasercut{position:absolute;inset:0;width:100%;height:100%;overflow:visible;
  pointer-events:none;z-index:6;opacity:0;}
.lasercut rect{fill:none;stroke:#fff;stroke-width:1.5;vector-effect:non-scaling-stroke;
  stroke-dasharray:1;stroke-dashoffset:1;
  filter:drop-shadow(0 0 3px rgba(255,255,255,.9)) drop-shadow(0 0 7px rgba(255,255,255,.45));}
.steps li.vv-lasing .lasercut{opacity:1;}
.steps li.vv-lasing .lasercut rect{animation:vvCut .3s linear both;}
.steps li.vv-cut .lasercut{opacity:0;transition:opacity .2s ease-out;}
@keyframes vvCut{from{stroke-dashoffset:1;}to{stroke-dashoffset:0;}}

/* Revealed: the row's contents become visible. Text is typed in separately by landing.js, so these
   are visible but empty at this instant. */
.glowwrap.vv-armed .steps li.vv-in > :not(.lasercut){opacity:1;}
.glowwrap.vv-armed.vv-lit .steps{background:rgba(236,230,217,.13);}

/* Row 1's content lifts into the shape the beam just cut. */
.steps li.vv-pop > :not(.lasercut){animation:vvPop .22s cubic-bezier(.2,.9,.3,1) both;}
@keyframes vvPop{
  0%{transform:scale(.97);opacity:0;}
  55%{transform:scale(1.015);opacity:1;}
  100%{transform:scale(1);opacity:1;}
}

/* Rows 2-4 slide out from under their predecessor. No clipping needed and none used: every row is
   OPAQUE and the descending z-index above means row N's own background paints over row N+1's
   content while it is still travelling. That is what makes it emerge from behind rather than
   beside — and it is why the rows themselves no longer move, only what is inside them. */
.steps li.vv-drop > :not(.lasercut){animation:vvDrop .26s cubic-bezier(.22,.85,.28,1) both;}
@keyframes vvDrop{from{transform:translateY(-100%);opacity:0;}to{transform:translateY(0);opacity:1;}}

/* The caret only exists while a row is being typed, so a finished card carries no stray glyph. */
.steps .vv-typing::after{content:"";display:inline-block;width:.5em;height:1em;margin-left:1px;
  background:var(--cad);vertical-align:-.14em;animation:vvCaret .7s steps(1) infinite;}
@keyframes vvCaret{0%,50%{opacity:1;}51%,100%{opacity:0;}}

/* The ring fades in once the last row's text has landed. There is no sparkle: it was in the
   original spec as the point the beam starts from, and Alter cut it on seeing it. --beam-origin
   stays, because it is still the right answer for WHERE the ring begins its travel. */
.glowwrap.vv-beam{box-shadow:0 2px 9px rgba(0,0,0,.26);}
.glowwrap.vv-beam::before{opacity:1;animation-play-state:running;transition:opacity .35s ease-in;}

/* Belt and braces. landing.js already declines to arm under reduced motion; this guarantees the
   finished state even if a card were armed and then the preference changed mid-run. */
@media (prefers-reduced-motion:reduce){
  .glowwrap.vv-armed::before{opacity:1;}
  .glowwrap.vv-armed .steps{background:rgba(236,230,217,.13);}
  .glowwrap.vv-armed .steps li > :not(.lasercut){opacity:1;}
  .lasercut{display:none;}
}

/* ── two-up form rows: align the INPUTS, not the labels ─────────────────────
   NOT from the artifact, and it is a translation defect the English page could never show.
   A `label` is a 2-row grid (caption, then field) and the two labels in a `.row` are stretched
   to equal height — but each lays its own rows out from the top, so a caption that wraps to two
   lines pushes its field down and out of line with its neighbour. "Phone (optional)" fits on one
   line; "Teléfono (opcional)" does not, so the Spanish page sat its phone input 11px below its
   email input while the English page looked perfect.
   `1fr auto` gives the caption the slack and pins the field to the bottom of the label, so the
   fields align no matter how many lines the captions take. Fixes the whole class, in any
   language, rather than shortening one Spanish string to fit. */
.row label{grid-template-rows:1fr auto;}
