/* ============================================================================
   WLP PAVING TOOLKIT · STYLES
   ----------------------------------------------------------------------------
   Direction: "daylight site plan". Paper white, blue ink, one saturated yellow
   used only on things you act on. The drawing is a flat illustration, not a
   material sample, so the page can stay calm and let the number be the event.

   Animation budget rule: transform and opacity ONLY. No animated width, height,
   top, left, margin or box-shadow. 93% of WLP conversions are mobile.
   Everything degrades to static under prefers-reduced-motion.

   NOTE: the @import must stay the first rule in the file. Anything above it,
   including :root, makes the browser drop the font request silently.
   ========================================================================== */

@import url('https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,500;12..96,600;12..96,700&family=Inter:wght@400;500;600;700&display=swap');

:root {
  color-scheme: light;

  /* ink */
  --ink: #16212B;
  --ink-2: #4E606F;
  --ink-3: #8494A1;

  /* paper */
  --paper: #F3F6F8;
  --card: #FFFFFF;
  --sunk: #F7F9FB;

  --line: #E6ECF1;
  --line-2: #D3DDE5;

  /* the one accent, reserved for what you act on */
  --accent: #FFC93D;
  --accent-deep: #E9A900;
  --accent-soft: #FFF6DE;

  --good: #2E9E6B;
  --good-soft: #EAF7F0;
  --flag: #C2410C;
  --flag-soft: #FFF3EA;
  --sky-soft: #EDF4F9;

  --r-lg: 20px;
  --r: 14px;
  --r-sm: 10px;
  --r-pill: 999px;

  /* Curves are taken, not invented. Built in easings are too weak and the
     Material curve (.4,0,.2,1) reads as a template default. */
  --ease-out: cubic-bezier(0.23, 1, 0.32, 1);
  --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);

  --display: 'Bricolage Grotesque', 'Inter', system-ui, sans-serif;
  --body: 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;

  --sh-1: 0 1px 2px rgba(18, 34, 48, .05);
  --sh-2: 0 1px 2px rgba(18, 34, 48, .05), 0 10px 28px -16px rgba(18, 34, 48, .28);
  --sh-3: 0 2px 4px rgba(18, 34, 48, .06), 0 26px 60px -28px rgba(18, 34, 48, .42);
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  font-family: var(--body);
  color: var(--ink);
  background: var(--paper);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}

/* Embedded in WordPress the widget is transparent so the host page shows
   through and the rounded corners are not painted over from behind. */
html.embed, body.embed { background: transparent; }
body.embed { padding: 2px 4px 18px; }

img, svg { display: block; max-width: 100%; }
button { font-family: inherit; }

:where(a, button, input, [tabindex]):focus-visible {
  outline: 2px solid var(--accent-deep);
  outline-offset: 2px;
  border-radius: 6px;
}

.wrap { max-width: 1120px; margin: 0 auto; padding: 0 22px; }

/* The HERO block lived here: eyebrow, display headline, lede and the three
   trust badges. It was deleted, not hidden, when the headline moved to
   WordPress. Leaving ~55 lines of rules that match nothing is how a stylesheet
   turns into a place people are afraid to edit.

   The page now opens on the map, so the top of the document needs the breathing
   room the hero used to provide. That is handed to .tabs-shell below, and to
   .panel further down for the single-trade routes where the tab strip is gone
   and there is nothing left to hold the top margin open. */

/* ==========================================================================
   TABS
   ========================================================================== */
.tabs-shell { position: relative; z-index: 5; max-width: 1120px; margin: 0 auto; padding: 22px 22px 20px; }
body.embed .tabs-shell { padding: 0 0 14px; }
/* One calculator per page: the switcher is meaningless once a URL commits to a
   single trade, and its shell has to collapse too or it leaves a strip of dead
   space up top. This fires on /asphalt as well as /e/asphalt, so it does not
   matter which of the two URLs gets pasted into WordPress. */
body.single .tabs-shell { display: none; }

.tabs {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 4px;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 16px; padding: 5px;
  box-shadow: var(--sh-2);
  position: relative;
  max-width: 620px; margin: 0 auto;
}
.tab {
  position: relative; z-index: 2;
  border: 0; background: transparent; cursor: pointer;
  padding: 14px 10px; border-radius: 12px;
  font-size: 14px; font-weight: 600; color: var(--ink-3);
  display: flex; align-items: center; justify-content: center; gap: 9px;
  transition: color .2s var(--ease-out), transform .18s var(--ease-out);
}
.tab:active { transform: scale(.98); }
.tab.on { color: var(--ink); }
.tab svg { width: 17px; height: 17px; flex: none; }
@media (hover: hover) and (pointer: fine) {
  .tab:hover { color: var(--ink-2); }
}

.tab-ink {
  position: absolute; top: 5px; left: 5px; z-index: 1;
  height: calc(100% - 10px);
  border-radius: 12px;
  background: var(--accent);
  transition: transform .42s var(--ease-out), width .42s var(--ease-out);
  will-change: transform;
}

/* ==========================================================================
   LAYOUT
   ========================================================================== */
.panel { padding: 0 0 80px; }
/* No tab strip on a single-trade URL, so the map would otherwise start hard
   against the top edge of the document. */
body.single .panel { padding-top: 22px; }
body.embed .panel { padding: 0; }
body.embed .panel > .wrap { padding: 0; }

.grid {
  display: grid; grid-template-columns: minmax(0, .88fr) minmax(0, 1.12fr);
  gap: 18px; align-items: start;
}
@media (max-width: 940px) { .grid { grid-template-columns: 1fr; gap: 14px; } }

.card {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  padding: 24px;
  box-shadow: var(--sh-2);
  position: relative;
}
.card + .card { margin-top: 14px; }
.card h2 {
  font-size: 11px; font-weight: 600; letter-spacing: .14em; text-transform: uppercase;
  color: var(--ink-3); margin: 0 0 18px;
  display: flex; align-items: center; gap: 9px;
}
.card h2::before {
  content: ''; width: 13px; height: 2px; background: var(--accent); border-radius: 2px; flex: none;
}
@media (max-width: 620px) { .card { padding: 18px; border-radius: 16px; } }

/* ==========================================================================
   MEASURE · satellite property tool
   ========================================================================== */
.measure { margin-bottom: 18px; }
.measure-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 14px; flex-wrap: wrap; margin-bottom: 14px;
}
/* This one card gets a real title instead of the small all caps label the
   other cards use, so the map reads as a tool and not as a form field. */
.measure-head .mt {
  display: block; text-transform: none; color: var(--ink);
  font-family: var(--display);
  font-size: 19px; font-weight: 600; letter-spacing: -.02em; margin: 0 0 5px;
}
.measure-head .mt::before { display: none; }
.measure-head .ms { font-size: 13px; color: var(--ink-2); margin: 0; line-height: 1.5; max-width: 52ch; }

.search { display: grid; grid-template-columns: 1fr auto; gap: 8px; margin-bottom: 12px; }
@media (max-width: 480px) { .search { grid-template-columns: 1fr; } }

.mapbox {
  position: relative;
  border: 1px solid var(--line-2);
  border-radius: var(--r);
  overflow: hidden;
  background: var(--sunk);
  height: 420px;
}
@media (max-width: 620px) { .mapbox { height: 320px; } }
#map { position: absolute; inset: 0; }

/* Nothing is fetched until the visitor asks for it. Tiles are the heaviest
   thing on the page and most visitors never open the map. */
.map-invite {
  position: absolute; inset: 0; z-index: 4;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 12px; text-align: center; padding: 24px; cursor: pointer;
  background: var(--sunk); border: 0; width: 100%; font-family: inherit;
}
.map-invite .mi-icon {
  width: 46px; height: 46px; border-radius: 50%; display: grid; place-items: center;
  background: var(--accent); color: var(--ink); flex: none;
}
.map-invite .mi-icon svg { width: 22px; height: 22px; }
.map-invite b { font-size: 15px; font-weight: 600; color: var(--ink); }
.map-invite span { font-size: 12.5px; color: var(--ink-2); max-width: 34ch; line-height: 1.5; }

.map-tools {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin-top: 12px;
}
.map-readout {
  margin-right: auto;
  display: inline-flex; align-items: baseline; gap: 8px;
  font-size: 13px; color: var(--ink-2);
}
.map-readout b {
  font-family: var(--display); font-size: 20px; font-weight: 700; color: var(--ink);
  letter-spacing: -.02em; font-variant-numeric: tabular-nums;
}
.map-hint { font-size: 12px; color: var(--ink-3); margin-top: 10px; line-height: 1.5; }

/* ==========================================================================
   INPUTS
   ========================================================================== */
.field { margin-bottom: 20px; }
.field:last-child { margin-bottom: 0; }
.field > label {
  display: block; font-size: 13.5px; font-weight: 600; color: var(--ink); margin-bottom: 9px;
  letter-spacing: -.01em;
}
.field .hint {
  font-size: 12px; font-weight: 400; color: var(--ink-3); margin-top: 8px; line-height: 1.5;
  text-wrap: pretty;
}

.inp {
  width: 100%; padding: 13px 14px;
  font-size: 16px; font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--ink); background: var(--card);
  border: 1.5px solid var(--line-2); border-radius: var(--r-sm); outline: none;
  transition: border-color .18s var(--ease-out), box-shadow .18s var(--ease-out);
  -moz-appearance: textfield;
}
.inp::-webkit-outer-spin-button, .inp::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.inp:focus {
  border-color: var(--accent-deep);
  box-shadow: 0 0 0 4px var(--accent-soft);
}
.inp:focus-visible { outline: none; }
.inp::placeholder { color: var(--ink-3); font-weight: 400; }
@media (hover: hover) and (pointer: fine) {
  .inp:hover { border-color: #BECBD6; }
}

/* The two questions that matter, sized for the visitor we actually get. A large
   share of this audience is over seventy, reading on a phone held at arm's
   length. So the label is a real <label>, never a placeholder that disappears
   the moment it is needed, and the number itself is set at 26px. */
.dim > label {
  display: block; font-size: 15px; font-weight: 600; color: var(--ink);
  margin-bottom: 8px; letter-spacing: -.01em;
}
.inp.big {
  padding: 16px 16px; font-size: 26px; font-weight: 700; letter-spacing: -.02em;
  border-radius: var(--r); min-height: 62px;
}
.inp.big::placeholder { font-weight: 500; font-size: 24px; }

.dims { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
@media (max-width: 480px) { .dims { grid-template-columns: 1fr; gap: 16px; } }

/* The other way to answer, offered as a sentence rather than a control. A
   segmented switch asks people to understand a toggle before they can type a
   number; this asks nothing until they want it. */
.linkish {
  display: inline-flex; align-items: center; min-height: 44px;
  margin-top: 2px; padding: 0; border: 0; background: transparent; cursor: pointer;
  font-family: inherit; font-size: 14.5px; font-weight: 600; color: var(--ink-2);
  text-decoration: underline; text-underline-offset: 3px;
  text-decoration-color: var(--line-2);
  transition: color .18s var(--ease-out), text-decoration-color .18s var(--ease-out);
}
.linkish:focus-visible {
  outline: 2px solid var(--accent-deep); outline-offset: 3px; border-radius: 4px;
}
@media (hover: hover) and (pointer: fine) {
  .linkish:hover { color: var(--ink); text-decoration-color: var(--accent-deep); }
}

/* ==========================================================================
   THE OPTIONAL PANEL
   Everything that is not length and width is already answered with the most
   common real world choice, so it is folded away and summarised in plain
   English. Native <details> rather than a hand rolled accordion: it is
   keyboard operable, screen readers announce collapsed and expanded, and it
   still opens if the JavaScript never arrives.
   ========================================================================== */
.more {
  margin-top: 22px; border: 1.5px solid var(--line); border-radius: var(--r);
  background: var(--sunk); overflow: hidden;
}
.more summary {
  display: grid; grid-template-columns: 1fr 22px; align-items: center;
  gap: 4px 10px; padding: 15px 16px; cursor: pointer; list-style: none;
  min-height: 56px;
}
.more summary::-webkit-details-marker { display: none; }
.more summary:focus-visible { outline: 2px solid var(--accent-deep); outline-offset: -2px; }
.mh {
  grid-column: 1; font-size: 15px; font-weight: 600; color: var(--ink);
  letter-spacing: -.01em;
}
.msum {
  grid-column: 1; font-size: 13px; font-weight: 400; color: var(--ink-3);
  line-height: 1.45; text-wrap: pretty;
}
.mchev {
  grid-column: 2; grid-row: 1 / span 2; display: flex; color: var(--ink-3);
  transition: transform .22s var(--ease-out);
}
.mchev svg { width: 20px; height: 20px; }
.more[open] .mchev { transform: rotate(180deg); }
@media (hover: hover) and (pointer: fine) {
  .more summary:hover .mh { color: var(--accent-deep); }
}
.more-body {
  padding: 4px 16px 18px; border-top: 1px solid var(--line);
  background: var(--card);
}
.more-body .field:first-child { margin-top: 18px; }
/* A short settle on open. Height cannot be animated on <details> without
   JavaScript, so the body arrives with a small rise instead of a jump. */
.more[open] .more-body { animation: panel .24s var(--ease-out) both; }
@keyframes panel {
  from { opacity: 0; transform: translateY(-6px); }
  to   { opacity: 1; transform: none; }
}

/* option chips */
.opts { display: grid; gap: 8px; }
/* Column counts are a wish, not an instruction. The panel sits in a column
   whose width depends on the grid, the phone and whether WordPress gave the
   iframe the full page, so the chips wrap on their own minimum instead of on
   a viewport breakpoint that knows nothing about the container. */
.opts.cols-2 { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
.opts.cols-3 { grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); }
.opts.cols-4 { grid-template-columns: repeat(auto-fit, minmax(128px, 1fr)); }

.opt {
  position: relative; overflow: hidden;
  cursor: pointer; text-align: left; width: 100%; font-family: inherit;
  border: 1.5px solid var(--line-2);
  background: var(--card); border-radius: 12px; padding: 12px 13px;
  transition: border-color .2s var(--ease-out), background .2s var(--ease-out), transform .18s var(--ease-out);
  user-select: none;
}
.opt:active { transform: scale(.99); }
.opt.on { background: var(--accent-soft); border-color: var(--accent); }
@media (hover: hover) and (pointer: fine) {
  .opt:hover { border-color: #BECBD6; }
  .opt.on:hover { border-color: var(--accent-deep); }
}
.opt .t { font-size: 13.5px; font-weight: 600; color: var(--ink); display: block; letter-spacing: -.01em; }
.opt .s { font-size: 11.5px; font-weight: 400; color: var(--ink-3); display: block; margin-top: 4px; line-height: 1.4; }

.toggle {
  display: flex; align-items: center; gap: 12px; cursor: pointer; user-select: none;
  width: 100%; text-align: left; background: transparent; border: 0; padding: 0;
  font-family: inherit; color: inherit;
}
.toggle .track {
  width: 44px; height: 25px; border-radius: 99px; background: var(--line-2);
  border: 1px solid var(--line-2);
  position: relative; flex: none; transition: background .22s var(--ease-out), border-color .22s var(--ease-out);
}
.toggle .track::after {
  content: ''; position: absolute; top: 2px; left: 2px; width: 19px; height: 19px;
  border-radius: 50%; background: var(--card); box-shadow: var(--sh-1);
  transition: transform .26s var(--ease-out);
}
.toggle.on .track { background: var(--accent); border-color: var(--accent-deep); }
.toggle.on .track::after { transform: translateX(19px); }
.toggle .tl { font-size: 13.5px; font-weight: 500; color: var(--ink); }

/* ==========================================================================
   STAGE · the illustration
   One drawing, three stories. Flat fills, no texture, no machinery.
   ========================================================================== */
.stage { margin-bottom: 20px; }

.stage-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 10px; margin-bottom: 12px;
}
.stage-label {
  font-size: 10.5px; font-weight: 600; letter-spacing: .14em; text-transform: uppercase;
  color: var(--ink-3);
}
.stage-dims {
  font-size: 12px; font-weight: 600; color: var(--ink-2);
  font-variant-numeric: tabular-nums;
  background: var(--sunk); border: 1px solid var(--line);
  padding: 4px 10px; border-radius: var(--r-pill);
}
.stage-dims:empty { display: none; }

.scene {
  border-radius: var(--r);
  overflow: hidden;
  background: var(--sky-soft);
}
.svg-scene { display: block; width: 100%; height: auto; }

/* ---------------------------------------------------------------------------
   Scene timeline. One sentence, in order, and nothing overlapping that does not
   belong together:

     0ms     the place you already own arrives      house, trees, sun, cloud
     380ms   the crew lays the surface              the sweep, left to right
     940ms   the markings go down                   joints or stalls
     1000ms  someone parks on it                    the car

   The previous version ran the first two phases on top of each other, so the
   house faded up while the asphalt was still being poured and the whole thing
   read as one undifferentiated shimmer. Told in order it reads as work being
   done, which is the only reason to animate this at all.
--------------------------------------------------------------------------- */

/* Phase 1. The property as it stands today. Trees carry their own stagger from
   the markup so the pair does not pop as one object. */
.scene .bld, .scene .tree { opacity: 0; transform: translateY(8px); }
.scene.play .bld, .scene.play .tree {
  transition: opacity 400ms var(--ease-out) var(--d, 0ms),
              transform 400ms var(--ease-out) var(--d, 0ms);
  opacity: 1; transform: translateY(0);
}
.scene .sun {
  opacity: 0; transform: scale(.85);
  transform-box: fill-box; transform-origin: center;
}
.scene.play .sun {
  transition: opacity 380ms var(--ease-out) 120ms, transform 520ms var(--ease-out) 120ms;
  opacity: 1; transform: scale(1);
}
.scene .cloud { opacity: 0; transform: translateX(-16px); }
.scene.play .cloud {
  transition: opacity 420ms var(--ease-out) 180ms, transform 640ms var(--ease-out) 180ms;
  opacity: .9; transform: translateX(0);
}

/* Phase 2. Laid left to right at constant speed, because that is how a crew
   lays it. Linear on purpose: an eased sweep would dart and then crawl, which
   is a machine nobody has ever operated. */
.scene .sweep { transform: scaleX(0); transform-box: fill-box; transform-origin: left center; }
.scene.play .sweep { transition: transform 560ms linear 380ms; transform: scaleX(1); }

/* Phase 2b. The same 560ms, driven. THE TWO NUMBERS BELOW ARE ONE NUMBER: the
   sweep crosses 420 user units in 560ms, so the rig has to cross the same 420
   in the same 560ms or the machine and the edge it is laying come apart. That
   is why 80% of 700ms is spelled out rather than rounded, and why this is
   linear too. Change the sweep duration and this has to move with it.

   The last 20% is the rig driving off frame at unchanged speed, which is the
   whole reason for the extra 140ms: a machine that stops dead the instant the
   job is done is a wipe again, just with a picture of a roller on the front. */
.scene .rig { opacity: 0; }
.scene.play .rig {
  animation: rigPass 700ms linear 380ms forwards;
}
@keyframes rigPass {
  0%   { opacity: 1; transform: translateX(0); }
  80%  { opacity: 1; transform: translateX(420px); }
  100% { opacity: 1; transform: translateX(525px); }
}

/* Phase 3. Paint goes on cured pavement, so the markings wait for the sweep to
   clear. Their stagger is written into the markup. */
.scene .joint, .scene .stall { opacity: 0; }
.scene.play .joint, .scene.play .stall {
  transition: opacity 240ms var(--ease-out) var(--d);
  opacity: 1;
}

/* Phase 4. The car is the payoff and the only thing allowed to arrive late. */
.scene .car { opacity: 0; transform: translateX(-34px); }
.scene.play .car {
  transition: transform 640ms var(--ease-out) 1000ms, opacity 320ms var(--ease-out) 1000ms;
  opacity: 1; transform: translateX(0);
}

/* ---------------------------------------------------------------------------
   Phase 5, which is not a phase but the rest of the visit.

   Everything above finishes at about 1.6 seconds. The visitor is here for
   minutes. Every previous version spent that entire time as a still image,
   which is the difference between a picture that faded in and a place that
   exists, and it is most of what "the animation feels cheap" was actually
   pointing at.

   Rules these loops follow, all four load bearing:
     - transform and opacity only, so they never leave the compositor and never
       cost the main thread while someone is typing dimensions;
     - each one on its own inner group, because the entrance transitions own
       the outer groups and a second rule on the same element wins outright
       rather than blending;
     - periods that do not divide into each other, so the scene never resolves
       into one synchronised pulse, which is the tell that it is a loop;
     - amplitudes small enough that you notice the scene is alive without ever
       catching it moving. A sway you can watch is a screensaver.
--------------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
  .scene .cloudb { animation: cloudDrift 31s ease-in-out infinite; }
  .scene .treeb {
    transform-box: fill-box; transform-origin: 50% 100%;
    animation: treeSway var(--s, 6s) ease-in-out infinite;
  }
  .scene .sunb {
    transform-box: fill-box; transform-origin: center;
    animation: sunBreathe 7.3s ease-in-out infinite;
  }
}
@keyframes cloudDrift {
  0%, 100% { transform: translateX(0); }
  50%      { transform: translateX(30px); }
}
@keyframes treeSway {
  0%, 100% { transform: rotate(-1.15deg); }
  50%      { transform: rotate(1.15deg); }
}
@keyframes sunBreathe {
  0%, 100% { transform: scale(1); opacity: 1; }
  50%      { transform: scale(1.05); opacity: .88; }
}

/* The drawing replaces the dashed placeholder, and it is the largest thing on
   the page, so the swap was the loudest cut in the interface. Only on the
   first one: after that the visitor is typing and a re-entrance would read as
   lag, not as craft. */
.scene.first { animation: sceneIn .26s var(--ease-out) both; }
@keyframes sceneIn {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}
.stage-empty.leaving { animation: emptyOut .14s var(--ease-out) both; }
@keyframes emptyOut {
  from { opacity: 1; transform: scale(1); }
  to   { opacity: 0; transform: scale(.98); }
}

/* empty state, before there is an area to draw */
.stage-empty {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 12px; text-align: center; padding: 46px 24px;
  background: var(--sunk); border: 1px dashed var(--line-2); border-radius: var(--r);
}
.stage-empty .ghost {
  width: 96px; height: 52px; border-radius: 5px;
  border: 1.5px dashed var(--line-2); position: relative;
}
.stage-empty .ghost::after {
  content: ''; position: absolute; left: 50%; top: 50%;
  width: 32px; height: 2px; background: var(--accent); border-radius: 2px;
  transform: translate(-50%, -50%);
}
.stage-empty p {
  font-size: 12.5px; line-height: 1.55; font-weight: 400;
  color: var(--ink-3); max-width: 280px; margin: 0; text-wrap: pretty;
}

/* ---- build up strip: what actually goes down, bottom to top ------------- */
.buildup { margin-top: 16px; border-top: 1px solid var(--line); padding-top: 12px; }
.buildup:empty { display: none; }
.bu {
  position: relative; display: flex; align-items: center; gap: 12px; padding: 7px 0;
}
.bu i {
  flex: none; width: 74px; border-radius: 4px; height: var(--h);
  background: var(--c);
  transform: scaleY(0); transform-origin: bottom;
  transition: transform 380ms var(--ease-out) var(--d);
}
.stage.built .bu i { transform: scaleY(1); }
.bu .bn { font-size: 13px; font-weight: 500; color: var(--ink); letter-spacing: -.01em; }
.bu .bq {
  margin-left: auto; font-size: 12.5px; font-weight: 600; color: var(--ink-2);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
/* The bars grow in build order, but every label was already sitting there
   before its bar existed, which gave away the sequence. Each name arrives with
   its own layer, reusing the delay the bar already carries. */
.bu .bn, .bu .bq { opacity: 0; }
.stage.built .bu .bn, .stage.built .bu .bq {
  transition: opacity 300ms var(--ease-out) calc(var(--d) + 120ms);
  opacity: 1;
}

/* ==========================================================================
   RESULT
   ========================================================================== */
.result-head {
  display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
  flex-wrap: wrap; margin-bottom: 4px;
}
.result-kicker {
  font-size: 10.5px; font-weight: 600; letter-spacing: .14em; text-transform: uppercase;
  color: var(--ink-3);
}
.result-scope {
  font-size: 12px; font-weight: 500; color: var(--ink-2);
  background: var(--sunk); border: 1px solid var(--line);
  padding: 4px 10px; border-radius: var(--r-pill);
}

.bigprice {
  font-family: var(--display);
  font-size: clamp(34px, 5.6vw, 54px); font-weight: 700; letter-spacing: -.04em;
  line-height: 1.04; font-variant-numeric: tabular-nums; margin: 12px 0 6px;
  display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap;
  color: var(--ink);
}
.bigprice .to {
  font-family: var(--body);
  font-size: .28em; font-weight: 500; color: var(--ink-3); letter-spacing: 0;
}
/* The price is the answer to the only question that was asked, so it is the
   one thing on this card that gets an entrance. It runs on the step from no
   estimate to an estimate and never again, because the card is rebuilt on
   every keystroke and a per keystroke entrance is a stutter, not a flourish. */
.bigprice.arrive, .result-scope.arrive {
  animation: priceIn .32s var(--ease-out) both;
}
@keyframes priceIn {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}

/* The fine print. Kept, because it is honest, and folded, because nobody
   reading a price needs four sentences about subgrade first. */
.more.fine { margin-top: 16px; }
.more.fine summary { grid-template-columns: 1fr 22px; padding: 13px 16px; min-height: 48px; }
.more.fine .more-body { padding-top: 14px; }
.fp {
  margin: 0 0 10px; font-size: 12.5px; line-height: 1.6;
  color: var(--ink-2); text-wrap: pretty;
}
.fp:last-child { margin-bottom: 0; }
.fp b { color: var(--ink); font-weight: 600; }
.fp span { font-variant-numeric: tabular-nums; font-weight: 600; color: var(--ink); }

.empty { color: var(--ink-2); font-weight: 500; font-size: 16px; padding: 14px 0 4px; }

.qty-list { display: grid; gap: 9px; margin-top: 22px; }
.qty {
  display: flex; align-items: center; gap: 14px;
  background: var(--sunk); border: 1px solid var(--line);
  border-radius: 12px; padding: 12px 14px;
  opacity: 0; transform: translateY(8px);
  animation: rise .4s var(--ease-out) forwards;
}
.qty:nth-child(1) { animation-delay: .04s } .qty:nth-child(2) { animation-delay: .1s }
.qty:nth-child(3) { animation-delay: .16s } .qty:nth-child(4) { animation-delay: .22s }
@keyframes rise { to { opacity: 1; transform: translateY(0) } }
.qty .qv {
  font-family: var(--display);
  font-size: 19px; font-weight: 700; letter-spacing: -.03em; color: var(--ink);
  font-variant-numeric: tabular-nums; min-width: 82px;
}
.qty .qv small { font-size: 10.5px; font-weight: 500; color: var(--ink-3); margin-left: 4px; }
.qty .ql { font-size: 13px; font-weight: 600; color: var(--ink); }
.qty .qn { font-size: 11.5px; font-weight: 400; color: var(--ink-3); margin-top: 3px; line-height: 1.4; }

.badge {
  display: inline-block; font-size: 9px; font-weight: 700; letter-spacing: .09em;
  text-transform: uppercase; padding: 3px 7px; border-radius: 5px; vertical-align: middle;
}
.badge.verified { background: var(--good-soft); color: var(--good); border: 1px solid rgba(46,158,107,.25); }
.badge.est { background: var(--accent-soft); color: #8A6400; border: 1px solid rgba(233,169,0,.3); }

.notice {
  background: var(--accent-soft); border: 1px solid rgba(233,169,0,.28); border-radius: 12px;
  padding: 13px 15px; font-size: 12.5px; line-height: 1.55; color: var(--ink-2); margin-top: 16px;
  text-wrap: pretty;
}
.notice b { color: #8A6400; }

/* Sits under the price when the job lands below what a crew day costs. Cool blue
   rather than the amber notice, because this is not a warning, it is the reason
   the number above it is what it is. */
.floornote {
  background: var(--sky-soft); border: 1px solid #CFE0EC; border-radius: 12px;
  padding: 13px 15px; font-size: 12.5px; line-height: 1.55; color: var(--ink-2);
  margin: 14px 0 0; text-wrap: pretty;
}
.floornote b { color: var(--ink); }

/* ==========================================================================
   CTA / LEAD GATE
   ========================================================================== */
.cta {
  background: var(--ink);
  color: #fff; border-radius: var(--r-lg);
  padding: 26px; margin-top: 14px;
  box-shadow: var(--sh-2);
}
.cta h3 {
  font-family: var(--display);
  font-size: 22px; font-weight: 700; letter-spacing: -.03em; margin: 0 0 8px; text-wrap: balance;
}
.cta p { font-size: 13.5px; line-height: 1.55; color: rgba(255,255,255,.72); margin: 0 0 18px; text-wrap: pretty; }
.cta ul { margin: 0 0 18px; padding: 0; list-style: none; display: grid; gap: 9px; }
.cta li { font-size: 13px; color: rgba(255,255,255,.78); display: flex; gap: 10px; align-items: flex-start; line-height: 1.45; }
.cta li svg { width: 15px; height: 15px; flex: none; margin-top: 2px; color: var(--accent); }

/* A row is a plain block, not a grid, so the label sits on top of its own input
   with only the label's own margin between them. It used to be a grid, which was
   fine while every field was a lone placeholder box and wrong the moment labels
   arrived: the grid gap would have stacked on top of the label margin and pushed
   each caption a third of a field away from the box it names. Only .two is a
   grid, and only because it genuinely has two columns to lay out. */
.form-row { margin-bottom: 11px; }
.form-row.two { display: grid; gap: 11px; }
@media (min-width: 560px) { .form-row.two { grid-template-columns: 1fr 1fr; gap: 10px; } }

/* Scoped to .cta because this is the dark card. The same element on paper
   elsewhere in the document keeps the light styling. */
.cta label {
  display: block;
  font-size: 12px; font-weight: 600; letter-spacing: -.005em;
  color: rgba(255,255,255,.72);
  margin-bottom: 6px;
}
/* "optional" reads as part of the label, not as a second instruction, so it is
   quieter than the label and never louder than the field. Saying which fields
   are optional beats marking the required ones with an asterisk nobody has ever
   had explained to them.
   Named opt-tag, not opt: .opt is already the big scope option card up at line
   373, and the first version of this rule collided with it, which painted a
   white card-sized block over the field. A class name that reads as a generic
   English word is a collision waiting to happen in a single stylesheet. */
.opt-tag {
  margin-left: 6px;
  font-size: 10.5px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase;
  color: rgba(255,255,255,.42);
  white-space: nowrap;
}
.cta .inp {
  background: rgba(255,255,255,.07); color: #fff;
  border-color: rgba(255,255,255,.16);
  font-weight: 500;
}
.cta .inp:focus { border-color: var(--accent); box-shadow: 0 0 0 4px rgba(255,201,61,.16); }
.cta .inp.err { border-color: #F87171; background: rgba(248,113,113,.12); }
/* The last row's margin is not enough separation between the final field and a
   full width yellow button. */
.cta #leadBtn { margin-top: 7px; }

.btn {
  width: 100%; border: 0; cursor: pointer; border-radius: 12px;
  padding: 15px 20px; font-size: 15px; font-weight: 600; letter-spacing: -.015em;
  display: inline-flex; align-items: center; justify-content: center; gap: 9px;
  text-decoration: none;
  transition: transform .16s var(--ease-out), filter .18s var(--ease-out);
}
.btn:active { transform: scale(.985); }
.btn-y { background: var(--accent); color: var(--ink); }
.btn-y:disabled { opacity: .55; cursor: not-allowed; }
.btn-ghost {
  background: transparent; color: rgba(255,255,255,.72);
  border: 1.5px solid rgba(255,255,255,.2); margin-top: 9px;
}
@media (hover: hover) and (pointer: fine) {
  .btn-y:hover { filter: brightness(1.05); }
  .btn-ghost:hover { background: rgba(255,255,255,.07); color: #fff; }
}

/* the two map buttons live on paper, not on ink */
.btn-sm {
  width: auto; border: 1.5px solid var(--line-2); background: var(--card); color: var(--ink);
  cursor: pointer; border-radius: var(--r-sm);
  padding: 11px 16px; font-size: 13px; font-weight: 600; font-family: inherit;
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  transition: border-color .18s var(--ease-out), background .18s var(--ease-out), transform .16s var(--ease-out);
}
.btn-sm:active { transform: scale(.98); }
.btn-sm:disabled { opacity: .45; cursor: not-allowed; }
.btn-sm.primary { background: var(--accent); border-color: var(--accent); }
@media (hover: hover) and (pointer: fine) {
  .btn-sm:hover:not(:disabled) { border-color: #BECBD6; }
  .btn-sm.primary:hover:not(:disabled) { border-color: var(--accent-deep); filter: brightness(1.04); }
}

.spinner {
  width: 16px; height: 16px; border-radius: 50%; flex: none;
  border: 2px solid rgba(22,33,43,.25); border-top-color: var(--ink);
  animation: spin .7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg) } }

.fineprint { font-size: 11px; line-height: 1.5; color: rgba(255,255,255,.5); margin-top: 13px; text-wrap: pretty; }
.fineprint a { color: rgba(255,255,255,.7); }

.cta-done { text-align: center; padding: 10px 0 4px; }
.cta-done .check {
  width: 54px; height: 54px; border-radius: 50%;
  background: var(--accent);
  display: grid; place-items: center; margin: 0 auto 16px; color: var(--ink);
  animation: pop .46s var(--ease-out) 1;
}
@keyframes pop { 0% { transform: scale(.85); opacity: 0 } 100% { transform: scale(1); opacity: 1 } }
.cta-done h3 { margin-bottom: 8px; }

/* ==========================================================================
   BUDGET SHEET (print artifact)
   ========================================================================== */
.sheet { display: none; }
@media print {
  body > *:not(.sheet) { display: none !important; }
  .sheet { display: block !important; }
}

/* ==========================================================================
   FOOTER
   ========================================================================== */
.foot { border-top: 1px solid var(--line); padding: 30px 0 48px; }
body.embed .foot { display: none; }
.foot p { font-size: 11.5px; line-height: 1.65; color: var(--ink-3); margin: 0 0 10px; max-width: 82ch; text-wrap: pretty; }
.foot b { color: var(--ink-2); }
.foot a { color: var(--ink-2); }
/* Two paragraphs of method notes are worth publishing and not worth making
   everyone scroll past. */
.foot-more { max-width: 82ch; margin: 0 0 16px; background: transparent; }
.foot-more .more-body { background: transparent; }

/* ==========================================================================
   REDUCED MOTION
   Fewer and gentler, not zero. The drawing still ends up drawn.
   ========================================================================== */
/* The lead gate lands a beat after the price so it reads as the consequence of
   the estimate rather than as something that jumped the visitor. */
.cta.arrive { animation: gateIn .38s var(--ease-out) .22s both; }
@keyframes gateIn {
  from { opacity: 0; transform: translateY(10px) scale(.985); }
  to   { opacity: 1; transform: none; }
}

/* Switching material swaps both columns at once. They rise together to meet
   the ink bar that is still sliding. */
.swap { animation: swapIn .22s var(--ease-out) both; }
@keyframes swapIn {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    transition-delay: 0ms !important;
    animation-delay: 0ms !important;
  }
  .scene .sweep { transform: scaleX(1) !important; }
  .scene .car, .scene .bld, .scene .tree, .scene .joint, .scene .stall {
    opacity: 1 !important; transform: none !important;
  }
  .scene .sun { opacity: 1 !important; transform: none !important; }
  .scene .cloud { opacity: .9 !important; transform: none !important; }
  /* The rig only ever means anything in motion. With the sweep forced open the
     surface is simply already there, and a roller parked on finished pavement
     is a prop with nothing to do, so it does not get drawn at all. */
  .scene .rig { display: none !important; }
  .bu i { transform: scaleY(1) !important; }
  .bu .bn, .bu .bq { opacity: 1 !important; }
  .qty { opacity: 1 !important; transform: none !important; }
}
