/* Self-hosted type. One narrowed Archivo variable file (wdth 74..100, wght
   400..900) serves the homepage and the inner pages; Lora italic is the
   quote voice; Space Mono is the chassis. No third-party CSS on the render
   path. Files live in /fonts, pinned immutable in netlify.toml. */
@font-face{font-family:"Archivo";font-style:normal;font-weight:400 900;font-stretch:74% 100%;font-display:swap;src:url("/fonts/archivo-vf.woff2") format("woff2")}
@font-face{font-family:"Lora";font-style:italic;font-weight:400 700;font-display:swap;src:url("/fonts/lora-italic.woff2") format("woff2")}
@font-face{font-family:"Lora";font-style:normal;font-weight:400 700;font-display:swap;src:url("/fonts/lora.woff2") format("woff2")}
@font-face{font-family:"Space Mono";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/spacemono-400.woff2") format("woff2")}

/* ===========================================================================
   PX PUSH → WILLOW CREEK GROUP
   Hand-written. No framework, no build step, no dependencies.

   Every color, size and spacing figure here was measured off pxpush.com in a
   real browser on 2026-08-15. Where a number looks arbitrary it is because it
   is theirs. See README.md §2 for the measurement table.

   Base font-size is 20px, not 16. Theirs is too. Every rem below assumes it.
   =========================================================================== */

/* The variable names are still --px-*, on purpose: each one names the ROLE it
   plays in PX Push's system, and the value is Willow Creek's answer to that
   role. Reading this block against README.md §2 shows the whole trade at a
   glance. Reasoning in BRAND-BLEND.md §2. */
:root{
  --px-black:      #14130E;   /* deepest ground. --ink, one step down, warm */
  --px-gray:       #1C1B17;   /* body ground. --ink exactly */
  --px-flood:      #2C4033;   /* --willow at flood depth. was #03049C */
  --px-paper:      #F7F4EC;   /* --cream. the one light section */
  --px-text:       #E7E3D7;   /* on dark. off-white with cream in it */
  --px-text-flood: #CBD8CD;   /* on the flood. off-white with willow in it */
  --px-white:      #FFFFFF;
  --px-phosphor:   #7BE8A4;   /* --willow at terminal brightness. status dot */
  --px-sky:        #A9C9E2;   /* --sky, unchanged. cool secondary on dark */
  --px-rust:       #A8503A;   /* --rust. a FILL only, never body text:
                                 on --ink it is 2.6:1. cream on it is 4.8:1 */
  --px-willow:     #5F8266;   /* --willow proper, for rules and edges */
  /* --willow-ink, straight out of CLAUDE.md. Cream on plain --willow is
     3.92:1, under the AA floor, which is the exact pairing that file already
     solved once for willow text on cream. Same hue, pulled down, 5.3:1. */
  --px-willow-ink: #4F6C53;

  /* type. Archivo carries a real wdth axis, which is what stands in for
     SemiSqueezed. 74 is the width that reads closest at display sizes.
     Lora is the addition rather than a substitution: PX Push has no serif
     anywhere, and it is the one face that lets a caller sound like a person
     instead of a log entry. See BRAND-BLEND.md §3. */
  --f-display: 'Archivo', 'Arial Narrow', sans-serif;
  --f-body:    'Archivo', system-ui, sans-serif;
  --f-mono:    'Space Mono', ui-monospace, monospace;
  --f-quote:   'Lora', Georgia, serif;

  /* rhythm, measured */
  --sec-pt: 3.84px;
  --sec-pb: 128px;
  --gut: clamp(16px, 4vw, 64px);
  --maxw: 1600px;
}

*,*::before,*::after{ box-sizing:border-box; }

html{
  font-size:20px;
  background:var(--px-gray);
  scroll-behavior:smooth;
  /* the whole point of the clone is that nothing scrolls sideways, ever */
  overflow-x:hidden;
}

body{
  margin:0;
  font-family:var(--f-body);
  font-size:1rem;
  line-height:1.45;
  color:var(--px-text);
  background:var(--px-gray);
  -webkit-font-smoothing:antialiased;
  overflow-x:clip;
}

img,video,canvas{ max-width:100%; display:block; }
a{ color:inherit; text-decoration:none; }
h1,h2,h3,p,ul,figure{ margin:0; }
ul{ list-style:none; padding:0; }

.monospace{
  font-family:var(--f-mono);
  font-weight:400;
  font-size:max(.6rem,12px);  /* theirs is 10px; 12px is the floor a phone can read */
  letter-spacing:-.032em;     /* -0.64px at 20px, measured */
  text-transform:uppercase;
  line-height:1.4;
}

.wrapper{
  width:100%;
  max-width:var(--maxw);
  margin-inline:auto;
  padding-inline:var(--gut);
}

/* ─────────────────────────── global overlays ───────────────────────────
   Theirs are two raster plates, /img/crt.png at opacity .25 and /img/noise.png.
   Both are painted procedurally in px.js into these elements, which is sharp
   at every DPR, costs no requests, and cannot go soft the way a generated
   scanline PNG does. */

.scanlines,.grain{
  position:fixed;
  inset:0;
  z-index:22;
  pointer-events:none;
}
.scanlines{ opacity:.25; background-image:repeating-linear-gradient(to bottom, rgba(20,19,14,.45) 0 1px, transparent 1px 3px); }
.grain{ opacity:.05; mix-blend-mode:overlay; }

/* ─────────────────────────── header ─────────────────────────── */

.header{
  position:fixed;
  inset:0 0 auto 0;
  z-index:120;              /* above the lifted trade mock at 101 */
  height:78px;              /* theirs is 70; ours carries a bigger status line */
  background:transparent;
  display:flex;
  align-items:center;
}
/* Theirs gets away with a fully transparent header because every section
   behind it is a flat color. Ours runs over a price list and a $950 set in
   64px type, which collided with the nav and the ticker. A soft scrim keeps
   the header legible over anything without reading as a bar. */
.header::before{
  content:'';
  position:absolute; inset:0 0 auto 0;
  height:150%;
  background:linear-gradient(180deg, rgba(10,10,7,.72) 0%, rgba(10,10,7,.34) 55%, transparent 100%);
  pointer-events:none;
  z-index:-1;
}
.header::after{
  content:""; position:absolute; inset:0; z-index:-1;
  background:rgba(14,13,10,.94);
  -webkit-backdrop-filter:blur(10px); backdrop-filter:blur(10px);
  border-bottom:1px solid rgba(231,227,215,.10);
  opacity:0; transition:opacity .25s;
}
.header.is-scrolled::after{ opacity:1; }
section[id],footer[id]{ scroll-margin-top:84px; }
#run-audit{ scroll-margin-top:100px; }
.header__tel{
  flex:0 0 auto; white-space:nowrap;
  display:inline-flex; align-items:center; min-height:44px;
  color:var(--px-white); text-decoration:none;
  font-size:max(.6rem,12px); letter-spacing:.02em;
  padding:.55rem 0; margin-left:.4rem;
}
.header__tel:hover{ color:var(--px-phosphor); }
.header__in{
  display:flex;
  align-items:center;
  gap:var(--gut);
}
/* Both of these need nowrap and shrink-proofing. The ticker is a flex sibling
   that takes every spare pixel, and without them the logo wrapped to three
   lines and the CTA to two the moment it was added. */
/* 21px tall was the whole hit area for the home link on a phone */
.header__logo{ display:flex; align-items:baseline; gap:.4rem; flex:0 0 auto; white-space:nowrap;
  padding-block:.7rem; }
.header__logo--mark{
  font-family:var(--f-display);
  font-variation-settings:'wdth' 74,'wght' 900;
  font-size:1.1rem;
  line-height:1;
  letter-spacing:-.02em;
}
.header__logo--word{
  font-family:var(--f-mono);
  white-space:nowrap;
  font-size:.6rem;
  letter-spacing:-.032em;
  text-transform:uppercase;
  opacity:.7;
}
/* The department status line, running in the dead space between the logo and
   the nav. min-width:0 is load-bearing: without it the flex item refuses to
   shrink below its content width and pushes the nav off the right edge. */
.ticker{
  flex:1 1 auto;
  min-width:0;
  margin-inline:.4rem;
  font-size:.68rem;          /* was .5rem. it carries seven prices. */
  letter-spacing:-.02em;
  opacity:.78;
  mask-image:linear-gradient(90deg,transparent,#000 6%,#000 94%,transparent);
  -webkit-mask-image:linear-gradient(90deg,transparent,#000 6%,#000 94%,transparent);
}
.ticker .marquee__item{ padding-right:0; }
.tsep{ opacity:.4; padding-inline:.55rem; }

.header__nav{ display:flex; gap:1.3rem; flex:0 0 auto; white-space:nowrap; font-size:.62rem; }
.header__nav a{ position:relative; padding-block:.4rem; }
.header__nav a::after{
  content:''; position:absolute; left:0; bottom:.2rem;
  width:100%; height:1px; background:currentColor;
  transform:scaleX(0); transform-origin:right;
  transition:transform .35s cubic-bezier(.22,1,.36,1);
}
.header__nav a:hover::after{ transform:scaleX(1); transform-origin:left; }

.header__cta{
  display:inline-flex; align-items:center; gap:.5rem;
  flex:0 0 auto; white-space:nowrap;
  font-size:.62rem;
  border:1px solid currentColor;
  padding:.5rem .8rem;
  min-height:44px;
  transition:background .3s,color .3s;
}
.header__cta:hover{ background:var(--px-text); color:var(--px-black); }

.dot{
  width:6px; height:6px; border-radius:50%;
  background:var(--px-phosphor);
  box-shadow:0 0 6px var(--px-phosphor);
  animation:pulse 2s ease-in-out infinite;
}
@keyframes pulse{ 0%,100%{opacity:1} 50%{opacity:.35} }

.header__burger{
  display:none;
  /* 44x44: it was 34x34, which is under every thumb-target guideline */
  width:44px; height:44px;
  background:none; border:0; padding:0; cursor:pointer;
  flex-direction:column; justify-content:center; gap:6px;
}
.header__burger span{ display:block; height:1px; background:var(--px-text); transition:transform .3s,opacity .3s; }
.header__burger[aria-expanded="true"] span:first-child{ transform:translateY(3.5px) rotate(45deg); }
.header__burger[aria-expanded="true"] span:last-child{ transform:translateY(-3.5px) rotate(-45deg); }

/* Theirs floods this panel with #03049C; ours floods it with the willow.
   Cream text on a cream panel is the exact bug CLAUDE.md warns about, so the
   color is stated rather than inherited. */
.mobilenav{
  position:fixed; inset:0; z-index:119;
  background:var(--px-flood);
  color:var(--px-white);
  display:grid; place-items:center;
}
.mobilenav[hidden]{ display:none; }
.mobilenav__in{ display:grid; gap:1.2rem; text-align:center; }
.mobilenav__in a{
  /* 44px minimum. These were 35px, and the portal link 13px. */
  min-height:44px;
  display:flex; align-items:center; justify-content:center;
  font-family:var(--f-display);
  font-variation-settings:'wdth' 74,'wght' 900;
  font-size:clamp(2rem,9vw,3.4rem);
  line-height:.96;
  color:var(--px-white);
  display:flex; align-items:center; justify-content:center; gap:.6rem;
}

/* ─────────────────────────── sections ─────────────────────────── */

.section{
  position:relative;
  padding-top:var(--sec-pt);
  padding-bottom:var(--sec-pb);
  isolation:isolate;
}
/* The hero opts out so the trade mock can be lifted clear of the global
   overlays. It carries no .topOverlay, which is the only thing the isolation
   was protecting. */
.section__hero{ isolation:auto; }
.section.no__p{ padding:0; }

.section__paper{ background:var(--px-paper); color:var(--px-black); }
.section__gray { background:var(--px-gray);  color:var(--px-text); }
.section__flood { background:var(--px-flood);  color:var(--px-text-flood); }
.section__black{ background:var(--px-black); color:rgba(231,227,215,.72); }

/* their trick for softening the seam between two sections: a sticky strip
   carrying a gradient of the section's own color, so the band above bleeds
   down into it instead of hard-cutting. */
.topOverlay{
  position:sticky; top:0; z-index:2;
  height:120px; margin-bottom:-120px;
  pointer-events:none;
}
.section__paper .topOverlay{ background:linear-gradient(var(--px-paper) 2%,transparent 100%); }
.section__gray  .topOverlay{ background:linear-gradient(var(--px-gray) 2%,transparent 100%); }
.section__flood  .topOverlay{ background:linear-gradient(var(--px-flood) 2%,transparent 100%); }
.section__black .topOverlay{ background:linear-gradient(var(--px-black) 2%,transparent 100%); }

/* ─────────────────────────── hero ─────────────────────────── */

/* grid-template-columns is stated, and it is minmax(0,1fr) rather than the
   implicit auto column. The hero holds a marquee whose track is thousands of
   pixels wide; an auto column sizes to that max-content and drags every other
   item in the column out with it. That is how .hero__foot came to be 1600px
   wide inside a 375px viewport with its text clipped off-screen. */
.section__hero{
  min-height:100svh;
  display:grid;
  grid-template-columns:minmax(0,1fr);
  grid-template-rows:auto;
  /* Arlo centres the whole split in the viewport. With the headline inside
     the copy column there is no longer a full-bleed row to push to the
     bottom, so the split centres instead of bottoming out. */
  align-content:center;
  padding-block:120px 40px;
  overflow:hidden;
  background:var(--px-black);
}
/* The hero used to sit on a dark ground, so white type was safe anywhere on
   it. Against a midday sky it is not: the terminal blurb and the scroll cue
   were close to invisible. A bottom scrim buys the contrast back and reads
   as haze rather than as a panel. */
.hero__plate{ position:absolute; inset:0; z-index:0; overflow:hidden; }
.sky__poster{ position:absolute; inset:0; width:100%; height:100%; object-fit:cover; display:block; }

.sky__vid{
  position:absolute; inset:0;
  width:100%; height:100%;
  object-fit:cover;
  opacity:0;
  transition:opacity .9s ease;
}
.hero__plate.has-video .sky__vid--day{ opacity:1; }
/* The sunset pass stays at zero opacity for the whole of its life. It is
   never displayed; it exists so drawImage has something to sample. Not
   display:none, which would stop it decoding and leave the squares black. */
.sky__vid--dusk{ opacity:0 !important; }
.sky__reveal{
  position:absolute; inset:0;
  width:100%; height:100%;
  pointer-events:none;
}
/* A left-hand scrim, so the copy column always has a ground to sit on. The
   plate's only scrim ran along the bottom, which was right when the copy sat
   in a bottom strip. The copy column is full height on the left now, over the
   brightest part of the sky, and the headline's second line and most of the
   terminal blurb were sitting at roughly 1.5:1. This fades out before the
   mock, which is opaque anyway, so the sky is untouched where it is seen. */
.hero__plate::before{
  content:''; position:absolute; inset:0; z-index:1;
  background:linear-gradient(100deg,
    rgba(10,12,18,.86) 0%,
    rgba(10,12,18,.72) 26%,
    rgba(10,12,18,.38) 46%,
    transparent 64%);
  pointer-events:none;
}
.hero__plate::after{
  content:''; position:absolute; inset:auto 0 0 0; height:52%; z-index:1;
  background:linear-gradient(180deg, transparent 0%, rgba(12,14,22,.30) 46%, rgba(10,12,18,.72) 100%);
  pointer-events:none;
}

/* The floor under the shader: what shows before the first frame, with WebGL
   unavailable, or under reduced motion. Same midday palette the shader
   starts from, so the handover is invisible. */
.hero__plate.is-missing{
  background:
    radial-gradient(120% 55% at 50% 96%, rgba(255,255,255,.55) 0%, transparent 62%),
    linear-gradient(180deg,#0E3C85 0%,#2E63A6 42%,#A9C9E2 78%,#C9DCEC 100%);
}

/* Arlo sets the second half of its headline in a serif italic. Lora italic
   is already this system's "a person is speaking" face, so the accent is
   WCG's own type doing that job rather than a borrowed one. */
.hero__title em{
  font-family:var(--f-quote);
  font-variation-settings:normal;
  font-style:italic; font-weight:400;
  color:#D6E6F3;
  letter-spacing:-.005em;
}
.hero__title{
  position:relative; z-index:3;
  min-width:0;
  font-family:var(--f-display);
  font-variation-settings:'wdth' 74,'wght' 900;
  /* Sized so "built to bring in business." holds one line in this column,
     which is what makes the <br> land the same two-line break Arlo's does.
     The old 6.4vw was set for a full-bleed row and broke into four lines
     here, with the last one stranded on bright cloud. */
  font-size:clamp(1.75rem,3.5vw,2.6rem);
  text-shadow:0 2px 10px rgba(8,10,16,.5);
  line-height:1.04;
  letter-spacing:-.014em;
  color:var(--px-white);
  margin:0 0 .5rem;
}

/* The badge started centered on the hero and landed straight on top of the
   headline, which read as a mistake rather than a layer. It sits below the
   type now, in the gap between the headline and the footer row. */
.hero__badge{
  position:absolute;
  left:50%; top:68%;
  translate:-50% -50%;
  z-index:2;
  /* a laptop is a wide object where the coin was a round one, so it needs
     more of the hero than the mark did */
  /* sized to the render's own 1200x759 rather than a square canvas */
  width:min(46vw,560px);
  aspect-ratio:1200/759;
  perspective:900px;
  pointer-events:none;
}
.prop__canvas{ width:100%; height:100%; }
/* The shadow lives out here, on the element that only carries perspective.
   A filter on a preserve-3d element forces the browser to flatten it, which
   kills the rotation entirely, so it can go neither on .machine nor on the
   faces. */
.hero__badge::before{
  content:""; position:absolute; left:10%; right:10%; bottom:-4%; height:22%;
  background:radial-gradient(ellipse at center, rgba(8,10,16,.48) 0%, rgba(8,10,16,.18) 45%, transparent 72%);
  filter:blur(10px); z-index:-1; pointer-events:none;
}

.machine{
  position:relative;
  width:100%;
  will-change:transform;
}
/* The still carries the hero until the video proves it can decode. Swapping
   on canplay rather than on a feature guess means Safari, save-data and a
   failed fetch all land on the same working fallback. */
.machine__turn{ display:none; width:100%; height:auto; }
.machine__still{ width:100%; height:auto; display:block; }
.machine.is-live .machine__turn{ display:block; }
.machine.is-live .machine__still{ display:none; }

/* minmax(0,1fr), not 1fr. A grid item's default min-width is auto, so the
   blurb's max-content width won: measured at 1358px inside a 375px viewport,
   clipped out of sight by the global overflow-x rather than wrapped. The
   overflow sweep passed the whole time because the clip hid it. */
.hero__foot{
  position:relative; z-index:3;
  display:grid;
  grid-template-columns:192px minmax(0,1fr);
  gap:var(--gut);
  align-items:end;
}

.hero__mini{
  display:block;
  width:192px; height:192px;    /* measured. square, not rounded. */
  border-radius:0;
  overflow:hidden;
  position:relative;
  background:#0B0B0B;
  border:1px solid rgba(255,255,255,.18);
  transition:border-color .35s;
}
.hero__mini:hover{ border-color:rgba(255,255,255,.5); }
.hero__mini:focus-visible{ outline:2px solid var(--px-phosphor); outline-offset:3px; }

/* The reel is the live site, translated upward on a continuous loop so it
   reads as somebody scrolling the page. A video would be heavier, softer at
   this size, and would not loop cleanly. */
.hero__mini--reel{
  display:block; position:absolute; inset:0;
  animation:reel 46s linear infinite;
}
.hero__mini--reel img{ width:100%; height:auto; display:block; }
/* -50% of the STACK, which is one full copy, so the second copy is exactly
   where the first started and the wrap cannot be seen. */
@keyframes reel{
  from { transform:translateY(0); }
  to   { transform:translateY(-50%); }
}
.hero__mini:hover .hero__mini--reel{ animation-play-state:paused; }

.hero__mini--flag{
  position:absolute; left:0; top:0; z-index:1;
  padding:.3rem .45rem;
  background:var(--px-rust);
  color:var(--px-paper);
}

.hero__mini--label{
  position:absolute; left:0; right:0; bottom:0;
  padding:.35rem .45rem;
  background:linear-gradient(0deg, rgba(10,10,7,.92), rgba(10,10,7,.62));
  color:var(--px-white);
}

.visually-hidden{
  position:absolute; width:1px; height:1px;
  margin:-1px; padding:0; overflow:hidden;
  clip-path:inset(50%); white-space:nowrap;
}

.hero__foot--r{ justify-self:end; display:grid; gap:1rem; justify-items:end; max-width:46ch; min-width:0; }
.hero__blurb,.hero__tag{
  max-width:46ch;
  width:100%;
  color:var(--px-white);
  text-shadow:0 1px 3px rgba(8,10,16,.75);
  font-size:max(.62rem,12.5px);
  line-height:1.7;
  overflow-wrap:anywhere;
  margin:0;
}
.hero__cta{ display:flex; flex-wrap:wrap; gap:.6rem; justify-content:flex-end; }
.btn--hero{ width:auto; font-size:max(.62rem,13px); padding:.9rem 1.15rem; }
.hero__cta .btn--solid{ background:var(--px-paper); color:var(--px-black); border-color:var(--px-paper); }
.hero__cta .btn--solid:hover{ background:var(--px-phosphor); border-color:var(--px-phosphor); color:var(--px-black); }
.quoteform .btn--solid{ background:var(--px-black); color:var(--px-paper); border-color:var(--px-black); }
.quoteform .btn--solid:hover{ background:var(--px-willow); border-color:var(--px-willow); }
.hero__cta .btn:not(.btn--solid){ color:var(--px-white); opacity:1; }
/* The two rules used to be eighteen literal hyphens each. That is a single
   unbreakable token, which first blew the grid out; overflow-wrap:anywhere
   fixed the overflow but let the run break mid-way, so a line could begin with
   a fragment of dashes and the whole thing read as junk. Drawn hairlines take
   whatever width is left over and cannot wrap at all. */
/* The box comes from the shared rule above, deliberately: with nowrap this
   flex line shrinks to its content, and any box of its own then lets it drift
   out of line with the copy it heads. */
.hero__tag{
  display:flex; align-items:center; gap:.6rem;
  margin:0 0 .55rem;
  white-space:nowrap;
}
.hero__rule{ flex:1 1 1.4rem; min-width:1.4rem; height:1px; background:currentColor; opacity:.42; }

/* Pinned to the right edge at mid-height, the headline marquee ran straight
   over the top of it and ate the first two words. It moved below the headline
   and above the footer row, where nothing used to be. The trade mock now
   occupies that right-hand column, so it moves again: static, at the top of
   the copy column, where it points down the page past the copy it belongs to. */
.hero__cue{
  z-index:4;
  display:flex; gap:.6rem;
  color:var(--px-white);
  opacity:.7;
  margin-top:.4rem;
}
.hero__cue span::after{ content:''; }

/* ─────────────── the hero split, and the trade mock ───────────────
   Arlo's hero is a split: brand copy one side, an animated website mock the
   other. The pattern is theirs; nothing else is. The frame is dark glass on
   this page's own ground, the tabs are .btn chrome rather than cream pills,
   and every mock inside is original DOM.

   Everything inside .mock__screens is sized in container query units, the
   same rule the rescue frame lives under: it scales to the FRAME, not the
   viewport. Do not convert these to vw. */

/* .section.no__p zeroes the hero's padding, so without this the mock frame's
   bottom border sits exactly on the fold and reads as a cut-off image rather
   than a browser window. */
.hero__split{
  /* z-index:auto, NOT a number. A numeric z-index here creates a stacking
     context and traps .hero__mock's z-index:101 inside level 3, which is how
     the scanline and vignette layers kept painting over the trade mock. The
     element still positions above .hero__plate by paint order. */
  position:relative; z-index:auto;
  display:grid;
  grid-template-columns:minmax(0,1fr) minmax(0,.86fr);
  gap:var(--gut);
  /* the copy column is shorter than the mock, so it centres against it
     rather than sitting on its baseline */
  align-items:center;
  padding-block:2.2rem;
}
.hero__copy{ display:grid; gap:1.2rem; justify-items:start; min-width:0; align-content:end;
  position:relative; z-index:3; }
.hero__copy .hero__blurb{ justify-self:start; max-width:44ch; }
.hero__copy .hero__cta{ justify-content:flex-start; }
/* .btn--solid carries width:100% for the audit form, where it is a submit
   button in a single-column grid. In the hero it is one of two buttons in a
   row, and 100% of the copy column made it 401px wide next to a 180px
   sibling. */
.hero__cta .btn--solid{ width:auto; }

.hero__mock{ min-width:0; display:grid; gap:.7rem; align-content:end;
  width:100%; max-width:560px; justify-self:end;
  /* clear of .scanlines (22), .gloom (23) and .vignette (100): the mock is
     a finished client site and must not be textured by this page's chrome */
  position:relative; z-index:101; }
.mock__label{ color:var(--px-white); opacity:.72; }

.mock__tabs{ display:flex; flex-wrap:wrap; gap:.4rem; }
.mock__tab{
  font-family:var(--f-mono);
  font-size:max(.58rem,12px);
  letter-spacing:-.032em;
  text-transform:uppercase;
  padding:.55rem .9rem;
  min-height:44px;
  border:1px solid rgba(231,227,215,.34);
  background:rgba(10,10,7,.42);
  color:var(--px-white);
  cursor:pointer;
  transition:background .3s,border-color .3s,color .3s,transform .3s;
}
.mock__tab:hover{ border-color:rgba(255,255,255,.7); transform:translateY(-1px); }
.mock__tab:focus-visible{ outline:2px solid var(--px-phosphor); outline-offset:2px; }
/* the active tab takes --rust, the same accent the "our most recent project"
   flag uses, so the hero has one accent rather than two */
.mock__tab[aria-selected="true"]{
  background:var(--px-rust); border-color:var(--px-rust); color:var(--px-paper);
}

.mock__frame{
  border:1px solid rgba(231,227,215,.28);
  border-radius:3px;
  overflow:hidden;
  background:#0B0B0B;
  box-shadow:0 30px 70px -20px rgba(0,0,0,.8);
}
.mock__bar{
  display:flex; align-items:center; gap:8px;
  padding:.45rem .6rem;
  background:#14130E;
  border-bottom:1px solid rgba(231,227,215,.14);
}
.mock__url{
  flex:1; min-width:0;
  margin-left:.4rem; padding:.25rem .6rem;
  background:rgba(231,227,215,.08);
  border-radius:2px;
  color:rgba(231,227,215,.66);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
/* aspect-ratio, not a height: the panels are absolutely positioned so the
   frame cannot resize between trades, which is what would make the swap read
   as the page jumping rather than a site loading. */
.mock__screens{
  position:relative;
  aspect-ratio:1/1.06;
  container-type:inline-size;
  background:var(--px-paper);
}

/* ---- the four mock client sites ----
   Rebuilt to the standard of a genuinely expensive small-business homepage.
   The moves that get you there are the same ones the premium prompt libraries
   use, minus the ones that would stop a plumber looking like a plumber:

     full-bleed photography as the HERO, not a side tile
     display type at three or four times the body size
     dark, rich grounds so the photograph carries the light
     one accent, used on exactly one thing per screen
     and a panel that looks like working software, not a picture of one

   No glass, no gradient mesh, no 3D: CLAUDE.md records the frosted-card build
   as tried and rejected, and those styles sell SaaS, not trades.

   Sized in container query units throughout: scales to the FRAME, not the
   viewport. Do not convert to vw. */

.ts{
  position:absolute; inset:0;
  display:flex; flex-direction:column;
  overflow:hidden;
  opacity:0; visibility:hidden;
  transition:opacity .3s ease;
  font-family:var(--f-body);
  /* the four values that decide whether a mock is a dark page or a light one */
  --ink:#FFFFFF;
  --dim:rgba(255,255,255,.74);
  --line:rgba(255,255,255,.16);
  --panelBg:rgba(10,14,18,.82);
}
.ts.is-active{ opacity:1; visibility:visible; }

/* per-trade ground and accent */
/* Two dark, two light, and four different temperatures. A plumber's site and
   a salon's site should not look like the same page recoloured, which is what
   four dark grounds had quietly turned them into. */
.ts--pl  { background:#0B1A26; --acc:#8FC3E8; --accInk:#06121C; }          /* deep navy, night */
.ts--el  { background:#0C0F09; --acc:#9BE8B8; --accInk:#0C0F09; }          /* near-black, technical */
.ts--cafe{                                                                  /* warm paper, daylight */
  background:#F7F0E4; --ink:#241609; --acc:#A15A1B; --accInk:#FFF7EC;
  --dim:rgba(36,22,9,.70); --line:rgba(36,22,9,.16); --panelBg:rgba(255,255,255,.78);
}
.ts--sal {                                                                  /* blush, airy */
  background:#F7F1F2; --ink:#2B1A28; --acc:#8D4F6C; --accInk:#FBF5F7;
  --dim:rgba(43,26,40,.68); --line:rgba(43,26,40,.15); --panelBg:rgba(255,255,255,.80);
}

/* ---- the photograph, full bleed, with only as much grade as the type needs ---- */
.px__shot{
  position:absolute; inset:0; width:100%; height:100%;
  object-fit:cover; display:block; z-index:0;
}
.px__shot--in{ position:absolute; }
.ts--pl::after,.ts--sal::after{
  content:''; position:absolute; inset:0; z-index:1; pointer-events:none;
  background:linear-gradient(180deg,
    rgba(6,14,20,.62) 0%, rgba(6,14,20,.24) 30%,
    rgba(6,14,20,.74) 66%, rgba(6,14,20,.95) 100%);
}
.ts--sal::after{
  background:linear-gradient(180deg,
    rgba(247,241,242,.42) 0%, rgba(247,241,242,.20) 26%,
    rgba(247,241,242,.86) 62%, rgba(247,241,242,.99) 100%);
}
.ts > *:not(.px__shot){ position:relative; z-index:2; }

/* ---- nav ---- */
.px__nav{
  display:flex; align-items:center; justify-content:space-between; gap:2cqw;
  padding:2.6cqw 3.4cqw;
}
.px__nav--over{ position:absolute; inset:0 0 auto 0; z-index:3; }
.px__mark{
  display:flex; align-items:center; gap:1.1cqw;
  font-family:var(--f-display); font-variation-settings:'wdth' 88,'wght' 900;
  font-size:2.5cqw; color:var(--ink); letter-spacing:-.005em; white-space:nowrap;
}
.px__dot{ width:2.4cqw; height:2.4cqw; border-radius:50%; background:var(--acc); flex:none; }
.px__navr{ display:flex; align-items:center; gap:2.2cqw; }
.px__link{
  font-family:var(--f-mono); font-size:1.5cqw; letter-spacing:.1em;
  text-transform:uppercase; color:var(--dim);
}
.px__cta{
  background:var(--acc); color:var(--accInk);
  padding:1.1cqw 2.2cqw; border-radius:2px;
  font-family:var(--f-mono); font-size:1.5cqw; font-weight:700;
  letter-spacing:.1em; text-transform:uppercase; white-space:nowrap;
}

/* ---- the statement. This is the single biggest lever on "expensive". ---- */
.px__mid{ margin-top:auto; padding:0 3.4cqw 2.6cqw; display:grid; gap:1.4cqw; justify-items:start; }
.px__mid--centre{ justify-items:center; text-align:center; padding-inline:5cqw; }
.px__eyebrow{
  font-family:var(--f-mono); font-size:1.45cqw;
  letter-spacing:.24em; text-transform:uppercase; color:var(--acc);
}
.px__h{
  font-family:var(--f-display); font-variation-settings:'wdth' 82,'wght' 900;
  font-size:7.2cqw; line-height:.94; letter-spacing:-.028em;
  color:var(--ink); margin:0;
  text-shadow:0 2px 22px rgba(0,0,0,.45);
}
.ts--cafe .px__h,.ts--sal .px__h{ text-shadow:none; }
/* the italic half, the way the reference sets its headlines */
.px__h em{
  font-family:var(--f-quote); font-style:italic; font-weight:400;
  font-variation-settings:normal; letter-spacing:-.012em; color:var(--acc);
}
.px__sub{
  font-size:1.95cqw; line-height:1.5; color:var(--dim); margin:0; max-width:34ch;
  display:flex; align-items:center; gap:1cqw;
}
.px__sub i{ font-style:normal; color:var(--acc); letter-spacing:.06em; }

/* ---- the working panel. This is the "function" half. ---- */
.px__panel{
  margin:0 3.4cqw 3.4cqw;
  padding:2.4cqw;
  background:var(--panelBg);
  border:1px solid var(--line);
  border-radius:3px;
  display:grid; gap:1.6cqw;
  box-shadow:0 24px 50px -22px rgba(0,0,0,.45);
}
.px__panel--flat{ margin:0; border-radius:0; border-inline:0; border-bottom:0; box-shadow:none; background:var(--panelBg); }
.px__panel--wide{ margin-inline:3.4cqw; }
.px__plbl{
  font-family:var(--f-mono); font-size:1.4cqw;
  letter-spacing:.2em; text-transform:uppercase; color:var(--dim); margin:0;
}

/* day picker */
.px__days{ display:grid; grid-template-columns:repeat(4,1fr); gap:1cqw; }
.px__day{
  display:grid; gap:.2cqw; justify-items:center;
  padding:1.2cqw .6cqw; border-radius:2px;
  border:1px solid var(--line);
  color:var(--dim);
}
.px__day b{ font-family:var(--f-mono); font-size:1.3cqw; letter-spacing:.1em; text-transform:uppercase; }
.px__day i{ font-style:normal; font-family:var(--f-display); font-variation-settings:'wdth' 90,'wght' 800; font-size:2.2cqw; color:var(--ink); }
.px__day.is-on{ background:var(--acc); border-color:var(--acc); color:var(--accInk); }
.px__day.is-on i{ color:var(--accInk); }

/* slots */
.px__slots{ display:flex; flex-wrap:wrap; gap:1cqw; }
.px__slot{
  padding:1.1cqw 1.8cqw; border-radius:2px;
  border:1px solid var(--line);
  font-family:var(--f-mono); font-size:1.45cqw; color:var(--dim);
  display:grid; gap:.2cqw;
}
.px__slot b{ font-size:1.45cqw; letter-spacing:.08em; text-transform:uppercase; }
.px__slot i{ font-style:normal; font-size:1.3cqw; color:var(--dim); }
.px__slot.is-on{ background:var(--acc); border-color:var(--acc); color:var(--accInk); }
.px__slot.is-on i{ color:rgba(0,0,0,.62); }
.px__slot--gone{ opacity:.34; text-decoration:line-through; }
.px__panel--wide .px__slots{ display:grid; grid-template-columns:repeat(3,1fr); }

/* pills + quantity */
.px__row{ display:flex; align-items:center; gap:1cqw; flex-wrap:wrap; }
.px__pill{
  padding:1cqw 1.9cqw; border-radius:50px;
  border:1px solid var(--line);
  font-family:var(--f-mono); font-size:1.4cqw; letter-spacing:.08em;
  color:var(--dim); white-space:nowrap;
}
.px__pill.is-on{ background:var(--acc); border-color:var(--acc); color:var(--accInk); }
.px__qty{
  margin-left:auto; display:flex; align-items:center; gap:1.4cqw;
  border:1px solid var(--line); border-radius:50px; padding:.7cqw 1.4cqw;
  font-family:var(--f-mono); font-size:1.5cqw; color:var(--ink);
}
.px__qty b{ color:var(--acc); }
.px__qty i{ font-style:normal; min-width:1.4cqw; text-align:center; }

/* the instant answer an estimate tool gives */
.px__quote{
  display:flex; align-items:baseline; justify-content:space-between; gap:1.4cqw;
  padding:1.6cqw 0; border-block:1px solid var(--line);
}
.px__qn{ font-family:var(--f-mono); font-size:1.4cqw; letter-spacing:.14em; text-transform:uppercase; color:var(--dim); }
.px__qv{ font-family:var(--f-display); font-variation-settings:'wdth' 86,'wght' 900; font-size:3.1cqw; color:var(--acc); }

/* the commit */
.px__go{
  background:var(--acc); color:var(--accInk);
  padding:1.5cqw 2.2cqw; border-radius:2px; text-align:center;
  font-family:var(--f-mono); font-size:1.6cqw; font-weight:700;
  letter-spacing:.08em; display:flex; align-items:center; justify-content:center; gap:.8cqw;
}
.px__go b{ font-weight:700; }
.px__note{
  margin:0; font-family:var(--f-mono); font-size:1.3cqw;
  letter-spacing:.06em; color:var(--dim);
  display:flex; align-items:center; gap:.8cqw; flex-wrap:wrap;
}
.px__note i{ font-style:normal; color:var(--acc); }

/* ---- cafe: a band, not a split ---- */
.px__band{
  position:relative; overflow:hidden;
  aspect-ratio:16/7.4;      /* close to the photograph's own shape */
  flex:none;
}
.px__band .px__shot{ position:absolute; object-position:58% 56%; }
.px__band::after{
  content:''; position:absolute; inset:0;
  /* only as much grade as the stamp needs; the page below is paper now */
  background:linear-gradient(180deg, rgba(30,18,12,.24) 0%, transparent 40%, rgba(30,18,12,.55) 100%);
}
.px__stamp{
  position:absolute; left:3.4cqw; bottom:2.4cqw; z-index:2;
  font-family:var(--f-mono); font-size:1.3cqw; letter-spacing:.18em;
  text-transform:uppercase; color:rgba(255,255,255,.9);
  text-shadow:0 1px 6px rgba(0,0,0,.8);
}
.px__body{ display:flex; flex-direction:column; gap:1.8cqw; padding:3cqw 3.4cqw 2cqw; min-width:0; flex:1; }
.px__body .px__h{ font-size:6.4cqw; }
.px__board{ display:grid; gap:1.1cqw; margin-top:auto; }
.px__board span{
  display:flex; align-items:baseline; justify-content:space-between; gap:1.4cqw;
  font-family:var(--f-quote); font-size:1.9cqw; color:var(--dim);
  padding-bottom:.9cqw; border-bottom:1px solid var(--line);
}
.px__board i{ font-style:normal; color:var(--acc); }

/* ---- electrician: photo over the top ---- */
.px__top{ position:relative; overflow:hidden; flex:1; min-height:0; display:flex; }
.px__top .px__shot{ position:absolute; }
.px__top::after{
  content:''; position:absolute; inset:0; z-index:1; pointer-events:none;
  background:linear-gradient(180deg, rgba(12,15,9,.66) 0%, rgba(12,15,9,.18) 34%, rgba(12,15,9,.94) 100%);
}
.px__top > *{ position:relative; z-index:2; }
/* The overlay nav has to win against the line above, which is (0,1,0) and lands
   later than `.px__nav--over`, so the electrician's header lost
   position:absolute, became an in-flow flex child, stretched to the full 484px
   photo and centred its own contents. The brand row floated at the vertical
   middle of the hero image and stole half the flex row, which is what crammed
   the headline into a 204px column.

   Fix it by ADDING a more specific rule, not by narrowing the one above:
   `.px__top > *:not(.px__nav--over)` is (0,2,0) because :not() carries its
   argument's specificity, which then ties with `.px__top .px__shot` and wins
   by order, stripping position:absolute off the photograph instead. */
.px__top > .px__nav--over{ position:absolute; inset:0 0 auto 0; z-index:3; }

/* ═════════════════ the motion layer ═════════════════
   What the premium prompt libraries actually sell is not glass, it is the fact
   that their pages are never completely still. This adds that life to the four
   mocks WITHOUT the styling that would stop a plumber's site looking like a
   plumber's site: no glass, no gradient mesh, no 3D.

   Four pieces, all of them cheap and all of them compositor-only:
     1. a slow light drifting across each hero
     2. the photographs settle out of a slight scale as the site finishes
     3. a sheen that travels the accent bars once, when they land
     4. one easing curve, used everywhere, instead of four different ones

   Every one is inside a prefers-reduced-motion guard at the bottom of the file
   and none of them animate layout, so nothing here costs a frame. */

.ts{ --ease-soft:cubic-bezier(.22,1,.36,1); }

/* ---- 1. the drifting light ----
   A wide, very low-contrast sweep that crosses each mock's hero on a long
   loop. It reads as a room the light moves through, not as an effect. */
@keyframes mock-drift{
  0%   { transform:translate3d(-18%,0,0); }
  100% { transform:translate3d(18%,0,0); }
}
.px__mid,.px__body{ position:relative; }
.ts--pl::before,.ts--cafe::before,.ts--sal::before,.ts--el::before{
  content:'';
  position:absolute; top:-40%; bottom:-40%; left:-30%; right:-30%;
  z-index:1;
  pointer-events:none;
  background:radial-gradient(42% 60% at 50% 50%,
    var(--drift, rgba(255,255,255,.11)) 0%, transparent 68%);
  animation:mock-drift 13s ease-in-out infinite alternate;
}
/* the light has to match the room it is in */
.ts--cafe{ --drift:rgba(120,70,20,.09); }
.ts--sal { --drift:rgba(90,50,80,.07); }
.ts--el  { --drift:rgba(155,232,184,.10); }
/* it sits above the photograph and below everything that has to be read */
.ts--pl::before,.ts--cafe::before,.ts--sal::before,.ts--el::before{ z-index:1; }

/* ---- 2. the photographs settle ----
   They already fade in with the build. They now come to rest out of a small
   scale as well, so the finished state feels arrived-at rather than switched
   on. Transform only: no layout, no reflow. */
.px__shot{ transition:opacity .4s ease, transform .9s var(--ease-soft); }
.hero__mock:is([data-stage="0"],[data-stage="1"]) .px__shot{ opacity:0; transform:scale(1.06); }
.hero__mock[data-stage="2"] .px__shot{ transform:scale(1.025); }

/* ---- 3. the sheen ----
   Travels each accent bar once, on the beat the brand colour arrives. One
   pass, not a loop: a bar that shines forever is a casino, a bar that shines
   once is a finish. */
@keyframes mock-sheen{
  0%   { transform:translateX(-120%); opacity:0; }
  18%  { opacity:1; }
  100% { transform:translateX(120%); opacity:0; }
}
.px__go{ position:relative; overflow:hidden; }
.hero__mock[data-stage="3"] .px__go::after,
.hero__mock[data-stage="4"] .px__go::after{
  content:'';
  position:absolute; inset:0;
  pointer-events:none;
  background:linear-gradient(100deg, transparent 30%,
    rgba(255,255,255,.28) 50%, transparent 70%);
  animation:mock-sheen 1.5s var(--ease-soft) .15s 1 both;
}


/* ---- 4. one curve, everywhere ---- */
.ts.is-active > *{
  transition:opacity .34s var(--ease-soft), transform .34s var(--ease-soft);
}

/* ---- the staged build ----
   The reference hero does not fade its mock in, it BUILDS it: wireframe, then
   the copy lands, then the brand colour arrives, then the polish. That is the
   thing that earns the line "watch a site take shape", and the mechanism is
   worth having. Five stages on .hero__mock[data-stage]:

     0  nothing placed
     1  blocks land, still grey, every run of text is a skeleton bar
     2  the bars resolve into real copy
     3  the brand colour floods in
     4  polish: the frame gains its shadow and the padlock appears

   The attribute SHIPS AT 4, so with px.js deleted or broken a visitor sees a
   finished site and never a wireframe. px.js tradeMock() drives the stages. */

/* the bar colour follows the ground it sits on, light-on-dark or dark-on-light */
/* every mock sits on a dark ground now, so the bar is always the light one */
.ts{ --sk:rgba(255,255,255,.24); }

/* Every run of text across the four mocks. Add a mock's text here or it simply
   skips the wireframe stage rather than breaking. */
.hero__mock[data-stage="0"] :is(
  .px__mark,.px__link,.px__cta,.px__eyebrow,.px__h,.px__h em,.px__sub,
  .px__plbl,.px__day b,.px__day i,.px__slot,.px__slot b,.px__slot i,
  .px__pill,.px__qty,.px__qn,.px__qv,.px__go,.px__note,
  .px__board span,.px__board i,.px__stamp,.px__cap,.px__slate
),
.hero__mock[data-stage="1"] :is(
  .px__mark,.px__link,.px__cta,.px__eyebrow,.px__h,.px__h em,.px__sub,
  .px__plbl,.px__day b,.px__day i,.px__slot,.px__slot b,.px__slot i,
  .px__pill,.px__qty,.px__qn,.px__qv,.px__go,.px__note,
  .px__board span,.px__board i,.px__stamp,.px__cap,.px__slate
){
  color:transparent;
  background:var(--sk);
  border-color:transparent;
  border-radius:3px;
}
/* Copy snaps out of the bar rather than fading, and the colour snaps in. Both
   were transitions, which made the FINISHED state depend on an animation
   completing; when it did not, the mock sat as a wireframe forever. The motion
   in this build comes from the block stagger, the photo settle, the sheen, the
   drift and the typing URL, none of which can strand content. */

/* stage 0: nothing placed yet. Transitions, not animations: a transition that
   never runs leaves the element at its FINAL value, so a stalled clock shows a
   finished site. An animation with fill-mode backwards would show nothing. */
.ts.is-active > *{
  transition:opacity .34s cubic-bezier(.22,1,.36,1), transform .34s cubic-bezier(.22,1,.36,1);
}
.hero__mock[data-stage="0"] .ts.is-active > *{ opacity:0; transform:translateY(10px); }
.hero__mock[data-stage="0"] .ts.is-active > *:nth-child(2){ transition-delay:.03s; }
.hero__mock[data-stage="0"] .ts.is-active > *:nth-child(3){ transition-delay:.06s; }
.hero__mock[data-stage="0"] .ts.is-active > *:nth-child(4){ transition-delay:.09s; }
.hero__mock[data-stage="0"] .ts.is-active > *:nth-child(5){ transition-delay:.12s; }
.hero__mock[data-stage="0"] .ts.is-active > *:nth-child(6){ transition-delay:.15s; }

/* stages 1-2: placed and readable, not yet branded. One filter does what would
   otherwise be four sets of per-mock colour overrides, and it greys the
   photographs along with everything else, which is exactly right: an unbuilt
   page has its pictures in place but not yet its colour. */
.hero__mock:is([data-stage="0"],[data-stage="1"],[data-stage="2"]) .ts{ filter:grayscale(1); }


/* the photographs themselves land with the copy, one beat before the colour */
.px__shot{
  transition:opacity .4s ease, transform .55s cubic-bezier(.22,1,.36,1);
}
.hero__mock:is([data-stage="0"],[data-stage="1"]) .px__shot{
  opacity:0; transform:scale(1.04);
}

/* stage 4: the polish a finished site has and a draft does not */
.mock__frame{
  transition:box-shadow .42s ease, transform .42s cubic-bezier(.22,1,.36,1);
  transform:translateZ(0);
}
.hero__mock[data-stage="4"] .mock__frame{
  box-shadow:0 40px 90px -22px rgba(0,0,0,.85), 0 10px 26px -12px rgba(0,0,0,.55);
  transform:translateY(-4px);
}
/* the caret that follows the URL while px.js types it */
.mock__url.is-typing::after{
  content:'';
  display:inline-block;
  width:.45em; height:1em;
  margin-left:1px;
  vertical-align:-.15em;
  background:currentColor;
  animation:mock-caret .6s steps(1) infinite;
}
@keyframes mock-caret{ 50%{ opacity:0; } }

.mock__lock{
  display:inline-flex; align-items:center; flex:none;
  width:12px; height:12px; margin-left:.4rem;
  color:var(--px-phosphor);
  opacity:0; transform:scale(.7);
  transition:opacity .3s ease, transform .3s cubic-bezier(.22,1,.36,1);
}
.hero__mock[data-stage="4"] .mock__lock{ opacity:1; transform:none; }
.mock__lock svg{ width:100%; height:100%; }


/* ─────────────────────────── process steps ─────────────────────────── */

.steps{
  display:grid;
  grid-template-columns:repeat(auto-fit,minmax(min(230px,100%),1fr));
  gap:1px;
  margin-top:3rem;
  background:rgba(231,227,215,.20);
  border:1px solid rgba(231,227,215,.20);
}
.steps li{
  background:var(--px-flood);
  padding:1.8rem 1.4rem;
  display:grid; gap:.6rem; align-content:start;
}
.steps__num{ color:var(--px-sky); }
.steps__hd{
  font-family:var(--f-display);
  font-variation-settings:'wdth' 86,'wght' 900;
  font-size:clamp(1.3rem,2.6vw,1.8rem);
  line-height:1; letter-spacing:-.015em;
  color:var(--px-white); margin:0;
}
.steps li p{ font-size:.86rem; line-height:1.5; margin:0; }

/* ─────────────────────────── what's included ─────────────────────────── */

.inclist{
  display:grid;
  grid-template-columns:repeat(auto-fit,minmax(min(240px,100%),1fr));
  gap:.55rem 1.4rem;
  margin-top:.6rem;
}
.inclist li{ display:flex; gap:.6rem; }
.inclist li::before{ content:'+'; color:var(--px-willow-ink); }
.inclist__note{ margin-top:1.2rem; font-size:.86rem; color:rgba(20,19,14,.7); }
/* .layout__body caps at 52ch, which is right for a paragraph and wrong for a
   two-column checklist: it forced the list into one column at every width. */
#included .layout__body{ max-width:74ch; }

.auditform__trust{
  display:flex; flex-wrap:wrap; gap:.1rem .2rem;
  color:rgba(28,27,23,.72);
}
.auditform__trust .tsep{ opacity:.55; }

/* ─────────────────────────── layout: third + body ─────────────────────────── */

.layout{
  display:grid;
  grid-template-columns:1fr 2fr;
  gap:var(--gut);
  padding-top:5rem;
}
.layout__third{ align-self:start; position:sticky; top:90px; }
.layout__body{ display:grid; gap:1.1rem; max-width:52ch; }

.lead{ font-size:1.15rem; line-height:1.35; }
.lead--em{
  font-family:var(--f-quote);
  font-style:italic;
  font-size:1.3rem;
  opacity:.85;
}

/* The one serif on the page, and the whole reason for the blend. A caller's
   sentence set in Space Mono becomes a log entry; set in Lora italic inside
   the same terminal chassis, the page argues both halves at once. One per
   section, maximum. BRAND-BLEND.md §3. */
.callout{
  font-family:var(--f-quote);
  font-style:italic;
  font-size:clamp(1.25rem,2.6vw,1.85rem);
  line-height:1.28;
  max-width:24ch;
  margin-top:1.6rem;
  padding-left:1.1rem;
  border-left:2px solid var(--px-willow);
}
.callout cite{
  display:block;
  margin-top:.9rem;
  font-family:var(--f-mono);
  font-size:.5rem;
  font-style:normal;
  letter-spacing:-.032em;
  text-transform:uppercase;
  opacity:.65;
}

.h2{
  font-family:var(--f-display);
  font-variation-settings:'wdth' 82,'wght' 900;
  font-size:clamp(1.7rem,4.4vw,3.2rem);
  line-height:1;
  letter-spacing:-.015em;
  max-width:16ch;
}

/* the full-width uppercase block that closes each of their sections */
.statement{
  font-family:var(--f-display);
  font-variation-settings:'wdth' 88,'wght' 500;
  font-size:clamp(1.05rem,2.9vw,2rem);
  line-height:1.12;
  text-transform:uppercase;
  letter-spacing:-.01em;
  margin-block:6rem 0;
  padding-inline:var(--gut);
  max-width:1600px;
  margin-inline:auto;
  opacity:.85;
}

/* ─────────────────────────── buttons ─────────────────────────── */

.btnrow{ display:flex; flex-wrap:wrap; gap:.6rem; margin-top:1.2rem; }
.btn{
  display:inline-flex; align-items:center; gap:.5rem;
  font-family:var(--f-mono);
  font-size:max(.62rem,12.5px);
  letter-spacing:-.032em;
  text-transform:uppercase;
  padding:.85rem 1.1rem;
  border:1px solid currentColor;
  background:transparent;
  color:inherit;
  cursor:pointer;
  transition:background .3s,color .3s;
}
.btn:hover{ background:currentColor; }
.btn:hover span,.btn:hover{ mix-blend-mode:normal; }
.section__flood .btn:hover,.section__black .btn:hover,.section__gray .btn:hover{ background:var(--px-white); color:var(--px-black); }
.section__paper .btn:hover{ background:var(--px-black); color:var(--px-paper); }
.btn--ghost{ opacity:.85; }
.btn--block{ width:100%; justify-content:space-between; margin-top:auto; }

/* ─────────────────────────── marquee ─────────────────────────── */

.marquee{
  display:block;
  width:100%;
  overflow:hidden;
  white-space:nowrap;
}
.marquee__track{
  display:inline-flex;
  will-change:transform;
}
.marquee__item{
  display:inline-flex; align-items:center; gap:.35em;
  padding-right:.35em;
}
.bullet{ font-size:.42em; translate:0 -.22em; }

.marquee--band{
  font-family:var(--f-display);
  font-variation-settings:'wdth' 74,'wght' 900;
  font-size:clamp(2.4rem,9vw,6.4rem);
  line-height:1;
  letter-spacing:-.02em;
  color:var(--px-white);
  margin-block:4rem;
}
.section__paper .marquee--band{ color:var(--px-black); }
.marquee--foot{
  font-family:var(--f-display);
  font-variation-settings:'wdth' 74,'wght' 900;
  font-size:clamp(3rem,14vw,9rem);
  line-height:.9;
  color:var(--px-white);
  opacity:.14;
  margin-top:3rem;
}

/* ─────────────────────────── audit ───────────────────────────
   The lead magnet. Everything else on this page can afford to be moody; this
   block cannot. It is the one element a plumber has to understand and act on
   in about four seconds, so it is cream on the dark section, the fields look
   like fields, and the sample report sits beside the form so the offer is
   visible before anything is asked for. */

.audit{
  margin-top:3rem;
  display:grid;
  grid-template-columns:1.05fr .95fr;
  gap:0;
  background:var(--px-paper);
  color:var(--px-gray);
  border:1px solid var(--px-paper);
  box-shadow:0 24px 60px rgba(0,0,0,.45);
}

.audit__ask{ padding:clamp(1.6rem,3.4vw,2.6rem); }

.audit__flags{ display:flex; flex-wrap:wrap; gap:.4rem; margin-bottom:1.2rem; }
.audit__flags span{
  border:1px solid rgba(28,27,23,.28);
  padding:.3rem .55rem;
  color:var(--px-gray);
}
.audit__flags span:first-child{
  background:var(--px-willow-ink); border-color:var(--px-willow-ink); color:var(--px-paper);
}

.audit__hd{
  font-family:var(--f-display);
  font-variation-settings:'wdth' 84,'wght' 900;
  font-size:clamp(1.6rem,3.4vw,2.5rem);
  line-height:1.02;
  letter-spacing:-.018em;
  color:var(--px-black);
  max-width:16ch;
}

.audit__steps{
  display:grid; gap:.65rem;
  margin:1.4rem 0 1.6rem;
  padding-top:1.2rem;
  border-top:1px solid rgba(28,27,23,.16);
  max-width:46ch;
}
.audit__steps li{ display:grid; grid-template-columns:1.6rem 1fr; gap:.7rem; align-items:start; }
.audit__steps b{
  font-family:var(--f-mono); font-size:.5rem; letter-spacing:-.032em;
  width:1.6rem; height:1.6rem; display:grid; place-items:center;
  background:var(--px-gray); color:var(--px-paper); border-radius:50%;
}

/* Fields that read as fields. The old build was a bare underline on
   near-black, which is the reason this section did not look like a form. */
.auditform{ display:grid; gap:.9rem; }
.auditform__row{ display:grid; grid-template-columns:1fr 1fr; gap:.8rem; }
.auditform__field{ display:grid; gap:.4rem; }
.auditform__field > span{ color:rgba(28,27,23,.78); }
.auditform__field input{
  font-family:var(--f-body);
  font-size:1rem;
  color:var(--px-black);
  background:#FFFFFF;
  border:1px solid rgba(28,27,23,.34);
  border-radius:2px;
  padding:.75rem .8rem;
  width:100%;
  transition:border-color .2s, box-shadow .2s;
}
.auditform__field input::placeholder{ color:rgba(28,27,23,.42); }
.auditform__field input:hover{ border-color:rgba(28,27,23,.6); }
.auditform__field input:focus-visible{
  outline:none;
  border-color:var(--px-willow);
  box-shadow:0 0 0 3px rgba(95,130,102,.28);
}

.btn--solid{
  background:var(--px-gray);
  color:var(--px-paper);
  border-color:var(--px-gray);
  justify-content:center;
  padding:1rem 1.2rem;
  font-size:max(.65rem,13px);
  width:100%;
}
.btn--solid:hover{ background:var(--px-willow); border-color:var(--px-willow); color:var(--px-paper); }

.auditform__note{
  font-size:.72rem;
  line-height:1.45;
  color:rgba(28,27,23,.74);
  max-width:46ch;
}

/* ---- the sample report, carried over from the live homepage ---- */

.report{
  padding:clamp(1.6rem,3.4vw,2.6rem);
  background:#EFEBE0;
  border-left:1px solid rgba(28,27,23,.14);
  display:grid;
  align-content:start;
  gap:.8rem;
}
.report__hd{ display:flex; gap:1rem; justify-content:space-between; align-items:flex-start; }
.report__lbl{ color:var(--px-rust); }
.report__biz{
  font-family:var(--f-display);
  font-variation-settings:'wdth' 90,'wght' 800;
  font-size:1.15rem; line-height:1.1; margin-top:.3rem;
  color:var(--px-black);
}
.report__meta{ color:rgba(28,27,23,.74); margin-top:.35rem; }

.ring{
  width:112px; height:112px; flex:none;
  border-radius:50%;
  display:grid; place-items:center;
  position:relative;
  background:conic-gradient(var(--px-willow) calc(var(--pct) * 1%), rgba(28,27,23,.14) 0);
}
.ring::after{ content:''; position:absolute; inset:9px; background:#EFEBE0; border-radius:50%; }
/* b and i were both grid children of the ring and centered independently, so
   the label sat on the stroke instead of under the number. One stack now. */
.ring__in{
  position:relative; z-index:1;
  display:grid; place-items:center; gap:.1rem;
  line-height:1;
}
.ring b{
  font-family:var(--f-display);
  font-variation-settings:'wdth' 90,'wght' 900;
  font-size:1.65rem; line-height:1; color:var(--px-black);
}
.ring i{
  font-style:normal; font-size:.42rem; letter-spacing:.02em;
  color:rgba(28,27,23,.74);
}

.report__finding{
  border-top:1px solid rgba(28,27,23,.16);
  padding-top:.8rem;
}
.report__finding h5{
  font-size:.92rem; font-weight:700; margin:0 0 .3rem;
  color:var(--px-black);
  display:flex; gap:.45rem; align-items:baseline;
}
.report__finding h5::before{ content:'!'; color:var(--px-rust); font-weight:900; }
.report__finding p{ font-size:.8rem; line-height:1.45; color:rgba(28,27,23,.78); }
.report__finding b{ color:var(--px-black); }

.report__ft{
  border-top:1px solid rgba(28,27,23,.16);
  padding-top:.8rem;
  color:rgba(28,27,23,.74);
  display:grid; gap:.5rem;
}
.report__ft--note{ font-size:.76rem; line-height:1.45; }

.checks-hd{ margin-top:3rem; color:rgba(231,227,215,.7); }
.checks{
  margin-top:.8rem;
  display:grid;
  grid-template-columns:repeat(auto-fit,minmax(min(240px,100%),1fr));
  gap:1px;
  background:rgba(231,227,215,.14);
  border:1px solid rgba(231,227,215,.14);
}
.checks li{
  background:var(--px-gray);
  padding:1.1rem;
  display:grid; gap:.5rem;
  align-content:start;
}
.checks li span{ opacity:.5; }

/* ─────────────────────────── benefits accordion ─────────────────────────── */
/* Theirs are five full-bleed rows, 1280x397 each, driven by a ScrollAccordion.
   Sticky stacking gives the same read with no library and no scroll hijack:
   each card pins, the next slides over it. */

.benefits{ margin-top:4rem; }
.benefits__item{
  position:sticky;
  top:90px;
  /* Theirs measure 397px with a filled media column. Ours has no imagery
     yet, so a hard 397 left a third of every card empty. Clamped, and the
     16:9 media will take it back to roughly theirs once the plates land. */
  min-height:clamp(250px,32vh,397px);
  display:grid;
  grid-template-columns:8rem minmax(0,1fr) 34%;
  grid-template-rows:auto auto;
  column-gap:var(--gut);
  row-gap:0;
  align-items:start;
  /* Rows stretch to fill a grid taller than its content, and the media used
     to claim a third row it never filled, so the body copy was pushed most of
     a card away from its own heading. */
  align-content:start;
  padding:2.4rem var(--gut);
  background:var(--px-flood);
  border-top:1px solid rgba(255,255,255,.28);
  overflow:hidden;
}
.benefits__num{ opacity:.6; }
.benefits__hd{
  font-family:var(--f-display);
  font-variation-settings:'wdth' 82,'wght' 900;
  font-size:clamp(1.5rem,3.6vw,2.6rem);
  line-height:1;
  letter-spacing:-.015em;
  color:var(--px-white);
  grid-column:2; grid-row:1;
}
.benefits__body{
  grid-column:2; grid-row:2;
  max-width:42ch;
  margin-top:1.1rem;
}
.benefits__media{
  grid-column:3; grid-row:1 / span 2;
  aspect-ratio:16/9;
  overflow:hidden;
  background:rgba(0,0,0,.25);
}
.benefits__media img{ width:100%; height:100%; object-fit:cover; }
.benefits__media:empty{ display:none; }


/* ─────────────────────────── tools ───────────────────────────
   Four rows rather than cards. These are things that keep running, not
   things to compare, so a price grid would be the wrong shape. */
.tools{ margin-top:3rem; border-top:1px solid rgba(231,227,215,.18); }
.tool{
  display:grid; grid-template-columns:1fr 2fr; gap:var(--gut);
  padding:1.8rem 0; border-bottom:1px solid rgba(231,227,215,.18);
  align-items:start;
}
.tool__name{ color:var(--px-white); display:grid; gap:.4rem; }
.tool__price{ color:var(--px-sky); opacity:.85; }
.tool p:last-child{ max-width:52ch; }

@media (max-width:860px){
  .tool{ grid-template-columns:1fr; gap:.7rem; }
}

/* ─────────────────────────── pricing ─────────────────────────── */

.section__pricing{ overflow:hidden; }
/* Theirs is a fixed 1280x720 canvas sitting behind the plan cards, not a
   full-section layer. Bounding it here keeps the WebGL backing store sane:
   stretched over the whole section it was asking for a 2530x4176 buffer.

   Centered, it sat directly behind two opaque plan cards and was invisible in
   every screenshot. It lives in the section's right-hand negative space now,
   beside the heading block and behind the sparse price rows. */
.pricing__prop{
  position:sticky;
  top:26%;
  height:0;
  z-index:0;
  pointer-events:none;
  opacity:.78;
  display:flex;
  justify-content:flex-end;
}
/* the rows ride over the disc, and they are transparent, so it reads through */
.pricelist{ position:relative; z-index:1; }
.pricing__prop canvas{
  width:min(620px,46vw);
  height:min(620px,46vw);
}
@media (max-width:1100px){
  .pricing__prop{ justify-content:center; padding-right:0; opacity:.3; }
  .pricing__prop canvas{ width:min(520px,80vw); height:min(520px,80vw); }
}
/* the stale full-bleed sizing rule that used to live here is deleted, not
   overridden: same specificity as the sized rule above and later in the
   file, so it silently won and collapsed the canvas to 1028x0. */
.section__pricing > .wrapper,
.section__pricing > .marquee,
.section__pricing > .statement{ position:relative; z-index:1; }

.plans{
  display:grid;
  /* min() so the track floor can never exceed the viewport: a bare 320px
     floor is wider than a 305px content box at the 320 breakpoint. */
  grid-template-columns:repeat(auto-fit,minmax(min(320px,100%),1fr));
  gap:1px;
  margin-top:4rem;
  background:rgba(255,255,255,.16);
  border:1px solid rgba(255,255,255,.16);
}
.plan{
  background:var(--px-black);
  padding:2rem;
  display:flex; flex-direction:column; gap:1rem;
  position:relative;
}
.plan--popular{ background:#1F1E19; }
.plan--wide{ margin-top:4rem; border:1px solid rgba(255,255,255,.16); }
/* --rust is a fill, never text: #A8503A on the ground is 2.6:1, while
   cream sitting ON it is 4.8:1. BRAND-BLEND.md §2. */
.plan__flag{
  position:absolute; right:2rem; top:2rem;
  background:var(--px-rust); color:var(--px-paper);
  padding:.3rem .55rem;
}
.plan__name{ font-size:.6rem; color:var(--px-white); }
.plan__price{
  font-family:var(--f-display);
  font-variation-settings:'wdth' 78,'wght' 900;
  font-size:clamp(2.4rem,6vw,4rem);
  line-height:.95;
  letter-spacing:-.02em;
  color:var(--px-white);
  display:flex; align-items:baseline; gap:.4rem;
}
.plan__from{
  font-family:var(--f-mono);
  font-size:.5rem; letter-spacing:-.032em; text-transform:uppercase;
  opacity:.6;
}
.plan__sub{ max-width:34ch; }
.plan__list{ display:grid; gap:.55rem; padding-top:1rem; border-top:1px solid rgba(255,255,255,.16); }
.plan__list li{ display:flex; gap:.6rem; }
.plan__list li::before{ content:'+'; opacity:.45; }
.plan__list--2col{ grid-template-columns:repeat(auto-fit,minmax(min(220px,100%),1fr)); }

.pricelist{
  margin-top:3rem;
  border-top:1px solid rgba(255,255,255,.16);
}
.pricelist li{
  display:flex; justify-content:space-between; gap:1rem;
  padding:.9rem 0;
  border-bottom:1px solid rgba(255,255,255,.16);
}
.pricelist b{ color:var(--px-white); }

/* ─────────────────────────── FAQ ─────────────────────────── */

.faq{ margin-top:3rem; border-top:1px solid rgba(255,255,255,.16); }
.faq__item{ border-bottom:1px solid rgba(255,255,255,.16); }
.faq__item summary{
  display:grid;
  grid-template-columns:5rem 1fr 2rem;
  align-items:center;
  gap:1rem;
  padding:1.4rem 0;
  cursor:pointer;
  list-style:none;
}
.faq__item summary::-webkit-details-marker{ display:none; }
.faq__q{
  font-family:var(--f-display);
  font-variation-settings:'wdth' 88,'wght' 600;
  font-size:clamp(1.05rem,2.4vw,1.6rem);
  line-height:1.1;
  color:var(--px-white);
}
.faq__item summary i{
  position:relative; width:14px; height:14px; justify-self:end;
}
.faq__item summary i::before,.faq__item summary i::after{
  content:''; position:absolute; inset:50% 0 auto 0;
  height:1px; background:currentColor;
  transition:transform .35s cubic-bezier(.22,1,.36,1);
}
.faq__item summary i::after{ transform:rotate(90deg); }
.faq__item[open] summary i::after{ transform:rotate(0deg); }
.faq__a{ padding:0 0 1.6rem; }
.faq__a p{ max-width:62ch; margin-left:6rem; }

/* ─────────────────────────── footer ─────────────────────────── */

.footer{ padding-bottom:0; overflow:hidden; }
.footer__in{
  display:flex; flex-wrap:wrap; gap:var(--gut);
  justify-content:space-between;
  padding-top:5rem;
  color:var(--px-white);
}
.footer__links{ display:grid; gap:.5rem; }
.footer__links a{ padding:.25rem 0; }
.footer__contact a{ color:var(--px-white); text-decoration:none; border-bottom:1px solid rgba(231,227,215,.35); }
.footer__contact a:hover{ color:var(--px-phosphor); }
.mobilenav__portal{
  font-family:var(--f-mono) !important; font-variation-settings:normal !important;
  font-size:max(.7rem,14px) !important; letter-spacing:.02em; opacity:.85;
  min-height:44px; padding-block:.6rem;
}
.mobilenav__tel{
  font-family:var(--f-mono) !important; font-variation-settings:normal !important;
  font-size:max(.8rem,16px) !important; letter-spacing:.02em;
  padding:.7rem 1.2rem; border:1px solid rgba(231,227,215,.45); margin-bottom:.6rem;
}
.quoteform{ margin-top:1.4rem; display:grid; gap:1rem; }
.quoteform select{ width:100%; font:inherit; color:inherit; background:transparent; border:1px solid currentColor; padding:.8rem .9rem; border-radius:0; }
.quoteform .auditform__note a{ color:inherit; }
.footer__tz{ opacity:.6; margin-bottom:.5rem; }


/* ─────────────────────────── Nº006 work ─────────────────────────── */
.works{ display:grid; grid-template-columns:1fr 1fr; gap:var(--gut); margin-top:3rem; }
.work{ display:flex; flex-direction:column; gap:1.1rem; min-width:0; }
.work__shot{
  position:relative; display:block; overflow:hidden;
  background:#0B0B0B; border:1px solid rgba(231,227,215,.18);
  padding-bottom:7%;
  transition:border-color .35s;
}
.work__shot:hover{ border-color:rgba(255,255,255,.5); }
.work__shot:focus-visible{ outline:2px solid var(--px-phosphor); outline-offset:3px; }
.work__desk{ display:block; width:100%; height:auto; }
.work__phone{
  position:absolute; right:5%; bottom:0; width:22%; height:auto;
  border:2px solid #0B0B0B; border-bottom:0;
  box-shadow:0 18px 40px -14px rgba(0,0,0,.85);
}
.work__name{ color:var(--px-white); margin:0 0 .5rem; }
.work__what{ color:var(--px-text); font-size:.86rem; line-height:1.5; margin:0 0 1rem; max-width:46ch; }
@media (max-width:860px){
  .works{ grid-template-columns:1fr; gap:2.4rem; }
  .work__phone{ width:26%; }
}

/* ─────────────────── the pointer-lit background ───────────────────
   Replaces the mesh cursor. The gradient lives here; px.js only moves the
   light, by writing --mx/--my/--gr. Blend mode is `screen` so it lifts the
   ground rather than painting a gray disc on top of it, which is the
   difference between the page being lit and the page being smeared. */

.gloom{
  position:fixed; inset:0;
  z-index:23;                 /* above .scanlines at 22, as on theirs */
  pointer-events:none;
  mix-blend-mode:screen;
  opacity:0;
  transition:opacity .6s ease;
  background:radial-gradient(var(--gr,340px) circle at var(--mx,50%) var(--my,50%),
    rgba(169,201,226,.20) 0%,
    rgba(123,232,164,.09) 34%,
    rgba(169,201,226,.03) 56%,
    transparent 72%);
}
.gloom.is-on{ opacity:1; }

/* static, and last over everything, so the corners never feel open */
.vignette{
  position:fixed; inset:0;
  z-index:100;
  pointer-events:none;
  background:radial-gradient(120% 90% at 50% 45%,
    transparent 52%, rgba(10,10,7,.34) 100%);
}

/* ─────────────────────────── reveals ─────────────────────────── */

[data-split] .word{
  display:inline-block;
  translate:0 .5em;
  opacity:0;
  transition:translate .7s cubic-bezier(.22,1,.36,1),opacity .7s;
  transition-delay:calc(var(--i) * 18ms);
}
[data-split].is-in .word{ translate:0 0; opacity:1; }



/* ═══════════════ PORTED: THE RESCUE SLIDER ═══════════════
   Lifted from the live homepage (Saturday/index.html) essentially unchanged.
   CLAUDE.md calls this "the signature piece" and warns that everything inside
   the frame is sized in container query units so it scales to the frame, not
   the viewport: DO NOT convert these to vw.

   Only the section chrome was rewritten for this page's design system. The
   two fake websites inside the browser frame are untouched. */

/* The ported rules reference the original brand tokens, so those five are
   bridged onto the blend rather than find-and-replaced through 140 lines of
   working CSS. */
.rescue{
  --cream:var(--px-paper);
  --wire:var(--px-black);
  --sky:var(--px-sky);
  --rust:var(--px-rust);
  --rule:rgba(231,227,215,.16);
}

/* Scroll distance is height minus one viewport, so 200vh gives 100vh of travel.
   With the tighter hold margins below, the wipe itself takes about 86vh. */
.rescue-stage{height:200vh;position:relative;margin-top:50px;}
.rescue-pin{position:sticky;top:0;height:100vh;display:flex;align-items:center;justify-content:center;padding:0 32px;}
.rescue-pin > div{width:min(1000px,100%);}
.browser{
  width:min(1000px,100%);border-radius:9px;overflow:hidden;position:relative;
  box-shadow:0 40px 90px -25px rgba(0,0,0,.75);background:#fff;
}
.browser-bar{
  background:#E3E0D8;padding:10px 14px;display:flex;align-items:center;gap:8px;
  border-bottom:1px solid #cdc8ba;
}
.dot{width:11px;height:11px;border-radius:50%;flex:none;}
.browser-url{
  flex:1;background:#fff;border-radius:20px;padding:4px 14px;margin-left:8px;
  font-family:'Space Mono',monospace;font-size:11px;color:#66635a;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0;
}
.screens{position:relative;aspect-ratio:16/9.4;overflow:hidden;container-type:inline-size;}
.screen{position:absolute;inset:0;overflow:hidden;}

/* --- the BEFORE site (deliberately awful) --- */
.before{background:#dfe3d8;font-family:'Times New Roman',serif;padding:0;}
.before .b-marquee{background:#000080;color:#FFFF00;font-size:13px;padding:4px 0;overflow:hidden;white-space:nowrap;}
.before .b-marquee span{display:inline-block;animation:slide-l 11s linear infinite;font-family:'Comic Sans MS','Chalkboard SE',cursive;}
@keyframes slide-l{from{transform:translateX(100%);}to{transform:translateX(-100%);}}
.before .b-inner{width:74%;margin:0 auto;background:#fff;border:3px ridge #808080;padding:14px 16px;text-align:center;}
.before .b-h{font-family:'Comic Sans MS','Chalkboard SE',cursive;color:#000080;font-size:clamp(15px,2.6vw,26px);margin:6px 0 3px;text-shadow:1px 1px 0 #aaa;}
.before .b-sub{font-size:clamp(9px,1.35vw,13px);color:#444;margin:0 0 10px;font-style:italic;}
.before .b-hr{height:4px;background:repeating-linear-gradient(90deg,#c00 0 8px,#00c 8px 16px);margin:9px 0;}
.before .b-row{display:flex;gap:8px;justify-content:center;flex-wrap:wrap;margin:10px 0;}
.before .b-pill{background:#c0c0c0;border:2px outset #eee;padding:2px 9px;font-size:clamp(8px,1.15vw,11px);}
.before .b-note{font-size:clamp(8px,1.2vw,12px);color:#222;line-height:1.35;max-width:44ch;margin:8px auto;}
.before .b-cons{font-family:'Comic Sans MS',cursive;color:#c00;font-size:clamp(9px,1.4vw,14px);font-weight:bold;}
.before .b-hits{font-family:'Courier New',monospace;font-size:clamp(8px,1.1vw,11px);color:#333;background:#000;color:#0f0;display:inline-block;padding:2px 7px;margin-top:8px;}
.before .b-menu{background:#333;color:#ddd;font-size:clamp(7px,1vw,10px);padding:10px;margin:9px auto;width:56%;transform:rotate(-2.2deg);filter:blur(.4px) brightness(.62);text-align:left;line-height:1.5;}

/* --- the AFTER site: what a real build looks like --- */
.after{background:#FBF8F1;color:#1A1713;display:flex;flex-direction:column;font-family:'Lora',serif;}
.af-util{
  background:#1A1713;color:#D8CEBE;display:flex;justify-content:space-between;
  padding:0.95cqw 3.2cqw;font-family:'Space Mono',monospace;font-size:1.05cqw;
  letter-spacing:.14em;text-transform:uppercase;
}
.af-util .gold{color:#C39A5C;}
.af-nav{
  display:flex;align-items:center;justify-content:space-between;
  padding:2.1cqw 3.2cqw;border-bottom:1px solid #E4DCCB;
}
.af-nav .mark{font-family:'Lora',serif;font-size:2.15cqw;letter-spacing:.30em;font-weight:600;}
.af-nav .lnk{display:flex;gap:2.6cqw;font-family:'Space Mono',monospace;font-size:1.05cqw;letter-spacing:.19em;text-transform:uppercase;color:#6B6353;}
.af-nav .res{
  background:#1A1713;color:#FBF8F1;padding:0.95cqw 2.1cqw;
  font-family:'Space Mono',monospace;font-size:1.02cqw;letter-spacing:.19em;text-transform:uppercase;
}
.af-hero{flex:1;display:grid;grid-template-columns:53% 47%;}
.af-left{padding:3.4cqw 3.2cqw 2.4cqw;display:flex;flex-direction:column;justify-content:center;}
.af-eyebrow{
  font-family:'Space Mono',monospace;font-size:1cqw;letter-spacing:.26em;text-transform:uppercase;
  color:#9C8A6B;margin:0 0 1.5cqw;
}
.af-left .af-h{
  font-family:'Lora',serif;font-style:italic;font-weight:400;
  font-size:4.5cqw;line-height:1.06;letter-spacing:-.015em;margin:0 0 1.8cqw;
}
.af-left h6 b{font-style:normal;font-weight:600;color:#8A5A2B;}
.af-left .sub{font-size:1.42cqw;line-height:1.6;color:#5A5347;margin:0 0 2.4cqw;max-width:30ch;}
.af-btns{display:flex;gap:1cqw;margin-bottom:2.6cqw;}
.af-b1{background:#8A5A2B;color:#FBF8F1;padding:1.15cqw 2.4cqw;font-family:'Space Mono',monospace;font-size:1.05cqw;letter-spacing:.17em;text-transform:uppercase;}
.af-b2{border:1px solid #C9BEA8;color:#1A1713;padding:1.15cqw 2.4cqw;font-family:'Space Mono',monospace;font-size:1.05cqw;letter-spacing:.17em;text-transform:uppercase;}
.af-rate{display:flex;align-items:center;gap:1cqw;padding-top:1.7cqw;border-top:1px solid #E4DCCB;font-family:'Space Mono',monospace;font-size:1.02cqw;letter-spacing:.11em;color:#6B6353;}
.af-rate .st{color:#C39A5C;letter-spacing:.14em;}
.af-right{position:relative;overflow:hidden;}
/* The real dish. The dark fallback gradient stays underneath so the frame
   never sits empty while the image loads, and the bottom grade returns as an
   inset shadow so the gold caption keeps its floor on a bright photograph. */
.af-photo{
  position:absolute;inset:0;
  background:linear-gradient(158deg,#3A2C20 0%,#241B14 46%,#120D09 100%);
}
.af-photo img{
  position:absolute;inset:0;width:100%;height:100%;
  object-fit:cover;object-position:50% 42%;display:block;
}
.af-photo::before{
  content:"";position:absolute;inset:0;z-index:1;
  background:radial-gradient(78% 52% at 50% 108%, rgba(12,9,6,.78), transparent 64%);
}
.af-photo::after{
  content:"";position:absolute;inset:0;opacity:.22;mix-blend-mode:overlay;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='3'/%3E%3C/filter%3E%3Crect width='120' height='120' filter='url(%23n)'/%3E%3C/svg%3E");
}
.af-frame{position:absolute;inset:2.1cqw;border:1px solid rgba(217,186,133,.42);pointer-events:none;}
.af-cap{
  position:absolute;left:3.4cqw;bottom:3.2cqw;color:#E8DCC6;
  font-family:'Space Mono',monospace;font-size:.98cqw;letter-spacing:.19em;text-transform:uppercase;
}
.af-cap i{display:block;font-style:normal;color:#C39A5C;margin-bottom:.4cqw;}

/* wipe seam */
.seam{position:absolute;top:0;bottom:0;width:3px;background:var(--sky);z-index:5;box-shadow:0 0 22px rgba(169,201,226,.85);}
.seam::after{
  content:"";position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);
  width:40px;height:40px;border-radius:50%;background:var(--sky);
  box-shadow:0 0 26px rgba(169,201,226,.65);
}
.seam i{
  position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);z-index:2;
  font-family:'Space Mono',monospace;font-size:15px;font-weight:700;color:var(--wire);font-style:normal;
}
.rescue-cap{
  display:flex;justify-content:space-between;gap:20px;max-width:1000px;
  margin:22px auto 0;font-family:'Space Mono',monospace;font-size:11px;
  letter-spacing:.16em;text-transform:uppercase;
}
/* --rust is a FILL, never text: #A8503A on this ground measures 2.6:1 and
   this caption was using it as a label colour. Same hue, lifted to carry
   type on a dark section. */
.rescue-cap .c1{color:#D98B72;}
.rescue-cap .c2{color:var(--sky);}
/* the stacked-mobile scaffolding is inert above the 760px breakpoint */
.only-m{display:none;}
.rescue-out{padding:70px 0 110px;text-align:center;}
.rescue-out p{font-size:clamp(20px,2.9vw,34px);font-style:italic;max-width:22ch;margin:0 auto 26px;line-height:1.3;}


/* the ported stage sits inside this page's section rhythm */
.rescue-stage{ margin-top:3rem; }
.rescue-out{ padding:0; text-align:left; }
.only-d{ display:inline; }

/* ═══════════════ PORTED: THE CALLS ═══════════════
   CLAUDE.md: "The strongest asset on this site is the caller quotes. Protect
   that voice. Don't smooth it into marketing language." The words are
   verbatim; only the typography moved, onto Lora italic, which is what
   BRAND-BLEND.md reserves for a person speaking. */

.calls{ margin-top:3rem; }
.call{
  display:grid; grid-template-columns:1fr 1fr; gap:var(--gut);
  align-items:center; padding:2.4rem 0;
  border-top:1px solid rgba(231,227,215,.20);
}
.call:nth-child(even) .call-q{ order:2; }
.call-q p{
  font-family:var(--f-quote);
  font-style:italic;
  font-size:clamp(1.15rem,2.5vw,1.85rem);
  line-height:1.26; letter-spacing:-.012em;
  color:var(--px-white);
  margin:0;
}
.call-a .name{
  font-family:var(--f-mono); font-size:.5rem; letter-spacing:.16em;
  text-transform:uppercase; color:var(--px-sky); margin:0 0 .7rem;
}
.call-a p{ font-size:.86rem; line-height:1.5; color:var(--px-text-flood); margin:0; }

@media (max-width:860px){
  .call{ grid-template-columns:1fr; gap:1rem; }
  .call:nth-child(even) .call-q{ order:0; }
}

@media (max-width:760px){
  .only-d{ display:none; }
  /* ---- rescue slider: a thumb-driven wipe, no scroll hijack ----
     Normal page flow (no sticky, no 200vh stage), but the two sites overlay
     like desktop and a drag on the frame moves the seam. touch-action:pan-y is
     the whole trick: vertical swipes still scroll the page, horizontal ones
     wipe. The frame runs full-bleed and takes most of the viewport height.
     With JS off or dead, the CSS below holds a readable 50/50 split.
     .screens keeps container-type:inline-size, so cqw units are untouched. */
  .rescue-stage{height:auto;}
  .rescue-pin{position:static;height:auto;display:block;padding:0 22px;}
  .rescue-pin > div{width:100%;}
  .browser{
    /* Full bleed WITHOUT viewport units. vw, dvw, svw and lvw all include the
       scrollbar, so 100vw overhung the viewport by half the scrollbar width on
       each side and showed as a sliver past the left edge. The parent's own
       22px padding is the exact amount to cancel. */
    width:calc(100% + 44px);margin-left:-22px;
    border-radius:0;overflow:hidden;
    box-shadow:0 24px 50px -22px rgba(0,0,0,.75);
  }
  .screens{
    position:relative;aspect-ratio:auto;overflow:hidden;
    height:auto;   /* the BEFORE mock in flow sets the height; no fixed vh */
    touch-action:pan-y;cursor:ew-resize;
  }
  /* BEFORE sits in normal flow so the frame is exactly as tall as its content;
     AFTER overlays it absolutely and is clipped by the wipe. */
  .screen{overflow:hidden;}
  .screen.before{position:relative;padding-bottom:10px;}
  .screen.after{position:absolute;inset:0;}
  /* Resting state shows the 1990s site nearly whole, like desktop's start,
     with a sliver of the premium site as the invitation to drag. A 50/50
     rest sawed the old site's window mid-word at the seam. */
  .screen.after{clip-path:inset(0 0 0 88%);}
  .seam{left:88%;opacity:1;}
  /* a real thumb knob, not just the glyph */
  .seam i{
    width:44px;height:44px;font-size:18px;
    display:flex;align-items:center;justify-content:center;
    background:var(--sky);border-radius:50%;
    box-shadow:0 2px 10px rgba(0,0,0,.45);
  }
  .rescue-cap{display:flex;align-items:baseline;padding:14px 22px 0;font-size:10px;letter-spacing:.1em;gap:12px;}
  .rescue-cap span{white-space:nowrap;}
  .only-d{display:none;}
  span.only-m{display:inline;}
  /* Stacked, not wiped. The wipe hijacked vertical scrolls that started on
     the frame, and the premium mock it revealed was set at 4px. Two frames,
     each with its own bar and caption, and the AFTER mock re-set for a
     390px container. */
  .browser > .browser-bar{display:none;}
  .screens{touch-action:auto;cursor:default;}
  .screen.after{position:static;clip-path:none !important;}
  .seam{display:none !important;}
  .rescue-cap{display:none;}
  .m-cap{display:block;font-family:var(--f-mono);font-size:12px;letter-spacing:.14em;text-transform:uppercase;padding:14px 14px 8px;margin:0;background:#0B0B0B;color:var(--px-sky);}
  .m-cap1{color:var(--px-rust);}
  .m-bar{display:flex;align-items:center;gap:6px;padding:8px 12px;background:#E7E3D7;}
  .m-bar .dot{width:8px;height:8px;border-radius:50%;display:inline-block;}
  .m-bar .browser-url{flex:1;margin-left:8px;background:#fff;border-radius:3px;padding:4px 8px;font-family:var(--f-mono);font-size:11px;color:#444;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
  .after{font-size:14px;}
  .af-util{font-size:10px;padding:8px 14px;letter-spacing:.08em;flex-wrap:wrap;gap:4px 12px;}
  .af-nav{padding:12px 14px;}
  .af-nav .mark{font-size:13px;letter-spacing:.22em;}
  .af-nav .lnk{display:none;}
  .af-nav .res{font-size:10px;padding:8px 12px;}
  .af-hero{grid-template-columns:1fr;}
  .af-left{padding:20px 16px 18px;}
  .af-eyebrow{font-size:10px;margin-bottom:10px;}
  .af-left .af-h{font-size:26px;margin-bottom:12px;}
  .af-left .af-h br{display:none;}
  .af-left .sub{font-size:13.5px;margin-bottom:14px;max-width:none;}
  .af-btns{gap:8px;margin-bottom:14px;}
  .af-b1,.af-b2{font-size:10.5px;padding:10px 14px;}
  .af-rate{font-size:10.5px;padding-top:12px;gap:8px;}
  .af-right{height:200px;}
  .af-frame{inset:10px;}
  .af-cap{left:16px;bottom:14px;font-size:10px;}
  .af-cap i{margin-bottom:3px;}
  /* The 1990s mock is sized in vw with px floors, not cqw like the AFTER
     site. In a 331px frame every size pins to its floor, the content runs
     wider than the window, and the menu photo, the under-construction bar
     and the visitor counter, which ARE the joke, get cut off. These
     retunes were the tail of the ported media query and I truncated them. */
  .before .b-marquee{font-size:8px;padding:3px 0;}
  .before .b-inner{width:80%;padding:9px 10px;border-width:2px;}
  .before .b-h{font-size:12px;margin:4px 0 2px;}
  .before .b-sub{font-size:7.5px;margin-bottom:7px;}
  .before .b-hr{height:3px;margin:6px 0;}
  .before .b-row{gap:5px;margin:7px 0;}
  .before .b-pill{font-size:7px;padding:1px 6px;}
  .before .b-note{font-size:7px;margin:6px auto;}
  .before .b-cons{font-size:8px;}
  .before .b-hits{font-size:7px;padding:1px 5px;margin-top:6px;}
  .before .b-menu{font-size:5.5px;padding:6px;margin:7px auto;}

}

/* ─────────────────────────── responsive ─────────────────────────── */

@media (max-width:1100px){
  .benefits__item{ grid-template-columns:6rem 1fr; }
  .benefits__media{ grid-column:1 / -1; grid-row:auto; margin-top:1.4rem; }
  .hero__cue{ display:none; }
  /* The split used to collapse here, which put the samples UNDERNEATH the copy
     on any window under 1100px. It holds down to 900px now (see below); between
     900 and 1100 the mock column simply gets narrower. */
  .hero__mock{ max-width:min(44vw,560px); }
  .hero__title{ font-size:clamp(1.75rem,3.9vw,2.4rem); }
}

@media (max-width:1100px){
  /* the report stacks under the ask rather than shrinking beside it */
  .audit{ grid-template-columns:1fr; }
  .report{ border-left:0; border-top:1px solid rgba(28,27,23,.14); }
}

/* Below 900px there is not enough width for a readable copy column beside a
   frame that still has to show a whole website, so the split stacks: copy
   first, then the samples. Above it, the samples stay on the right. */
@media (max-width:900px){
  .hero__split{ grid-template-columns:minmax(0,1fr); gap:1.8rem; }
  .hero__copy{ justify-items:start; }
  /* justify-self:end is right in two columns and wrong stacked, where it
     stranded the frame against the right edge with the left half empty */
  .hero__mock{ justify-self:start; max-width:min(560px,100%); }
  .hero__title{ font-size:clamp(1.9rem,5.4vw,2.8rem); }
}

@media (max-width:860px){
  :root{ --sec-pb:80px; }
  .auditform__row{ grid-template-columns:1fr; }
  .audit__hd{ max-width:none; }
  .report__hd{ flex-wrap:wrap; }
  html{ font-size:18px; }
  .header__nav{ display:none; }
  .header__cta{ display:none; }
  .header__tel{ display:none; }
  /* The marquee showed three words at a time and took 19 seconds to say the
     sentence. On a phone the sentence is simply printed. */
  .hero__title{ font-size:clamp(2.1rem,10vw,3rem); line-height:1; margin-top:4.5rem; }
  .hero__title .marquee{ white-space:normal; overflow:visible; }
  .hero__title .marquee__track{ animation:none !important; transform:none !important; display:block; }
  .hero__title .marquee__item{ display:block; white-space:normal; padding:0; }
  .hero__title .marquee__item ~ .marquee__item{ display:none; }
  .hero__title .bullet{ display:none; }
  .hero__foot--r{ justify-self:stretch; justify-items:start; max-width:none; }
  .hero__cta{ justify-content:flex-start; width:100%; }
  .hero__cta .btn{ flex:1 1 auto; justify-content:center; }
  .header__burger{ display:flex; margin-left:auto; }
  .layout{ grid-template-columns:1fr; padding-top:3rem; }
  .layout__third{ position:static; }
  .hero__foot{ grid-template-columns:minmax(0,1fr); }
  .hero__blurb,.hero__tag{ justify-self:start; max-width:100%; }
  .hero__mini{ width:140px; height:140px; }
  /* The tabs scroll sideways rather than wrapping to two rows and pushing the
     frame off the fold. -webkit-overflow-scrolling and the negative gutter
     let them run to the screen edge, which is the affordance that says there
     are more of them. */
  .mock__tabs{
    flex-wrap:nowrap; overflow-x:auto; scrollbar-width:none;
    margin-inline:calc(-1 * var(--gut)); padding-inline:var(--gut);
    scroll-snap-type:x proximity;
  }
  .mock__tabs::-webkit-scrollbar{ display:none; }
  .mock__tab{ flex:0 0 auto; scroll-snap-align:start; }
  /* A ~340px-wide frame cannot carry any of these four at their desktop
     density: everything lands at 5px type. Each mock drops its own least
     load-bearing block and scales its type up, so all four stay legible and
     stay different from one another. */
  /* keeping the salon's price rows and adding the electrician's closing bar
     pushed both past a 1.12 frame; this is measured to the tallest of the
     four at this width, so none of them clip */
  .mock__screens{ aspect-ratio:1/1.20; }

  /* The frame is ~330px here. The statement and the working panel are what
     sell these, so they stay and everything decorative gives way. */
  .px__link{ display:none; }
  .px__mark{ font-size:4.4cqw; }
  .px__dot{ width:4.2cqw; height:4.2cqw; }
  .px__cta{ font-size:3.2cqw; padding:2cqw 3.2cqw; }
  .px__eyebrow{ font-size:3.2cqw; }
  .px__h{ font-size:9.6cqw; }
  /* the statement block gives back what the panel needed */
  .px__mid{ padding-bottom:2cqw; gap:1.1cqw; }
  .px__sub{ font-size:3.5cqw; }
  .px__panel{ margin:0 2.8cqw 2.6cqw; padding:2.5cqw; gap:1.7cqw; }
  .px__panel--wide{ margin-inline:3cqw; }
  .px__plbl{ font-size:3.1cqw; }
  .px__day b{ font-size:3cqw; }
  .px__day i{ font-size:3.6cqw; }
  .px__slot{ font-size:3.1cqw; padding:2cqw 2.8cqw; }
  .px__slot b{ font-size:3.1cqw; }
  .px__slot i{ font-size:2.8cqw; }
  .px__pill{ font-size:3.1cqw; padding:1.9cqw 3.2cqw; }
  .px__qty{ font-size:3.1cqw; }
  .px__qn{ font-size:3cqw; }
  .px__qv{ font-size:5cqw; }
  .px__go{ font-size:3.4cqw; padding:2.6cqw; }
  .px__note{ font-size:2.9cqw; }
  /* The cafe band keeps the photograph's own shape, but a taller band was the
     one thing pushing this mock past the frame: it overran by 8-15px at every
     width under 900 and clipped the order button. */
  .px__band{ aspect-ratio:16/7.2; }
  .px__body{ padding:2.6cqw 3.4cqw 1.6cqw; gap:2cqw; }
  .px__body .px__h{ font-size:8cqw; }
  .px__board span{ font-size:3cqw; }
  .px__board span:nth-child(n+3){ display:none; }
  .px__stamp{ font-size:2.9cqw; }
  /* three slots across is too tight; the salon shows the first two */
  .px__panel--wide .px__slots{ grid-template-columns:repeat(2,1fr); }
  .px__panel--wide .px__slot:nth-child(3){ display:none; }
  /* the drifting light is a large-screen pleasure and pure cost on a phone */
  .ts--pl::before,.ts--cafe::before,.ts--sal::before,.ts--el::before{ display:none; }
  /* 38% put the mark straight back on top of the headline, which is the
     exact collision that was fixed on desktop. It sits below it here too. */
  .hero__badge{ width:64vw; top:41%; }
  .auditform{ grid-template-columns:1fr; }
  .benefits__item{ grid-template-columns:1fr; top:70px; min-height:0; }
  .benefits__hd,.benefits__body{ grid-column:1; }
  .faq__item summary{ grid-template-columns:3.4rem 1fr 1.4rem; }
  .faq__a p{ margin-left:0; }
  .statement{ margin-top:3.5rem; }
  .ticker{ display:none; }
  .gloom{ display:none; }
}

@media (min-width:2000px){
  html{ font-size:22px; }
  :root{ --maxw:1900px; }
}


/* ─────────────────── below-fold layout, deferred ───────────────────
   First paint used to wait on the layout of every section below the hero:
   2.5s of it on a phone. These sections lay out when they approach the
   viewport. The placeholder heights are MEASURED (harness/section-heights.json,
   2026-08-21) per breakpoint, and contain-intrinsic-size:auto keeps the real
   height once a section has rendered, so anchor links land where they should;
   px.js anchorSettle() corrects the last few pixels after the scroll ends.
   The values are CONTENT-box heights: the section's own padding (132px on
   desktop, 84px under 861px, 4px on the footer) is subtracted, because the
   browser adds it back on top of the placeholder.
   The two sticky sections (rescue, benefits) are left alone. */
#calls,#audit,#work,#tools,#prices,#brand,#faq,#quote,.footer{ content-visibility:auto; }
@media (max-width:600px){
  #calls{ contain-intrinsic-size:auto 1929px; }
  #audit{ contain-intrinsic-size:auto 2973px; }
  #work{ contain-intrinsic-size:auto 1429px; }
  #tools{ contain-intrinsic-size:auto 1515px; }
  #prices{ contain-intrinsic-size:auto 2321px; }
  #brand{ contain-intrinsic-size:auto 1569px; }
  #faq{ contain-intrinsic-size:auto 1122px; }
  #quote{ contain-intrinsic-size:auto 939px; }
  .footer{ contain-intrinsic-size:auto 513px; }
}
@media (min-width:601px) and (max-width:1100px){
  #calls{ contain-intrinsic-size:auto 1543px; }
  #audit{ contain-intrinsic-size:auto 2431px; }
  #work{ contain-intrinsic-size:auto 1055px; }
  #tools{ contain-intrinsic-size:auto 1389px; }
  #prices{ contain-intrinsic-size:auto 1896px; }
  #brand{ contain-intrinsic-size:auto 1457px; }
  #faq{ contain-intrinsic-size:auto 1219px; }
  #quote{ contain-intrinsic-size:auto 865px; }
  .footer{ contain-intrinsic-size:auto 624px; }
}
@media (min-width:1101px){
  #calls{ contain-intrinsic-size:auto 1678px; }
  #audit{ contain-intrinsic-size:auto 2050px; }
  #work{ contain-intrinsic-size:auto 1297px; }
  #tools{ contain-intrinsic-size:auto 1478px; }
  #prices{ contain-intrinsic-size:auto 2011px; }
  #brand{ contain-intrinsic-size:auto 1460px; }
  #faq{ contain-intrinsic-size:auto 1405px; }
  #quote{ contain-intrinsic-size:auto 912px; }
  .footer{ contain-intrinsic-size:auto 682px; }
}

/* The marquee runs as a CSS animation on the compositor. px.js measures one
   item, sets --w and --dur, and pauses the track while it is off screen. */
@keyframes px-marquee{ to{ transform:translate3d(calc(-1 * var(--w,0px)),0,0); } }
.marquee__track.is-running{ animation:px-marquee var(--dur,30s) linear infinite; }
.marquee__track.is-paused{ animation-play-state:paused; }

/* ─────────────────────────── reduced motion ─────────────────────────── */
/* CLAUDE.md: canvas and shader must not initialize, content must stay visible,
   and nothing may depend on an animation having run. px.js reads the same
   query and skips every renderer; this half guarantees the layout. */

@media (prefers-reduced-motion:reduce){
  html{ scroll-behavior:auto; }
  .marquee__track{ transform:none !important; animation:none !important; }
  .marquee__item ~ .marquee__item{ display:none; }
  .marquee{ white-space:normal; overflow:visible; }
  .marquee__item{ display:inline; }
  .benefits__item{ position:static; }
  /* the laptop is a photograph now, not a canvas: hold it still rather than
     removing the hero's only object */
  .pricing__prop{ display:none; }
  .machine{ transform:none !important; }
  .scanlines,.grain,.gloom,.vignette{ display:none; }
  /* the reel is the one motion left in the hero, so it holds on the top of
     the client's page instead of scrolling */
  .hero__mini--reel{ animation:none; }
  /* the marquee unwraps to normal wrapping text, which a 70px header cannot
     hold, so the status line goes rather than breaking the header */
  .ticker{ display:none; }
  .dot{ animation:none; }
  [data-split] .word{ translate:none; opacity:1; transition:none; }
  /* The mock still swaps on a click, instantly. px.js never auto-rotates
     under this query, so the only motion left would be the draw-on. */
  /* px.js pins the stage at 4 under this query, so the mock is simply the
     finished site. These kill the transitions that would otherwise still run
     when a visitor picks a different trade. */
  .ts,.ts.is-active > *,.mock__frame,.mock__lock,.mock__tab{ transition:none; }
  .mock__frame{ transform:none; }
  .mock__url.is-typing::after{ display:none; }
  .hero__mock .ts{ filter:none; }
  /* and the whole motion layer: no drift, no settle, no sheen */
  .ts--pl::before,.ts--cafe::before,.ts--sal::before,.ts--el::before{ animation:none; display:none; }
  .hero__mock .px__go::after{ animation:none; display:none; }
  .px__shot{ transform:none; }
}

@media (prefers-contrast:more){
  .scanlines,.grain{ display:none; }
}

/* The footer's phone, email and link list were 19-26px tall on a phone, which
   is under the 44px floor, on the two things a plumber standing in a basement
   is most likely to tap. Padding is cancelled by an equal negative margin so
   the hit area grows without moving the layout. */
.footer__contact a{ display:inline-block; padding-block:14px; margin-block:-14px; }
.footer__links a{ display:inline-flex; align-items:center; min-height:44px; }

/* ---------- view transitions ----------
   Ported from wcg-system.css. index.html links only this file, so without it
   the homepage was the single page with no transition, in both directions. */
@view-transition{ navigation: auto; }
::view-transition-old(root){ animation: vt-out .28s cubic-bezier(.4,0,1,1) both; }
::view-transition-new(root){ animation: vt-in .42s cubic-bezier(0,0,.2,1) both; }
@keyframes vt-out{ to{ opacity:0; transform:translateY(-8px); } }
@keyframes vt-in{ from{ opacity:0; transform:translateY(10px); } }
.header{view-transition-name:sitenav}
.footer{view-transition-name:sitefoot}
@media (prefers-reduced-motion:reduce){
  ::view-transition-old(root),::view-transition-new(root){ animation:none; }
}

/* ---------- Messenger button ----------
   Square with this system's 2px radius rather than the usual circular chat
   blob, so it reads as part of the site instead of a bolted-on plugin.

   z-index 110 is picked from THIS page's existing stack, not invented: it
   clears the vignette at 100 and sits under the mobile nav at 119, so an open
   menu covers it without needing :has(). It is fixed at the body level on
   purpose. Do NOT solve a stacking problem here by adding a numeric z-index to
   .section__hero or .hero__split: both are deliberately isolation:auto and
   z-index:auto so the trade mock can clear the four global overlays, and a
   number on either drops the overlays back over the mock. */
.msgr{
  position:fixed; right:20px; bottom:20px; z-index:110;
  width:56px; height:56px; border-radius:2px;
  display:grid; place-items:center;
  background:var(--px-black); color:var(--px-paper);
  border:1px solid rgba(247,244,236,.14);
  box-shadow:0 12px 28px -12px rgba(0,0,0,.6);
  transition:transform .18s ease, background .18s ease, border-color .18s ease;
}
.msgr svg{ width:26px; height:26px; fill:currentColor; display:block; }
.msgr:hover{ background:var(--px-willow-ink); border-color:var(--px-willow); transform:translateY(-2px); }
.msgr:focus-visible{ outline:2px solid var(--px-sky); outline-offset:3px; }
/* The phone number is the primary conversion on this site and outranks
   Messenger, so on a phone this shrinks and tucks tighter rather than growing. */
@media (max-width:600px){
  .msgr{ right:14px; bottom:14px; width:50px; height:50px; }
  .msgr svg{ width:23px; height:23px; }
}
@media (prefers-reduced-motion:reduce){
  .msgr{ transition:none; }
  .msgr:hover{ transform:none; }
}

/* Applied by px.js only while the hero still owns the screen. The default is
   VISIBLE, so no-JS shows a working button. visibility is discrete on purpose:
   an opacity-only hide that never finishes would leave a dead click target
   floating over the mock. */
.msgr--tucked{
  opacity:0; visibility:hidden; pointer-events:none;
  transform:translateY(10px);
}

/* ---------- the header ticker needs room or it needs to go ----------
   .marquee is flex:1 1 auto, so it absorbs the whole shortfall whenever the
   header's other five children do not fit. Measured across the band: 0px from
   880 to 1280, then a clipped 52px at 1366 and 108px at 1440, which is about
   eleven characters of a scrolling sentence and reads as a rendering glitch
   rather than a ticker. It only does its job from about 1560 up, where it
   settles at its 245px cap.

   Adding Client Login to the nav pushed that boundary further right, but the
   collapse below 1280 predates it. Hiding it under the width where it can
   carry a sentence is honest: the element is aria-hidden decoration, and every
   price it names also appears in the price list and in services.html, so
   nothing is lost by not drawing it. */
@media (max-width:1560px){
  .header__in .marquee{ display:none; }
}

/* ---------- cafe gallery ----------
   Asymmetric on purpose: one tall plate beside two stacked ones. The salon
   carries the even strip, and the four mocks only work as a portfolio while
   their block signatures stay different from each other. */
.px__gal{
  display:grid;
  grid-template-columns:1.55fr 1fr;
  grid-template-rows:repeat(2,minmax(0,1fr));
  gap:.55cqw;
  height:17.5cqw;
  padding:0 3.4cqw 2.4cqw;
  flex:0 0 auto;
}
.px__gal img{ width:100%; height:100%; object-fit:cover; display:block; border-radius:2px; }
.px__gal-a{ grid-row:1 / span 2; }
/* The masthead gives up the height the gallery takes, so the frame is
   unchanged. Re-measure all four if you touch either number. */
.ts--cafe .px__band{ aspect-ratio:16/4.6; }

/* ---------- salon services strip ----------
   Even, captioned, hairline-separated: the counterpart to the cafe's gallery,
   and the block CLAUDE.md already lists in this mock's signature. */
.px__strip{
  display:grid; grid-template-columns:repeat(2,1fr);
  gap:1.2cqw; padding:0 3.4cqw 2cqw; flex:0 0 auto;
}
.px__strip figure{ margin:0; display:grid; gap:.7cqw; }
.px__strip img{
  width:100%; height:17cqw; object-fit:cover; object-position:50% 32%;
  display:block; border-radius:2px; border:1px solid var(--line);
}
.px__cap{
  font-family:var(--f-mono); font-size:1.4cqw; letter-spacing:.14em;
  text-transform:uppercase; color:var(--dim); text-align:left;
}

/* ============================================================================
   BESPOKE DIRECTIONS applied to the four mocks.

   sitepitch/sp/direction.py picks one direction per build from the trade and
   makes it a binding contract: type pair, grid, corners, motion verb and an
   explicit refuse list. sitepitch/BESPOKE.md: "Mixing two reads as a theme,
   and a theme is what the client could have bought for $950." These four were
   four ad-hoc designs; the picker maps them to three directions.

     plumber, electrician -> BRUTAL      (both are in EMERGENCY_WORDS)
     cafe                 -> MATERIAL
     salon                -> EDITORIAL   (destination salon, per BESPOKE.md)

   THE TYPE PAIRS ARE SUBSTITUTED, NOT IMPORTED. The directions name licensed
   pools (Anton/IBM Plex Mono, Fraunces/IBM Plex Sans, Fraunces/Instrument
   Sans) and the demos pull them from the Google Fonts CDN. This site self-
   hosts and has no Google Fonts link, and adding five families to the busiest
   page for four 560px miniatures is the wrong trade. The three self-hosted
   families cover both shapes the doctrine actually asks for:

     brutal   grotesque + mono  -> Archivo + Space Mono
     material serif + sans      -> Lora + Archivo
     editorial serif + sans     -> Lora + Archivo

   The rule that carries is "No third family". Every mock was running all
   three at once.
   ============================================================================ */

/* ---- BRUTAL: plumber + electrician ----
   "corners 0px, no exceptions" and refuses curves, warmth-by-default, soft
   shadows, any bounce. Body copy is the mono half of the pair. */
.ts--pl, .ts--el{ --f-body:'Space Mono', ui-monospace, monospace; }
.ts--pl :is(.px__cta,.px__panel,.px__day,.px__slot,.px__go,.px__pill,.px__dot,.px__qty,img),
.ts--el :is(.px__cta,.px__panel,.px__day,.px__slot,.px__go,.px__pill,.px__dot,.px__qty,img){
  border-radius:0;
}
.ts--pl .px__panel, .ts--el .px__panel{ box-shadow:none; }
/* The Lora italic is the third family and it is also warmth, which this
   direction refuses by name. The headline runs entirely in the grotesque. */
.ts--pl .px__h em, .ts--el .px__h em{
  font-family:var(--f-display); font-style:normal;
  font-variation-settings:'wdth' 82,'wght' 900;
}
/* "break it once with one oversized number or dimension" */
.ts--el .px__qv{ font-size:5.4cqw; letter-spacing:-.03em; }

/* ---- MATERIAL: cafe ----
   "corners 0 or 2px", refuses colour that fights the photography. Serif
   display over sans text, so the mono labels go. */
.ts--cafe{ --f-mono:'Archivo', system-ui, sans-serif; }
.ts--cafe :is(.px__pill,.px__qty){ border-radius:2px; }
.ts--cafe .px__h{ font-family:var(--f-body-serif,'Lora',Georgia,serif); font-weight:500; }
.ts--cafe .px__h em{ font-style:italic; }

/* ---- EDITORIAL: salon ----
   "corners 0px", refuses icon cards, box shadows, CENTERED EVERYTHING, and
   more than one accent. The centred hero was the clearest break: five runs of
   text were centre-aligned. Text now sits in a narrow column inside the wide
   container, which is what the 12-column editorial grid asks for. */
.ts--sal{ --f-mono:'Archivo', system-ui, sans-serif; }
.ts--sal :is(.px__cta,.px__panel,.px__slot,.px__go,img){ border-radius:0; }
.ts--sal .px__panel{ box-shadow:none; }
.ts--sal .px__mid--centre{
  justify-items:start; text-align:left;
  padding-inline:3.4cqw; max-width:34ch;
}

/* ---- CINEMATIC: the inn ----
   Replaces the electrician, which the picker also sent to brutal, so the four
   mocks were showing three directions. sitepitch/sp/direction.py cinematic:
   "16:9 chapters, film-style captions in tracked caps, a dark grade. Each
   section is a scene with one subject." Grid is "full-bleed 16:9 frames
   stacked as chapters; text overlays hold to the lower third", corners 0px,
   and it refuses busy chrome and light-grade stock brightness.

   Every frame is a true 16:9 so the shape rule survives compression into a
   560px miniature: lead 560x315, then a two-up of 278x156, then the panel.
   Serif display over sans text, no mono, because cinematic refuses "more than
   one typeface moment per scene". */
.ts--inn{
  background:#0B0E10;
  --ink:#F2EFE9; --acc:#C9A227; --accInk:#0B0E10;
  --dim:rgba(242,239,233,.74);
  --line:rgba(242,239,233,.16);
  --panelBg:rgba(8,10,12,.92);
  --f-mono:'Archivo', system-ui, sans-serif;
}
.px__chap{ position:relative; margin:0; overflow:hidden; flex:0 0 auto; }
.px__chap img{ width:100%; height:100%; object-fit:cover; display:block; }
.px__chap--lead{ aspect-ratio:16/9; }
.px__chaps{ display:grid; grid-template-columns:1fr 1fr; gap:.45cqw; flex:0 0 auto; }
.px__chaps .px__chap{ aspect-ratio:16/9; }
/* the dark grade, and the reason the lower third stays readable */
.px__chap::after{
  content:''; position:absolute; inset:0; z-index:1; pointer-events:none;
  background:linear-gradient(180deg, rgba(6,8,10,.34) 0%, rgba(6,8,10,.04) 36%, rgba(6,8,10,.90) 100%);
}
.px__chap figcaption{
  position:absolute; inset:auto 0 0 0; z-index:2;
  padding:0 3.4cqw 2.2cqw; display:grid; gap:.8cqw; justify-items:start;
}
.px__slate{
  font-family:var(--f-mono); font-size:1.35cqw; letter-spacing:.26em;
  text-transform:uppercase; color:var(--dim);
}
.ts--inn .px__h{
  font-family:'Lora', Georgia, serif; font-weight:500;
  font-variation-settings:normal;
  font-size:6.6cqw; line-height:1.02; letter-spacing:-.012em;
  color:var(--ink); text-shadow:none; margin:0;
}
.ts--inn .px__h em{ font-style:italic; color:var(--acc); }
.ts--inn :is(.px__cta,.px__pill,.px__go,.px__panel,.px__qty,img){ border-radius:0; }
.ts--inn .px__panel{ box-shadow:none; }
/* the rate reads as a price, not a headline; it is the scene's one number */
.px__qv--rate{
  margin-left:auto; font-family:'Lora', Georgia, serif; font-size:3.4cqw;
  color:var(--ink); letter-spacing:-.01em;
}
.px__qv--rate i{ font-style:normal; font-size:.46em; color:var(--dim); letter-spacing:.04em; }

/* The three chapters must keep their 16:9, so the panel gives up the height.
   That is also the on-direction choice: cinematic refuses busy chrome. */
.ts--inn .px__panel{ padding:2.6cqw 3.4cqw 2.8cqw; gap:1.7cqw; }
.ts--inn .px__plbl{ margin:0; }

/* The overlay nav has to beat `.ts > *:not(.px__shot)`, which is (0,2,0)
   because :not() carries its argument's specificity, against `.px__nav--over`
   at (0,1,0). Without this the inn's header sat IN FLOW and ate 65px, which
   is exactly what pushed the three 16:9 chapters past the frame. The
   electrician had the identical bug one level down, against `.px__top > *`.
   Anything that overlays a photo needs its own rule at this depth. */
.ts > .px__nav--over{ position:absolute; inset:0 0 auto 0; z-index:3; }

/* The plumber's mark was a coloured dot, and brutal takes the curve off it,
   which left a plain square. Neither says anything. A hairline-ruled monogram
   in the mono half of the pair is what the direction actually describes:
   "mono plus grotesque, hard hairline rules, raw edges". */
.ts--pl .px__dot{
  width:auto; height:auto; flex:none; background:none;
  border:1px solid var(--acc); color:var(--acc);
  font-family:var(--f-mono); font-style:normal;
  font-size:1.25cqw; letter-spacing:.14em; line-height:1;
  padding:.5cqw .65cqw;
}
/* Three links is a site; two reads as one somebody abandoned. Tightened so the
   third fits the row without crowding the phone number, which on an emergency
   trade outranks everything else in the header. */
.ts--pl .px__navr{ gap:1.7cqw; }
