/* True Square — foundation
   One typeface. Near-black, warm white, chalk blue. Square geometry.
   Every value here is a decision; see docs/DESIGN.md before changing any of it. */

@font-face {
  font-family: 'Archivo';
  src: url('../fonts/Archivo.woff2') format('woff2');
  font-weight: 400 900;
  font-stretch: 75% 125%;
  font-style: normal;
  font-display: swap;
}

:root {
  /* Black and white, and nothing in between pretending to be a colour.
     The page was a warm olive-black; against a blue mark it read dingy and
     made the type look grey. True black is crisper and the blue actually
     carries. Sam, 2026-08-01: "That background color is so ugly." */
  --ink:        #000000;
  --ink-2:      #0B0B0D;
  --ink-3:      #17171A;
  --paper:      #FFFFFF;
  --paper-dim:  #C4C4C8;
  --paper-mute: #87878C;

  /* The accent is chalk-line blue. A snapped chalk line is the tool you use
     to mark something true and square — it is the name, made literal.
     Exactly one accent. Nothing else gets to be a colour. */
  --chalk:      #2F6BE0;
  --chalk-soft: #7BA3EE;

  --rule:       #242427;

  /* How long the page takes to change rooms. Short on purpose: this is a
     SNAP, not a dissolve. A slow gradient is the thing every AI-built site
     does, and text contrast turns to mush halfway through it. */
  --flip:       180ms;

  /* Type scale. Body starts at 18px — the buyer is 45 and reading on a phone
     in a truck. Sam called earlier work too small; it will not be too small. */
  --t-micro:  0.8125rem;  /* 13px — the floor, nothing smaller exists */
  --t-small:  0.9375rem;  /* 15px */
  --t-body:   1.125rem;   /* 18px */
  --t-lead:   1.375rem;   /* 22px */
  --t-h3:     1.75rem;
  --t-h2:     clamp(2rem, 4.5vw, 3.25rem);
  --t-h1:     clamp(2.75rem, 7vw, 5.5rem);

  --gutter:   clamp(1.25rem, 5vw, 4.5rem);
  --measure:  62ch;
}

*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--ink);
  color: var(--paper);
  font-family: 'Archivo', -apple-system, BlinkMacSystemFont, 'Helvetica Neue', Arial, sans-serif;
  font-size: var(--t-body);
  line-height: 1.55;
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* Square geometry. Radius is not a global default — it is granted, rarely. */
img, svg, video { display: block; max-width: 100%; height: auto; }

a { color: inherit; }

h1, h2, h3 {
  font-weight: 700;
  line-height: 1.04;
  letter-spacing: -0.022em;
  margin: 0;
  text-wrap: balance;
}
h1 { font-size: var(--t-h1); font-weight: 800; letter-spacing: -0.032em; }
h2 { font-size: var(--t-h2); }
h3 { font-size: var(--t-h3); letter-spacing: -0.014em; }

p { margin: 0; }

.wrap {
  width: 100%;
  max-width: 1440px;
  margin-inline: auto;
  padding-inline: var(--gutter);
}

/* Sections deliberately do NOT share a uniform vertical rhythm.
   Each one is sized to the job it does. */

/* ---------- masthead ---------- */

.masthead {
  position: absolute;
  inset: 0 0 auto 0;
  z-index: 10;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1.5rem;
  padding-block: clamp(1.25rem, 2.5vw, 2rem);
}

/* The logo. One square band folded once at the corner, and the name set in
   Bricolage Grotesque. Inlined rather than loaded as an image so the masthead
   never flashes and the request count stays where it is.

   The wordmark inside the SVG is filled with `currentColor`, so the ONE file
   works on dark and on light — set `color` and it follows. */
.wordmark {
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  color: var(--paper);
}
.wordmark .logo {
  height: clamp(1.9rem, 2.9vw, 2.4rem);
  width: auto;
  display: block;
}
.wordmark:hover { color: var(--paper); }

.masthead nav {
  display: flex;
  gap: clamp(1rem, 2.5vw, 2.25rem);
  font-size: var(--t-small);
  font-weight: 600;
}
.masthead nav a { text-decoration: none; color: var(--paper-dim); }
.masthead nav a:hover { color: var(--paper); }

@media (max-width: 640px) {
  .masthead nav .hide-sm { display: none; }
}

/* ---------- hero ---------- */

.hero {
  position: relative;
  min-height: 100svh;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 34rem);
  grid-template-rows: 1fr auto;
  column-gap: clamp(2rem, 6vw, 6rem);
  align-items: center;
  padding-block: clamp(6rem, 12vh, 9rem) clamp(1.5rem, 4vh, 3rem);
}
.hero-foot { grid-column: 1 / -1; }

@media (max-width: 980px) {
  .hero { grid-template-columns: minmax(0, 1fr); }
}

.hero-body { align-self: center; }

.hero h1 { max-width: 15ch; }

.hero .sub {
  margin-top: clamp(1.5rem, 3vw, 2.25rem);
  font-size: var(--t-lead);
  line-height: 1.45;
  color: var(--paper-dim);
  max-width: 46ch;
}

.hero .sub strong { color: var(--paper); font-weight: 600; }

/* The chalk line. It snaps once, on load, under the headline —
   the way you snap a line to mark something true before you build to it.
   One piece of motion, doing one job. */
.snap {
  margin-top: clamp(2rem, 5vw, 3.5rem);
  height: 2px;
  background: var(--chalk);
  transform-origin: left center;
  transform: scaleX(0);
  animation: snap 620ms cubic-bezier(.16,.9,.3,1) 260ms forwards;
}
@keyframes snap { to { transform: scaleX(1); } }

.hero-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.75rem 2rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--rule);
  font-size: var(--t-small);
  color: var(--paper-mute);
}
.hero-foot .price { color: var(--paper); font-weight: 700; font-size: var(--t-body); }

@media (prefers-reduced-motion: reduce) {
  .snap { animation: none; transform: scaleX(1); }
}

/* ---------- the work ----------
   The portfolio is the product, so it gets the most space and the least
   decoration. Big targets — the buyer is on a phone and is not a designer. */

.work { padding-block: clamp(4rem, 10vh, 8rem) 0; border-top: 1px solid var(--rule); }

.section-head {
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
  gap: clamp(1.5rem, 4vw, 4rem);
  align-items: end;
}
.section-head h2 { max-width: 20ch; }
.section-note {
  color: var(--paper-mute);
  font-size: var(--t-body);
  max-width: 46ch;
  padding-bottom: 0.35rem;
}
@media (max-width: 860px) {
  .section-head { grid-template-columns: 1fr; align-items: start; }
  .section-note { padding-bottom: 0; }
}

/* The portfolio is the product, so it is shown rather than described.
   These are real screenshots of the real pages, captured by
   tools/build-shots.mjs — never a mockup, so the homepage can't show work
   that has drifted or doesn't exist. */

.demo { border-top: 1px solid var(--rule); padding-block: clamp(3rem, 7vw, 6rem); }
.demo:last-of-type { border-bottom: 1px solid var(--rule); }

.demo-head {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(1rem, 3vw, 3.5rem);
  align-items: end;
}
.demo .trade {
  font-size: var(--t-micro);
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--c);
}
.demo h3 {
  margin-top: 0.5rem;
  font-size: clamp(1.875rem, 4.6vw, 3.5rem);
  font-weight: 800;
  letter-spacing: -0.03em;
}
.demo .what { color: var(--paper-dim); font-size: var(--t-body); max-width: 42ch; }
.demo .what b { color: var(--paper); font-weight: 600; }

/* The phone sits over the corner of the laptop shot because the first thing
   a contractor asks is what it looks like on a phone. No device chrome —
   a rounded plastic bezel would be decoration pretending to be information. */
.shots { position: relative; margin-top: clamp(1.75rem, 4vw, 3rem); }
.shots .desk {
  display: block; width: 100%;
  border: 1px solid var(--rule);
  background: var(--ink-2);
}
.shots .phone {
  position: absolute;
  right: clamp(0.75rem, 3%, 3rem);
  bottom: clamp(-1.5rem, -4%, -3rem);
  width: clamp(104px, 16%, 210px);
  border: 1px solid var(--ink-3);
  box-shadow: 0 26px 60px -22px rgba(0,0,0,.85);
}
@media (max-width: 640px) { .shots .phone { display: none; } }

.demo .go { margin-top: clamp(2.75rem, 6vw, 4rem); }

/* Buttons.
   The old one was a 66px slab set in 22px type, which on a phone filled the
   whole width and read as a banner rather than a control. This is smaller but
   still well over the 44px minimum target, and it carries an arrow so it reads
   as somewhere to go rather than something to look at. */
.btn {
  --btn-bg: var(--chalk);
  --btn-fg: #fff;
  display: inline-flex; align-items: center; justify-content: center;
  gap: 0.75em;
  min-height: 56px; padding: 0.9rem 1.6rem;
  font-size: var(--t-body); font-weight: 700; letter-spacing: -0.005em;
  text-decoration: none;
  background: var(--btn-bg); color: var(--btn-fg);
  border: 1px solid transparent;
  transition: background-color 150ms ease, border-color 150ms ease,
              color 150ms ease, transform 90ms ease;
}
/* the arrow is drawn, not typed: a character would inherit the world's
   typeface and change shape five times down the page */
.btn::after {
  content: "";
  width: 0.42em; height: 0.42em; flex: none;
  border-top: 2px solid currentColor; border-right: 2px solid currentColor;
  transform: rotate(45deg) translate(-0.02em, 0.02em);
  position: relative; top: -0.02em;
  transition: transform 180ms cubic-bezier(.3,.9,.4,1);
}
.btn:hover { --btn-bg: #4B82EA; }
.btn:hover::after { transform: rotate(45deg) translate(0.12em, -0.12em); }
.btn:active { transform: translateY(1px); }
.btn:focus-visible { outline: 3px solid var(--btn-bg); outline-offset: 3px; }
@media (prefers-reduced-motion: reduce) {
  .btn, .btn::after { transition: none; }
  .btn:hover::after { transform: rotate(45deg) translate(-0.02em, 0.02em); }
}

.btn.ghost { --btn-bg: transparent; --btn-fg: var(--paper); border-color: var(--ink-3); }
.btn.ghost:hover { border-color: var(--paper); --btn-bg: transparent; }
.btn.ghost:focus-visible { outline-color: var(--paper); }
:where(a, button):focus-visible { outline: 3px solid var(--chalk); outline-offset: 3px; }
@media (max-width: 560px) { .btn { width: 100%; } }

/* ---------- what you get ---------- */

.included { padding-block: clamp(4rem, 9vh, 7rem) 0; }
.gets { margin-top: clamp(2rem, 4vw, 3rem); border-top: 2px solid var(--paper); counter-reset: get; }
.get {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.35fr);
  gap: 0.5rem clamp(1.5rem, 4vw, 4rem);
  padding-block: clamp(1.25rem, 2.6vw, 2rem);
  border-bottom: 1px solid var(--rule);
}
.get h3 { font-size: clamp(1.25rem, 2.4vw, 1.75rem); font-weight: 700; }
.get p { color: var(--paper-mute); font-size: var(--t-body); max-width: 52ch; }
@media (max-width: 760px) { .get { grid-template-columns: 1fr; } }

/* ---------- price ---------- */

.price-sec { padding-block: clamp(4rem, 9vh, 7rem) 0; }
.price-box {
  margin-top: clamp(1.75rem, 4vw, 2.75rem);
  border: 1px solid var(--rule);
  padding: clamp(1.75rem, 4vw, 3.25rem);
  display: grid;
  grid-template-columns: minmax(0, auto) minmax(0, 1fr);
  gap: clamp(1.5rem, 4vw, 4rem);
  align-items: start;
}
.price-num {
  font-size: clamp(2.75rem, 7vw, 4.75rem);
  font-weight: 800; letter-spacing: -0.035em; line-height: 1;
}
.price-num span { display: block; font-size: var(--t-body); font-weight: 600;
  letter-spacing: 0; color: var(--paper-mute); margin-top: 0.5rem; }
.price-terms { list-style: none; margin: 0; padding: 0; }
.price-terms li {
  padding-block: 0.75rem; border-bottom: 1px solid var(--rule);
  font-size: var(--t-body); color: var(--paper-dim);
}
.price-terms li:last-child { border-bottom: 0; }
.price-terms b { color: var(--paper); font-weight: 600; }
@media (max-width: 760px) { .price-box { grid-template-columns: 1fr; } }

/* ---------- foot ---------- */

.foot {
  margin-top: clamp(4rem, 9vh, 7rem);
  padding-block: clamp(2rem, 5vh, 3.5rem);
  border-top: 1px solid var(--rule);
  color: var(--paper-mute);
  font-size: var(--t-small);
}
.foot-note { max-width: 62ch; }
.foot-mark {
  margin-top: 1.75rem;
  display: flex; align-items: center; gap: 0.6rem;
  font-weight: 700; color: var(--paper-dim);
}
.foot-mark .footmark {
  width: 1.15em; height: 1.15em; flex: none; display: block;
}

/* ---------- the explainer ----------
   The one page on this site that is pure reading. It exists so Shelby can send
   a prospect something useful that isn't a pitch, so it is set for reading
   comfort and nothing else: one column, long measure, big type. */

.article { padding-block: clamp(6rem, 12vh, 9rem) 0; }
.article h1 { max-width: 17ch; }
.article .standfirst {
  margin-top: clamp(1.5rem, 3vw, 2.25rem);
  font-size: var(--t-lead); line-height: 1.45;
  color: var(--paper-dim); max-width: 44ch;
}
.article .body { margin-top: clamp(3rem, 7vw, 5rem); max-width: 34rem; }
.article .body > * + * { margin-top: 1.5rem; }
.article .body p { font-size: var(--t-body); line-height: 1.65; color: var(--paper-dim); }
.article .body h2 {
  margin-top: clamp(3rem, 6vw, 4.5rem);
  font-size: clamp(1.5rem, 3.2vw, 2.25rem);
  max-width: 20ch;
}
.article .body h2 + p { margin-top: 1.25rem; }
.article .body strong { color: var(--paper); font-weight: 600; }

/* A direct quotation from Google's own documentation. Given its own weight
   because "Google says this" is worth more than anything we could assert. */
.quote {
  border-left: 2px solid var(--chalk);
  padding: 0.25rem 0 0.25rem 1.5rem;
  margin-inline: 0;
}
.quote p { color: var(--paper); font-size: var(--t-lead); line-height: 1.45; }
.quote cite {
  display: block; margin-top: 0.75rem;
  font-style: normal; font-size: var(--t-small); color: var(--paper-mute);
}

/* Plain figures. Not "stat cards" — a number in a box with a coloured icon is
   the exact thing that reads as marketing. */
.figures { border-top: 2px solid var(--paper); }
.fig {
  display: grid; grid-template-columns: minmax(4.5rem, auto) 1fr;
  gap: 0.35rem 1.5rem; align-items: baseline;
  padding-block: 1.125rem; border-bottom: 1px solid var(--rule);
}
.fig b { font-size: clamp(1.5rem, 3.4vw, 2.125rem); font-weight: 800; letter-spacing: -0.03em; }
.fig span { color: var(--paper-dim); font-size: var(--t-body); line-height: 1.5; }

.caveat {
  margin-top: 2rem; padding: 1.25rem 1.5rem;
  border: 1px solid var(--rule);
  font-size: var(--t-small); line-height: 1.6; color: var(--paper-mute);
}
.caveat b { color: var(--paper-dim); font-weight: 600; }

.do { list-style: none; margin: 0; padding: 0; }
.do li {
  padding-block: 1rem; border-bottom: 1px solid var(--rule);
  font-size: var(--t-body); color: var(--paper-dim); line-height: 1.55;
}
.do li b { color: var(--paper); font-weight: 600; }

.article .after {
  margin-top: clamp(3rem, 6vw, 4.5rem); padding-top: 2rem;
  border-top: 1px solid var(--rule);
}

/* ══════════════════════════════════════════════════════════════════════════
   THE MISSED CALL — the site's one signature thing

   Every site we build gets one interactive thing, and it must BE the subject
   rather than decorate it. Ours is the product itself: a call arrives while
   you are on a job, nobody gets to it, and the text goes out anyway.

   The test this has to pass is the same one we set for clients — does the
   moving thing answer a question the customer actually has? His question is
   "what does this actually do for me on a Tuesday", and this is the answer.
   ══════════════════════════════════════════════════════════════════════════ */

.ring {
  margin: 0;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: clamp(1.25rem, 3vw, 2rem);
  align-items: center;
}
@media (max-width: 980px) {
  .ring {
    margin-top: clamp(2.5rem, 6vw, 4rem);
    grid-template-columns: auto minmax(0, 1fr);
  }
}
@media (max-width: 560px) { .ring { grid-template-columns: minmax(0, 1fr); } }

/* ---- the handset: an iPhone -------------------------------------------
   Sam: "make it look like an iPhone and matching iOS in the demo."
   A generic phone reads as a mockup. The details are what make it read as a
   real phone on a real job: the Dynamic Island, the status bar, bubble tails,
   the typing dots, the home bar. Font is the system stack, so on an Apple
   device this renders in the actual system typeface. */
.handset {
  --w: clamp(10rem, 13vw, 12.5rem);
  width: var(--w);
  aspect-ratio: 393 / 852;
  border-radius: calc(var(--w) * 0.148);
  padding: calc(var(--w) * 0.0235);
  flex: none;
  background: linear-gradient(148deg, #8d8f92 0%, #3d4043 14%, #1c1e20 46%, #55585c 84%, #232527 100%);
  box-shadow:
    0 0 0 1px rgba(0,0,0,0.9),
    0 1.6rem 3.6rem -1rem rgba(0,0,0,0.85),
    inset 0 1px 1px rgba(255,255,255,0.28);
  position: relative;
  font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'Helvetica Neue', system-ui, sans-serif;
  letter-spacing: -0.01em;
}
.hs-screen {
  height: 100%;
  border-radius: calc(var(--w) * 0.126);
  background: #000;
  overflow: hidden;
  position: relative;
  display: grid;
  isolation: isolate;
}
.hs-screen > .hs-call, .hs-screen > .hs-thread { grid-area: 1 / 1; }

/* status bar + Dynamic Island */
.ios-status {
  position: absolute; inset: 0 0 auto 0; z-index: 4;
  height: calc(var(--w) * 0.125);
  display: flex; align-items: center; justify-content: space-between;
  padding: 0 calc(var(--w) * 0.075) 0 calc(var(--w) * 0.09);
  font-size: calc(var(--w) * 0.058); font-weight: 600; color: #fff;
  pointer-events: none;
}
.ios-time { padding-top: calc(var(--w) * 0.012); }
.ios-icons { display: flex; align-items: center; gap: calc(var(--w) * 0.026); }
.ios-icons svg { height: calc(var(--w) * 0.05); width: auto; fill: #fff; stroke: #fff; }
.ios-icons .ic-batt { height: calc(var(--w) * 0.055); }
.ios-island {
  position: absolute; z-index: 5;
  top: calc(var(--w) * 0.028); left: 50%; transform: translateX(-50%);
  width: calc(var(--w) * 0.30); height: calc(var(--w) * 0.083);
  background: #000; border-radius: 999px;
}
.ios-home {
  position: absolute; z-index: 5; bottom: calc(var(--w) * 0.022); left: 50%;
  transform: translateX(-50%);
  width: calc(var(--w) * 0.34); height: calc(var(--w) * 0.0165);
  background: rgba(255,255,255,0.92); border-radius: 999px;
}

/* the incoming call */
.hs-call {
  z-index: 2;
  display: flex; flex-direction: column; align-items: center;
  padding: calc(var(--w) * 0.30) 8% calc(var(--w) * 0.13);
  text-align: center;
  background: linear-gradient(180deg, #2b2f36 0%, #14171c 55%, #0a0c0f 100%);
  transition: opacity 380ms ease;
}
.ios-callname {
  margin: 0; font-size: calc(var(--w) * 0.108); font-weight: 500;
  color: #fff; letter-spacing: -0.02em;
}
.ios-callsub { margin: 0.3em 0 0; font-size: calc(var(--w) * 0.058); color: rgba(255,255,255,0.62); }
.ios-keys {
  margin-top: auto; width: 100%;
  display: flex; justify-content: space-around; align-items: flex-start;
}
.ios-key { display: flex; flex-direction: column; align-items: center; gap: calc(var(--w) * 0.035); }
.ios-key > svg {
  width: calc(var(--w) * 0.175); aspect-ratio: 1; padding: calc(var(--w) * 0.045);
  border-radius: 50%; fill: #fff; box-sizing: border-box;
}
.ios-key.decline > svg { background: #FF3B30; }
.ios-key.accept  > svg { background: #34C759; }
.ios-key i { font-style: normal; font-size: calc(var(--w) * 0.052); color: rgba(255,255,255,0.85); }

.ring[data-phase="ringing"] .ios-key.accept > svg {
  animation: hs-ring 1.1s ease-in-out infinite;
}
@keyframes hs-ring {
  0%, 100% { transform: none; box-shadow: 0 0 0 0 rgba(52,199,89,0.55); }
  50%      { transform: scale(1.06); box-shadow: 0 0 0 calc(var(--w) * 0.05) rgba(52,199,89,0); }
}
.ring[data-phase="missed"] .hs-call,
.ring[data-phase="texting"] .hs-call,
.ring[data-phase="replied"] .hs-call,
.ring[data-phase="done"] .hs-call { opacity: 0; }

/* Messages */
.hs-thread {
  z-index: 3;
  background: #000;
  display: grid; grid-template-rows: auto minmax(0, 1fr) auto;
  opacity: 0; transition: opacity 380ms ease;
}
.ring[data-phase="texting"] .hs-thread,
.ring[data-phase="replied"] .hs-thread,
.ring[data-phase="done"] .hs-thread { opacity: 1; }

.ios-nav {
  padding: calc(var(--w) * 0.135) 6% calc(var(--w) * 0.035);
  display: grid; justify-items: center; gap: calc(var(--w) * 0.018);
  background: rgba(28,28,30,0.92);
  border-bottom: 0.5px solid rgba(255,255,255,0.14);
  position: relative;
}
.ios-back {
  position: absolute; left: 5%; bottom: calc(var(--w) * 0.055);
  color: #0A84FF; font-size: calc(var(--w) * 0.11); line-height: 1;
}
.ios-avatar {
  width: calc(var(--w) * 0.115); aspect-ratio: 1; border-radius: 50%;
  background: linear-gradient(180deg, #8E8E93, #636366);
  color: #fff; font-size: calc(var(--w) * 0.048); font-weight: 500;
  display: grid; place-items: center;
}
.ios-who { font-size: calc(var(--w) * 0.05); color: rgba(255,255,255,0.92); }

.hs-msgs {
  padding: calc(var(--w) * 0.05) calc(var(--w) * 0.045);
  display: flex; flex-direction: column; gap: calc(var(--w) * 0.032);
  overflow: hidden;
}
.hs-msg {
  position: relative;
  max-width: 78%;
  padding: calc(var(--w) * 0.042) calc(var(--w) * 0.058);
  border-radius: calc(var(--w) * 0.085);
  font-size: calc(var(--w) * 0.058);
  line-height: 1.28;
  animation: hs-pop 340ms cubic-bezier(.2,.9,.3,1) both;
}
.hs-msg.out { align-self: flex-end; background: #0A84FF; color: #fff; }
.hs-msg.in  { align-self: flex-start; background: #26262A; color: #fff; }
/* the tail — the detail that stops it looking like a chat widget */
.hs-msg::after {
  content: ""; position: absolute; bottom: 0;
  width: calc(var(--w) * 0.055); height: calc(var(--w) * 0.055);
}
.hs-msg.out::after {
  right: calc(var(--w) * -0.008);
  background: radial-gradient(circle at 100% 0, transparent calc(var(--w) * 0.055), #0A84FF 0);
}
.hs-msg.in::after {
  left: calc(var(--w) * -0.008);
  background: radial-gradient(circle at 0 0, transparent calc(var(--w) * 0.055), #26262A 0);
}
.hs-msg .tick {
  position: absolute; right: 0.15em; bottom: calc(var(--w) * -0.075);
  font-size: calc(var(--w) * 0.042); color: rgba(255,255,255,0.45);
  white-space: nowrap;
}
/* he is typing */
.hs-typing {
  align-self: flex-start; display: flex; gap: calc(var(--w) * 0.022);
  padding: calc(var(--w) * 0.052) calc(var(--w) * 0.058);
  background: #26262A; border-radius: calc(var(--w) * 0.085);
  animation: hs-pop 300ms ease both;
}
.hs-typing span {
  width: calc(var(--w) * 0.038); aspect-ratio: 1; border-radius: 50%;
  background: rgba(255,255,255,0.55);
  animation: hs-dot 1.25s ease-in-out infinite;
}
.hs-typing span:nth-child(2) { animation-delay: 0.18s; }
.hs-typing span:nth-child(3) { animation-delay: 0.36s; }
@keyframes hs-dot { 0%,60%,100% { opacity:.35; transform:none } 30% { opacity:1; transform:translateY(-12%) } }
@keyframes hs-pop {
  from { opacity: 0; transform: translateY(0.45rem) scale(0.96); }
  to   { opacity: 1; transform: none; }
}

.ios-bar {
  padding: calc(var(--w) * 0.03) calc(var(--w) * 0.045) calc(var(--w) * 0.075);
  background: rgba(28,28,30,0.92);
  border-top: 0.5px solid rgba(255,255,255,0.14);
}
.ios-field {
  display: block; border: 0.5px solid rgba(255,255,255,0.22);
  border-radius: 999px; padding: calc(var(--w) * 0.028) calc(var(--w) * 0.06);
  font-size: calc(var(--w) * 0.05); color: rgba(255,255,255,0.4);
}

/* ---- the log beside it --------------------------------------------------- */
.ring-log { min-width: 0; }
.ring-clock {
  margin: 0 0 1.1rem; padding-bottom: 0.9rem;
  border-bottom: 1px solid var(--rule);
  display: flex; align-items: baseline; gap: 0.7rem; flex-wrap: wrap;
  font-size: var(--t-small);
}
.ring-clock [data-clock] {
  font-variant-numeric: tabular-nums;
  font-weight: 800; font-size: 1.5rem; color: var(--paper);
  min-width: 3.4ch; letter-spacing: -0.02em;
}
.ring-state { color: var(--paper-mute); }
.ring-steps { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.7rem; }
.ring-steps li {
  display: grid; grid-template-columns: 3.4rem minmax(0, 1fr); gap: 0.85rem;
  font-size: var(--t-small); color: var(--paper-dim); line-height: 1.45;
  animation: step-in 420ms ease both;
}
.ring-steps li b { color: var(--paper); font-weight: 600; }
.ring-steps li i {
  font-style: normal; font-variant-numeric: tabular-nums;
  color: var(--paper-mute); font-size: var(--t-micro);
  padding-top: 0.15em;
}
.ring-steps li.good b { color: var(--chalk-soft); }
@keyframes step-in { from { opacity: 0; transform: translateY(0.4rem); } to { opacity: 1; transform: none; } }

.ring-again {
  margin-top: 1.4rem;
  font: inherit; font-size: var(--t-small); font-weight: 600;
  color: var(--paper-dim); background: none;
  border: 1px solid var(--rule); border-radius: 2px;
  padding: 0.55rem 1rem; cursor: pointer;
  opacity: 0; pointer-events: none; transition: opacity 300ms ease, color 150ms, border-color 150ms;
}
.ring[data-phase="done"] .ring-again { opacity: 1; pointer-events: auto; }
.ring-again:hover { color: var(--paper); border-color: var(--paper-mute); }
.ring-again:focus-visible { outline: 2px solid var(--chalk); outline-offset: 2px; }

/* Reduced motion is a designed still, not a broken animation — and the copy
   must never describe movement the visitor is not being shown. */
@media (prefers-reduced-motion: reduce) {
  .hs-msg, .ring-steps li { animation: none; }
  .ring[data-phase="ringing"] .hs-key.accept::after { animation: none; opacity: 0.5; }
  .ring-again { opacity: 1; pointer-events: auto; }
}

/* ══════════════════════════════════════════════════════════════════════════
   THE SNAP — the page changes rooms

   Sam: "would it be dumb to make it do a super cool feature where it dissolves
   out and turns white... and the logo flips?"

   Not dumb, but the DISSOLVE would have been. A background that gradually
   fades light as you scroll is on the list of tells we reject, and contrast
   collapses in the middle of it. So it snaps instead, at a section boundary,
   in under a fifth of a second.

   It earns its place three ways: the accent is a chalk line and a snap is what
   you do with one, so it is the name made physical; the portfolio is full of
   photographs and screenshots, which genuinely read better on white; and it
   answers dark-or-light with BOTH, as chapters.

   Every surface colour is a variable, so a section flips by redefining six of
   them. Nothing below this line needs to know which room it is in.
   ══════════════════════════════════════════════════════════════════════════ */

.on-light {
  --ink:        #FFFFFF;
  --ink-2:      #F4F4F2;
  --ink-3:      #E7E7E4;
  --paper:      #000000;
  --paper-dim:  #4A4A4E;
  --paper-mute: #77777C;
  --rule:       #DEDEDA;
  background: var(--ink);
  color: var(--paper);
}

/* The masthead rides over the top and inverts as the boundary passes under it.
   This is the part Sam meant by "the logo flips" — and it is free, because the
   wordmark inside the SVG is filled with currentColor. */
.masthead {
  position: fixed;
  transition: color var(--flip) ease;
  mix-blend-mode: normal;
}
.masthead.over-light { color: #000; }
.masthead.over-light nav a { color: #4A4A4E; }
.masthead.over-light nav a:hover { color: #000; }
.masthead.over-light .wordmark { color: #000; }

body { transition: background-color var(--flip) ease; }

@media (prefers-reduced-motion: reduce) {
  .masthead, body { transition: none; }
}

/* ══════════════════════════════════════════════════════════════════════════
   THE FIVE WORLDS

   Sam: "as we scroll I want it to morph into each companies world."

   The claim on this page is that no two sites we build look alike. Every
   portfolio makes that claim, and every portfolio then shows five screenshots
   in five identical grey cards — which quietly argues the opposite.

   So the page stops asserting it and starts DOING it. Scroll past Streetside
   and the page IS Streetside: its black-blue, its bulk, its bleed. Scroll on
   and it snaps into Cedar & Stone's warm paper and serif. The argument and the
   demonstration become the same object.

   Each world redefines six surface variables and picks a different layout.
   Nothing else in the stylesheet needs to know which world it is in.
   ══════════════════════════════════════════════════════════════════════════ */

.demo {
  border: 0;
  padding-block: clamp(4.5rem, 11vh, 9rem);
  background: var(--ink);
  color: var(--paper);
  transition: background-color var(--flip) ease, color var(--flip) ease;
}
.demo:last-of-type { border: 0; }
.demo-inner { min-width: 0; }

/* 1 · STREETSIDE — pressure washing. Wet asphalt at night. */
.demo[data-world="streetside"] {
  --ink: #08131A; --ink-2: #0E212C; --ink-3: #16323F;
  --paper: #EAF5FA; --paper-dim: #9DC2D2; --paper-mute: #6A8FA0;
  --rule: #163643; --c: #4FB8E8;
}
/* 2 · CEDAR & STONE — landscaping. Warm paper and a proper serif. */
.demo[data-world="cedarstone"] {
  --ink: #F3EEE3; --ink-2: #E9E1D2; --ink-3: #DED4C0;
  --paper: #1E2318; --paper-dim: #55604A; --paper-mute: #8A9179;
  --rule: #DAD1BE; --c: #4E7C36;
}
/* 3 · RIDGELINE — roofing. Slate, weather, a rust-coloured warning. */
.demo[data-world="ridgeline"] {
  --ink: #191D22; --ink-2: #21262C; --ink-3: #2C333A;
  --paper: #EDF0F3; --paper-dim: #A6AEB8; --paper-mute: #767E89;
  --rule: #2B3238; --c: #E07A3C;
}
/* 4 · FAIR WEATHER — painting. Domestic daylight. */
.demo[data-world="fairweather"] {
  --ink: #FBF9F6; --ink-2: #F2EEE8; --ink-3: #E7E1D8;
  --paper: #292622; --paper-dim: #6A635B; --paper-mute: #948C82;
  --rule: #E5DFD6; --c: #6E7F6A;
}
/* 5 · LEVEL FIVE — drywall. A contract drawing: white, ruled, monospaced. */
.demo[data-world="levelfive"] {
  --ink: #FFFFFF; --ink-2: #F7F7F5; --ink-3: #EDEDEA;
  --paper: #14140F; --paper-dim: #56564E; --paper-mute: #8A8A82;
  --rule: #E2E2DC; --c: #8A6B36;
}

.demo .trade { color: var(--c); }
.demo .what  { color: var(--paper-dim); }
.demo .what b { color: var(--paper); }
.demo .shots .desk, .demo .shots .phone { border-color: var(--rule); }

/* ---- the five layouts ---------------------------------------------------
   Same content, five different shapes. This is the part that stops the
   section reading as one template repeated five times. */

/* 1 · bleed — the screenshot runs off the right edge of the page */
.w-bleed .demo-head { grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr); }
.w-bleed h3 { font-size: clamp(2.75rem, 8vw, 6rem); letter-spacing: -0.045em; }
.w-bleed .shots { margin-right: calc(var(--gutter) * -1); }
.w-bleed .shots .desk { border-right: 0; }

/* 2 · centred — a printed brochure. Everything narrow and calm. */
.w-centred .demo-inner { max-width: 62rem; }
.w-centred .demo-head { grid-template-columns: minmax(0, 1fr); text-align: center; justify-items: center; }
.w-centred .trade { letter-spacing: 0.22em; }
.w-centred h3 {
  font-family: 'Archivo', Georgia, serif;
  font-weight: 500; font-stretch: 88%;
  font-size: clamp(2.25rem, 5vw, 3.75rem); letter-spacing: -0.01em;
}
.w-centred .what { max-width: 44ch; margin-inline: auto; }
.w-centred .shots { max-width: 52rem; margin-inline: auto; }
.w-centred .go { text-align: center; }

/* 3 · split — half text, half picture, edge to edge */
.w-split .demo-inner {
  display: grid; grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr);
  gap: clamp(1.75rem, 4vw, 4rem); align-items: center;
}
.w-split .demo-head { grid-template-columns: minmax(0, 1fr); align-content: center; }
.w-split .shots { margin-top: 0; }
.w-split .go { grid-column: 1; margin-top: 2rem; }
@media (max-width: 900px) { .w-split .demo-inner { grid-template-columns: minmax(0, 1fr); } }

/* 4 · wide — an enormous thin name sitting over a low, wide picture */
.w-wide .demo-head { grid-template-columns: minmax(0, 1fr); gap: 1.25rem; }
.w-wide h3 {
  font-weight: 300; font-stretch: 112%;
  font-size: clamp(3rem, 11vw, 8.5rem); letter-spacing: -0.05em; line-height: 0.92;
}
.w-wide .what { max-width: 52ch; }

/* 5 · drawing — a contract drawing. Ruled, monospaced, dimensioned. */
.w-drawing .demo-inner { border: 1px solid var(--rule); padding: clamp(1.5rem, 4vw, 3rem); }
.w-drawing .trade,
.w-drawing .go .btn {
  font-family: ui-monospace, 'SFMono-Regular', Menlo, Consolas, monospace;
  letter-spacing: 0.06em;
}
.w-drawing h3 { font-weight: 600; letter-spacing: -0.01em; }
.w-drawing .shots { border: 1px solid var(--rule); padding: clamp(0.75rem, 2vw, 1.5rem); }
.w-drawing .shots::before {
  content: "ELEV. 01 — HOME"; position: absolute; top: -0.75rem; left: 1rem;
  background: var(--ink); padding-inline: 0.5rem;
  font: 400 var(--t-micro)/1 ui-monospace, monospace; letter-spacing: 0.12em;
  color: var(--paper-mute);
}

/* The mark turns over when the page changes world.
   Sam asked whether it should always be spinning. It should not — a logo that
   never stops rotating is decoration, and it is one of the tells. This turns
   ONCE, at the moment the page turns over, so the movement means something. */
.wordmark .logo { transform-style: preserve-3d; backface-visibility: visible; }
.masthead.flip .logo { animation: mark-turn 520ms cubic-bezier(.5,0,.2,1); }
@keyframes mark-turn {
  from { transform: rotateY(0deg); }
  to   { transform: rotateY(360deg); }
}
@media (prefers-reduced-motion: reduce) {
  .masthead.flip .logo { animation: none; }
}

/* ── The lockup contracts to the mark ──────────────────────────────────────
   Sam: "as we scroll the word disappear and then it becomes just the square?"

   Right instinct, and it settles the square-or-words question properly: both,
   at the moments each one earns. At the top of the page nobody knows this
   brand, so it gets the full name. Once you are reading, the name has done its
   job and the mark can hold the corner on its own.

   The words fade and the frame closes from the right, so the square never
   moves a pixel — the lockup contracts around a fixed point rather than
   sliding about. */
.masthead { --logo-h: clamp(1.9rem, 2.9vw, 2.4rem); }
.wordmark {
  overflow: hidden;
  /* full lockup is 1370 × 320 viewBox units wide → 4.28 × its height */
  max-width: calc(var(--logo-h) * 4.32);
  transition: max-width 340ms cubic-bezier(.4,0,.2,1);
}
.wordmark .logo {
  height: var(--logo-h);
  /* flex must NOT shrink this: an SVG squeezed by a narrower flex line scales
     its whole content down instead of being clipped, and the mark collapses to
     a sliver. It keeps its full width and the parent's overflow does the work. */
  flex: none;
  width: calc(var(--logo-h) * 4.32);
  /* and the global `svg { max-width: 100% }` reset has to be waived here, or
     it caps the artwork to the closing frame and scales it down instead. */
  max-width: none;
}
.wordmark .wm-words {
  opacity: 1;
  transition: opacity 200ms ease 90ms;
}
/* the symbol alone is 310 of those 1370 units → 0.97 × its height */
.masthead.compact .wordmark { max-width: calc(var(--logo-h) * 1.02); }
.masthead.compact .wordmark .wm-words { opacity: 0; transition-delay: 0ms; }

@media (prefers-reduced-motion: reduce) {
  .wordmark, .wordmark .wm-words { transition: none; }
}

/* ══════════════════════════════════════════════════════════════════════════
   WHERE YOU ARE — the cube

   Sam: "A 3d cube that slowly moves around?" and, more importantly,
   "Are people gonna understand what's happening?"

   No, they were not. The page was changing world and nothing said why, so it
   read as flicker rather than as an argument. A transformation nobody can read
   is worse than no transformation.

   A cube drifting on its own would have been decoration — the exact tell we
   have spent this build avoiding. This one is the readout: six faces, home
   plus one per company, and it turns a quarter turn only when the page
   actually crosses into a new world. It never moves for its own sake.

   It is also the live instrument every site we build is supposed to have and
   ours did not.
   ══════════════════════════════════════════════════════════════════════════ */

.rail {
  position: fixed; z-index: 20;
  right: var(--gutter); bottom: clamp(1.25rem, 3vh, 2.25rem);
  display: flex; align-items: center; gap: 0.85rem;
  pointer-events: none;
  color: var(--paper);
  transition: color var(--flip) ease;
}
.cube-stage { perspective: 420px; width: 2.6rem; height: 2.6rem; flex: none; }
.cube {
  position: relative; width: 100%; height: 100%;
  transform-style: preserve-3d;
  transition: transform 760ms cubic-bezier(.55,0,.15,1);
}
.cube .face {
  position: absolute; inset: 0;
  display: grid; place-items: center;
  font: 700 0.6875rem/1 ui-monospace, monospace;
  letter-spacing: 0.06em;
  color: #fff;
  background: var(--f);
  box-shadow: inset 0 0 0 1px rgba(255,255,255,0.14);
  backface-visibility: hidden;
}
/* six faces of a 2.6rem cube — half-edge is 1.3rem */
.cube .f0 { transform: rotateY(0deg)    translateZ(1.3rem); }
.cube .f1 { transform: rotateY(90deg)   translateZ(1.3rem); }
.cube .f2 { transform: rotateY(180deg)  translateZ(1.3rem); }
.cube .f3 { transform: rotateY(-90deg)  translateZ(1.3rem); }
.cube .f4 { transform: rotateX(90deg)   translateZ(1.3rem); }
.cube .f5 { transform: rotateX(-90deg)  translateZ(1.3rem); }
/* A resting tilt so two faces are always visible: square-on it reads as a flat
   square, which defeats the point of it being a cube at all. */
.cube { --tilt: rotateX(-13deg) rotateY(-19deg); }
.cube[data-face="0"] { transform: var(--tilt) rotateY(0deg); }
.cube[data-face="1"] { transform: var(--tilt) rotateY(-90deg); }
.cube[data-face="2"] { transform: var(--tilt) rotateY(-180deg); }
.cube[data-face="3"] { transform: var(--tilt) rotateY(-270deg); }
.cube[data-face="4"] { transform: var(--tilt) rotateX(-90deg); }
.cube[data-face="5"] { transform: var(--tilt) rotateX(90deg); }

.rail-label {
  display: grid; gap: 0.1rem;
  font-size: var(--t-micro); line-height: 1.25;
}
/* Fixed elements do not inherit a section's colours, so the rail is told
   which room it is over. Without this the label was white-on-white over
   Cedar & Stone, Fair Weather and Level Five. */
.rail.over-light { color: #000; }
.rail.over-light .rail-n, .rail.over-light .rail-trade { color: #6A6A6E; }
.rail-n     { font-family: ui-monospace, monospace; color: var(--paper-mute); letter-spacing: 0.08em; }
.rail-name  { font-weight: 700; }
.rail-trade { color: var(--paper-mute); }

@media (max-width: 620px) {
  .rail { right: auto; left: var(--gutter); bottom: 0.9rem; gap: 0.6rem; }
  .cube-stage { width: 2rem; height: 2rem; perspective: 320px; }
  .cube .face { font-size: 0.5625rem; }
  .cube .f0,.cube .f1,.cube .f2,.cube .f3 { transform: rotateY(var(--r,0deg)) translateZ(1rem); }
  .cube .f1 { --r: 90deg; } .cube .f2 { --r: 180deg; } .cube .f3 { --r: -90deg; }
  .cube .f4 { transform: rotateX(90deg) translateZ(1rem); }
  .cube .f5 { transform: rotateX(-90deg) translateZ(1rem); }
  .rail-trade { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .cube { transition: none; }
}

/* ── The worlds bring their own letterforms ────────────────────────────────
   Sam: "the whole world transforms when we enter into that company. Right?"

   Colour and layout were changing but the TYPE was not — every world was still
   Archivo, with Cedar & Stone's serif faked by stretching it. That is the one
   thing a designer would spot instantly, because a typeface is most of what
   makes a brand feel like itself.

   So each world now uses the real display face from the real site: Streetside's
   condensed Barlow, Cedar & Stone's Fraunces, Ridgeline's Zilla Slab, Fair
   Weather's Instrument Serif, Level Five's mono. Files are loaded from the
   demo that owns them — no copies to drift out of sync.

   They are fetched on approach by worlds.js, not up front, so the page still
   opens in ten requests. Until one arrives the fallback renders and swaps. */
.demo[data-world="streetside"]  h3 { font-family: 'Barlow Condensed', 'Archivo', sans-serif; font-weight: 700; font-stretch: normal; letter-spacing: -0.01em; }
.demo[data-world="streetside"]  .trade { font-family: 'Barlow Condensed', 'Archivo', sans-serif; }
.demo[data-world="cedarstone"]  h3 { font-family: 'Fraunces', 'Archivo', Georgia, serif; font-weight: 400; font-stretch: normal; }
.demo[data-world="ridgeline"]   h3 { font-family: 'Zilla Slab', 'Archivo', Georgia, serif; font-weight: 700; }
.demo[data-world="fairweather"] h3 { font-family: 'Instrument Serif', 'Archivo', Georgia, serif; font-weight: 400; font-stretch: normal; letter-spacing: -0.02em; }
.demo[data-world="levelfive"]   h3 { font-family: 'Spline Sans Mono', ui-monospace, monospace; font-weight: 600; letter-spacing: -0.02em; }

/* ── Each world gets a surface, not just a colour ──────────────────────────
   Sam: "a full transformation where it feels completely different and not at
   all the same as the next world."

   Colour, type and layout were changing; the SURFACE was not. Every world was
   still a flat fill, and flat fills all feel like the same page repainted.
   Each one now has a texture built from its own trade, drawn in CSS so there
   are no image requests and nothing to 404. */

.demo { position: relative; isolation: isolate; }
.demo::before {
  content: ""; position: absolute; inset: 0; z-index: -1;
  pointer-events: none;
}

/* wet asphalt under a work light */
.demo[data-world="streetside"]::before {
  background:
    radial-gradient(120% 70% at 12% 0%, rgba(79,184,232,0.16), transparent 60%),
    radial-gradient(90% 60% at 88% 100%, rgba(79,184,232,0.09), transparent 65%);
}
/* laid paper */
.demo[data-world="cedarstone"]::before {
  background-image: repeating-linear-gradient(90deg, rgba(30,35,24,0.045) 0 1px, transparent 1px 3px);
  opacity: 0.75;
}
/* slate, and the pitch of a roof */
.demo[data-world="ridgeline"]::before {
  background-image: repeating-linear-gradient(-38deg, rgba(255,255,255,0.035) 0 1px, transparent 1px 14px);
}
/* daylight moving across a wall */
.demo[data-world="fairweather"]::before {
  background: linear-gradient(103deg, rgba(110,127,106,0.10) 0%, transparent 42%, rgba(255,240,214,0.30) 100%);
}
/* graph paper — the drawing the quote is taken off */
.demo[data-world="levelfive"]::before {
  background-image:
    linear-gradient(rgba(20,20,15,0.055) 1px, transparent 1px),
    linear-gradient(90deg, rgba(20,20,15,0.055) 1px, transparent 1px);
  background-size: 26px 26px;
}

/* ── and its own idea of what a button is ─────────────────────────────── */
.demo .btn { --btn-bg: var(--c); --btn-fg: var(--ink); }
.demo .btn:hover { --btn-bg: var(--paper); }

/* industrial signage: square, condensed, stencilled */
.demo[data-world="streetside"] .btn {
  border-radius: 0; font-weight: 800; font-stretch: 88%;
  letter-spacing: 0.06em; text-transform: uppercase; font-size: var(--t-small);
  padding-inline: 1.9rem;
}
/* a printed brochure: an outlined pill, set wide and quiet */
.demo[data-world="cedarstone"] .btn {
  --btn-bg: transparent; --btn-fg: var(--paper);
  border-color: var(--paper); border-radius: 999px;
  font-weight: 600; letter-spacing: 0.03em; padding-inline: 2.1rem;
}
.demo[data-world="cedarstone"] .btn:hover { --btn-bg: var(--paper); --btn-fg: var(--ink); }
/* a spec sheet: solid, hard-edged, plain */
.demo[data-world="ridgeline"] .btn { border-radius: 2px; }
/* a gallery caption: no box at all, just a rule that grows */
.demo[data-world="fairweather"] .btn {
  --btn-bg: transparent; --btn-fg: var(--paper);
  padding-inline: 0; min-height: 44px; position: relative;
  font-weight: 600;
}
.demo[data-world="fairweather"] .btn::before {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0;
  height: 2px; background: var(--c);
  transform-origin: left; transition: transform 260ms cubic-bezier(.3,.9,.4,1);
}
.demo[data-world="fairweather"] .btn:hover::before { transform: scaleX(1.06); }
.demo[data-world="fairweather"] .btn:hover { --btn-bg: transparent; }
/* an annotation on a drawing: outlined, monospaced, small */
.demo[data-world="levelfive"] .btn {
  --btn-bg: transparent; --btn-fg: var(--paper);
  border-color: var(--paper); border-radius: 0;
  font-family: 'Spline Sans Mono', ui-monospace, monospace;
  font-size: var(--t-small); letter-spacing: 0.06em; text-transform: uppercase;
}
.demo[data-world="levelfive"] .btn:hover { --btn-bg: var(--paper); --btn-fg: var(--ink); }

/* ── jumping between worlds ────────────────────────────────────────────────
   Sam: "I also wanna be able to jump to the different worlds."
   The cube says where you are; these say where you can go. Real buttons, so
   they work from the keyboard and read properly to a screen reader. */
.rail { pointer-events: none; }
.rail-jump {
  display: flex; gap: 0.3rem; margin-top: 0.4rem;
  pointer-events: auto;
}
.rail-jump button {
  width: 1.05rem; height: 0.4rem; padding: 0; border: 0; border-radius: 1px;
  background: var(--j); opacity: 0.4; cursor: pointer;
  transition: opacity 160ms ease, transform 160ms ease;
}
.rail-jump button:hover { opacity: 0.8; transform: scaleY(1.5); }
.rail-jump button[aria-current="true"] { opacity: 1; transform: scaleY(1.6); }
.rail-jump button:focus-visible { outline: 2px solid currentColor; outline-offset: 2px; opacity: 1; }
.sr {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap;
}

/* ══════════════════════════════════════════════════════════════════════════
   AFTER THE WORLDS

   The page used to fall off a cliff here: five vivid worlds and then flat text
   on flat black for the rest of it, which read like a worse website took over.
   These sections get a spine of their own — numbers, weight, and somewhere to
   put your eye — without borrowing any world's identity, because from here on
   the page is ours again.
   ══════════════════════════════════════════════════════════════════════════ */

.included { border-top: 1px solid var(--rule); }
.get { position: relative; counter-increment: get; }
.get h3::before {
  content: counter(get, decimal-leading-zero);
  display: block;
  font: 400 var(--t-micro)/1 ui-monospace, monospace;
  letter-spacing: 0.12em;
  color: var(--chalk);
  margin-bottom: 0.6rem;
}
.get::after {
  content: ""; position: absolute; left: 0; top: -1px; height: 2px; width: 0;
  background: var(--chalk); transition: width 420ms cubic-bezier(.16,.9,.3,1);
}
.get:hover::after { width: 100%; }
@media (prefers-reduced-motion: reduce) { .get::after { transition: none; } }

/* ---- the price, and the thing it was missing ---------------------------- */
.price-act {
  display: flex; flex-wrap: wrap; align-items: center; gap: 1rem 1.75rem;
  margin-top: clamp(1.75rem, 4vw, 2.5rem);
  padding-top: clamp(1.5rem, 3vw, 2rem);
  border-top: 1px solid var(--rule);
}
.price-tel {
  font-weight: 700; font-size: var(--t-lead); text-decoration: none;
  letter-spacing: -0.01em;
}
.price-tel:hover { color: var(--chalk-soft); }
.btn-lg { min-height: 58px; padding: 0.95rem 1.8rem; font-size: var(--t-body); }

/* ---- the measurement ---------------------------------------------------- */
.proof { padding-block: clamp(4rem, 9vh, 7rem) 0; border-top: 1px solid var(--rule); }
.vs {
  margin-top: clamp(2rem, 4vw, 3rem);
  display: grid; grid-template-columns: 1fr 1fr; gap: 1px;
  background: var(--rule);
  border: 1px solid var(--rule);
}
.vs-col { background: var(--ink); padding: clamp(1.5rem, 3.5vw, 2.5rem); }
.vs-us  { background: var(--ink-2); }
.vs-tag {
  font: 700 var(--t-micro)/1 ui-monospace, monospace;
  letter-spacing: 0.1em; text-transform: uppercase; color: var(--paper-mute);
}
.vs-us .vs-tag { color: var(--chalk); }
.vs-big {
  margin-top: 1rem; font-size: clamp(3rem, 8vw, 5.5rem); font-weight: 800;
  letter-spacing: -0.04em; line-height: 0.95;
  font-variant-numeric: tabular-nums;
}
.vs-big span {
  display: block; font-size: var(--t-small); font-weight: 400;
  letter-spacing: 0; color: var(--paper-mute); margin-top: 0.5rem;
}
.vs dl { margin: clamp(1.5rem, 3vw, 2rem) 0 0; display: grid; gap: 0.6rem; }
.vs dl div {
  display: flex; justify-content: space-between; gap: 1rem;
  padding-bottom: 0.6rem; border-bottom: 1px solid var(--rule);
  font-size: var(--t-small);
}
.vs dt { color: var(--paper-mute); }
.vs dd { margin: 0; font-weight: 700; font-variant-numeric: tabular-nums; }
.proof-note { margin-top: 1.25rem; font-size: var(--t-micro); color: var(--paper-mute); max-width: 60ch; }
@media (max-width: 700px) { .vs { grid-template-columns: 1fr; } }

/* ---- talk to us --------------------------------------------------------- */
.talk {
  margin-top: clamp(4rem, 9vh, 7rem);
  padding-block: clamp(4rem, 9vh, 7rem);
  border-top: 1px solid var(--rule);
  background: var(--ink-2);
}
.talk-inner {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 26rem);
  gap: clamp(2rem, 6vw, 5rem); align-items: start;
}
@media (max-width: 900px) { .talk-inner { grid-template-columns: minmax(0, 1fr); } }
.talk-lead { margin-top: 1.25rem; font-size: var(--t-lead); color: var(--paper-dim); max-width: 40ch; }

/* The number is the biggest tappable thing on the page, because the whole
   argument of this business is that a customer should not have to work to
   reach you. It would be absurd to make that argument in small grey text. */
.talk-tel {
  display: inline-flex; flex-direction: column; gap: 0.15rem;
  margin-top: clamp(1.75rem, 4vw, 2.5rem);
  padding: 1rem 1.75rem;
  background: var(--chalk); color: #fff; text-decoration: none;
  border-radius: 2px;
  transition: background 150ms ease;
}
.talk-tel:hover { background: #4B82EA; }
.talk-tel-label { font-size: var(--t-micro); letter-spacing: 0.12em; text-transform: uppercase; opacity: 0.8; }
.talk-tel-num { font-size: clamp(1.5rem, 3.5vw, 2.125rem); font-weight: 800; letter-spacing: -0.02em; }
.talk-facts {
  list-style: none; margin: clamp(1.75rem, 4vw, 2.5rem) 0 0; padding: 0;
  display: grid; gap: 0.7rem; font-size: var(--t-small); color: var(--paper-mute);
  max-width: 44ch;
}
.talk-facts li { padding-left: 1.1rem; position: relative; }
.talk-facts li::before { content: "—"; position: absolute; left: 0; color: var(--chalk); }

.talk-form { display: grid; gap: 1rem; }
.hp { position: absolute; left: -9999px; }
.f-row { display: grid; gap: 0.4rem; }
.f-row label { font-size: var(--t-small); font-weight: 600; }
.f-row label span { font-weight: 400; color: var(--paper-mute); }
.f-row input, .f-row textarea {
  font: inherit; font-size: var(--t-body);
  color: var(--paper); background: var(--ink);
  border: 1px solid var(--rule); border-radius: 2px;
  padding: 0.7rem 0.85rem; width: 100%;
}
.f-row input:focus-visible, .f-row textarea:focus-visible {
  outline: 2px solid var(--chalk); outline-offset: 1px; border-color: var(--chalk);
}
.f-row textarea { resize: vertical; }
.talk-form .btn-lg { justify-self: start; margin-top: 0.5rem; }
.f-note { font-size: var(--t-micro); color: var(--paper-mute); }

/* ---- the masthead call to action ---------------------------------------- */
.nav-cta {
  padding: 0.4rem 0.9rem;
  border: 1px solid currentColor; border-radius: 2px;
  color: var(--paper) !important;
}
.nav-cta:hover { background: var(--chalk); border-color: var(--chalk); color: #fff !important; }
.masthead.over-light .nav-cta { color: #000 !important; }
.masthead.over-light .nav-cta:hover { color: #fff !important; }
@media (max-width: 520px) { .masthead nav a:not(.nav-cta) { display: none; } }

/* The price box was a two-column grid with the number alone in the left column,
   which left a tall empty well under it. The call to action now lives in that
   column, directly under the price, where the eye already is. */
/* Every child is placed explicitly. Giving ONE of them a definite row was
   enough to move the auto-placement cursor: the terms took column one and
   $297 ended up stranded on the right, which is exactly backwards. */
.price-num   { grid-column: 1; grid-row: 1; }
.price-terms { grid-column: 2; grid-row: 1 / span 2; }
.price-box > .price-act {
  grid-column: 1; grid-row: 2;
  align-self: start;
  flex-direction: column; align-items: flex-start; gap: 1rem;
  border-top: 0; padding-top: 0;
  margin-top: clamp(1.5rem, 3vw, 2rem);
}
@media (max-width: 760px) {
  .price-box { grid-template-columns: minmax(0, 1fr); }
  .price-num, .price-terms, .price-box > .price-act { grid-column: 1; grid-row: auto; }
}

/* The cube is a WORLD indicator, so it belongs on screen only while there are
   worlds on screen. It used to sit over the body copy for the whole rest of
   the page, which is both a collision and a lie — there is nothing to indicate
   down there. */
.rail {
  opacity: 0; visibility: hidden;
  transition: opacity 260ms ease, visibility 260ms;
}
.rail.showing { opacity: 1; visibility: visible; }
@media (prefers-reduced-motion: reduce) { .rail { transition: none; } }

/* ── Tap it and the real site loads, here ─────────────────────────────────
   The cards showed a PNG under the words "drag across it and watch the jet do
   it" — describing motion the visitor was not being shown, which is the exact
   thing we forbid on client sites. Now the screenshot is a door: press it and
   the actual demo loads in place and you can use it without leaving the page.

   Nothing loads until it is asked for, so the page still opens in nine
   requests. It is our own site in the frame, so still zero third parties. */
.shots { position: relative; }
.live-go {
  position: absolute; left: 50%; bottom: clamp(1rem, 3vw, 2rem);
  transform: translateX(-50%);
  font: inherit; font-size: var(--t-small); font-weight: 700;
  color: var(--ink); background: var(--paper);
  border: 0; border-radius: 2px;
  padding: 0.7rem 1.25rem; cursor: pointer;
  box-shadow: 0 0.5rem 1.5rem -0.4rem rgba(0,0,0,0.55);
  transition: transform 160ms ease, background 160ms ease;
}
.live-go:hover { transform: translateX(-50%) translateY(-2px); background: var(--c, var(--chalk)); color: var(--ink); }
.live-go:active { transform: translateX(-50%); }
.live-go:focus-visible { outline: 2px solid var(--c, var(--chalk)); outline-offset: 3px; }

/* Getting above the fixed masthead took two goes. `z-index` on the frame alone
   did nothing, because .demo carries `isolation: isolate` for its world texture
   and that traps every child's z-index inside the card — so the masthead's
   "Talk to us" landed squarely on top of the frame's Close button and the
   visitor was stuck inside the demo.
   The article itself has to be raised, and the masthead simply gets out of the
   way: while you are inside a demo, our navigation is noise. */
.live-frame {
  position: relative;
  margin-top: clamp(1.75rem, 4vw, 3rem);
  scroll-margin-top: 1.5rem;
}
.demo.has-live { z-index: 40; }
.live-open .masthead,
.live-open .rail { opacity: 0; pointer-events: none; }
.live-open .masthead { transition: opacity 180ms ease; }
.live-bar {
  display: flex; align-items: center; gap: 0.75rem;
  padding: 0.55rem 0.85rem;
  background: var(--ink-3); border: 1px solid var(--rule); border-bottom: 0;
  font: 400 var(--t-micro)/1 ui-monospace, monospace; color: var(--paper-mute);
}
.live-bar .dot { width: 0.55rem; height: 0.55rem; border-radius: 50%; background: var(--rule); flex: none; }
.live-bar .url { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.live-bar button {
  font: inherit; color: var(--paper-dim); background: none; border: 0;
  cursor: pointer; padding: 0.2rem 0.4rem;
}
.live-bar button:hover { color: var(--paper); }
.live-frame iframe {
  display: block; width: 100%; aspect-ratio: 16 / 10;
  border: 1px solid var(--rule); background: var(--ink-2);
}
@media (max-width: 700px) { .live-frame iframe { aspect-ratio: 3 / 4; } }

/* ══════════════════════════════════════════════════════════════════════════
   THE AESTHETIC PASS

   Nothing here changes what the page says. It changes how much room each part
   of it gets, and how consistently. Most of what made the page feel unfinished
   was rhythm: sections that set their own spacing, a hero with a hole in the
   middle of it, and headings that ran straight into the next thing.
   ══════════════════════════════════════════════════════════════════════════ */

:root {
  /* One vertical scale, used everywhere. Sections were each inventing their
     own padding, which is what made the page feel assembled rather than set. */
  --sp-sec:  clamp(4.5rem, 10vh, 8rem);   /* between sections */
  --sp-head: clamp(2.5rem, 5vw, 4.5rem);  /* under a section heading */
  --sp-blk:  clamp(1.5rem, 3vw, 2.5rem);  /* between blocks inside one */
}

/* ---- the hero: close the hole in the middle ---------------------------- */
.hero { min-height: min(100svh, 54rem); align-content: center; }
.hero-body { align-self: center; }
.hero .sub { max-width: 42ch; }
.snap { margin-top: clamp(1.75rem, 4vw, 3rem); }
.hero-foot {
  margin-top: clamp(2.5rem, 6vh, 5rem);
  padding-top: 1.25rem;
  row-gap: 0.5rem;
}

/* ---- headings get room to land ----------------------------------------- */
.section-head { margin-bottom: var(--sp-head); }
.work .section-head { margin-bottom: 0; }
.work > .wrap { padding-bottom: var(--sp-head); }
.included, .price-sec, .proof { padding-block: var(--sp-sec) 0; }
.talk { margin-top: var(--sp-sec); padding-block: var(--sp-sec); }

/* the measured note was breaking into three short lines */
.proof-note { max-width: 72ch; }

/* ---- the form: give the labels air ------------------------------------- */
.talk-form { gap: var(--sp-blk); }
.f-row { gap: 0.5rem; }
.f-row input, .f-row textarea {
  padding: 0.85rem 1rem;
  transition: border-color 140ms ease, background-color 140ms ease;
}
.f-row input:hover, .f-row textarea:hover { border-color: var(--paper-mute); }
.f-row input::placeholder, .f-row textarea::placeholder { color: var(--paper-mute); }
.talk-form .btn-lg { margin-top: 0; }
@media (min-width: 901px) { .talk-inner { align-items: center; } }

/* ---- the footer ---------------------------------------------------------
   Our own rule for every client site is a real footer: hours, service area,
   insurance, the company name and a copyright line. Ours had three sentences
   and a mark, which is the tell we tell clients not to have. */
.foot { border-top: 1px solid var(--rule); padding-block: var(--sp-sec) clamp(2rem, 5vh, 3.5rem); }
.foot-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  gap: var(--sp-blk) clamp(1.5rem, 4vw, 3.5rem);
  padding-bottom: var(--sp-blk);
  margin-bottom: var(--sp-blk);
  border-bottom: 1px solid var(--rule);
}
.foot-col h3 {
  font-size: var(--t-micro); font-weight: 700; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--paper-mute); margin-bottom: 0.7rem;
}
.foot-col p, .foot-col li { font-size: var(--t-small); color: var(--paper-dim); }
.foot-col ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.4rem; }
.foot-col a { text-decoration: none; }
.foot-col a:hover { color: var(--paper); text-decoration: underline; }
.foot-note { font-size: var(--t-micro); color: var(--paper-mute); max-width: 68ch; }
.foot-mark { margin-top: var(--sp-blk); font-size: var(--t-small); }

/* ── A small radius, granted rather than assumed ───────────────────────────
   Sam: "Should we give slight round edge on some of those hard edge buttons?"

   On our own buttons, yes: a hard 0 on a big solid block reads like an
   unstyled default rather than a decision, and 3px reads intentional.

   NOT on Streetside or Level Five. Those two are square for a reason —
   industrial signage is cut square and a drawing annotation is drawn square —
   and leaving them makes the rounding everywhere else read as a choice rather
   than a habit. DESIGN.md: "Radius is not a global default. It is granted,
   rarely." */
.btn, .btn-lg, .nav-cta, .talk-tel, .live-go, .f-row input, .f-row textarea { border-radius: 3px; }
.demo[data-world="streetside"] .btn,
.demo[data-world="levelfive"]  .btn { border-radius: 0; }
.demo[data-world="cedarstone"] .btn { border-radius: 999px; }
.demo[data-world="fairweather"] .btn { border-radius: 0; }

/* ── The line snaps as each world opens ────────────────────────────────────
   Sam: "When the worlds change as we scroll is there anything that happens as
   it shifts?"

   It was a cut: the masthead inverted, the mark turned, the cube turned a
   quarter, and the world simply arrived. This is the boundary itself doing
   something, and it is the one gesture that belongs to us — you snap a chalk
   line to mark where the next thing starts, which is what the name means and
   what already happens under the hero headline.

   It fires ONCE, when the world opens, in that world's own colour. It is not
   a fade-in on scroll: nothing appears out of nothing, a line is struck. */
.demo::after {
  content: "";
  position: absolute; top: 0; left: 0; right: 0; height: 2px;
  background: var(--c);
  transform: scaleX(0); transform-origin: left center;
  transition: transform 520ms cubic-bezier(.16,.9,.28,1);
  z-index: 1;
}
.demo.opened::after { transform: scaleX(1); }

/* Reduced motion gets the finished state, drawn, not a broken animation. */
@media (prefers-reduced-motion: reduce) {
  .demo::after { transition: none; transform: scaleX(1); }
}
