/* Self-hosted Roboto — only the weights actually used. In June, self-hosting these plus a
   preload on the H1 weight took /google-business-appeal-denied/ from LCP 4.3s to 2.1s. */
@font-face { font-family: "Roboto"; font-style: normal; font-weight: 400; font-display: swap; src: url(/assets/fonts/roboto-400.woff2) format("woff2"); }
@font-face { font-family: "Roboto"; font-style: normal; font-weight: 700; font-display: swap; src: url(/assets/fonts/roboto-700.woff2) format("woff2"); }
@font-face { font-family: "Roboto"; font-style: normal; font-weight: 800; font-display: swap; src: url(/assets/fonts/roboto-800.woff2) format("woff2"); }
@font-face { font-family: "Roboto Slab"; font-style: normal; font-weight: 400; font-display: swap; src: url(/assets/fonts/robotoslab-400.woff2) format("woff2"); }

/* appsolutelyseo.com — single stylesheet.
   NEVER async-load this file. The media="print" swap cost the MHH homepage 17 points at CLS 0.342.
   Budget: keep under ~24 KB. Mobile-first. Every interactive element needs a visible focus state. */

:root {
  --ink:        #17608A;   /* deep brand blue — the ground is blue now, not black */
  --ink-deep:   #114A6B;
  /* The hero field is the exact logo blue, sampled from images/logo.png. Flat, not a ramp,
     so it is that colour everywhere. Copy on it is dark: white would be 3.01:1 and fail AA. */
  --hero-from:  #379ED7;
  --hero-to:    #379ED7;
  --hero-ink:   #0C1B24;   /* 5.94:1 on the logo blue */
  --hero-ink-2: #0F2833;   /* 5.08:1 — secondary copy */
  --ink-2:      #1B6E9C;
  --ink-3:      #2E86B8;
  --paper:      #FFFFFF;
  --body:       #EFF6FB;
  --text:       #14181D;
  --muted:      #5A6570;
  --line:       #D9E3EC;
  --accent:     #379ED7;
  --accent-hi:  #5FB4E2;
  --accent-ink: #FFFFFF;
  --brand-deep: #1F719E;   /* buttons carrying white text — 5.38:1 */
  --brand-wash: #D7E7F4;
  --call:       #3BC64F;   /* the phone green, as TEXT on dark — 8.8:1 */
  --call-deep:  #067A2A;   /* the phone green as a BUTTON with white text — 5.49:1 */
  --ok:         #067A2A;
  --ok-wash:    #E6F4EA;
  --danger:     #B3261E;
  --danger-wash:#FCEBEA;

  --f-head: "Roboto Slab", Georgia, serif;
  --f-body: "Roboto", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;

  --wrap: 1120px;
  --gap: clamp(1rem, 3vw, 2rem);
  --radius: 12px;
}

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

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

body {
  margin: 0;
  background: var(--paper);
  color: var(--text);
  font-family: var(--f-body);
  font-size: 17px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

img { max-width: 100%; height: auto; display: block; }

a { color: var(--brand-deep); text-underline-offset: 2px; }
a:focus-visible,
button:focus-visible,
input:focus-visible,
textarea:focus-visible,
summary:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 2px;
  border-radius: 3px;
}

h1, h2, h3, h4 { font-family: var(--f-head); font-weight: 400; line-height: 1.15; margin: 0; text-wrap: balance; }
p { margin: 0 0 1em; }

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

.skip {
  position: absolute; left: -9999px; top: 0;
  background: var(--accent); color: var(--accent-ink);
  padding: .75rem 1rem; font-weight: 700; z-index: 100;
}
.skip:focus { left: 0; }

/* ---------- header ---------- */

.site-header {
  background: #fff;
  color: var(--text);
  border-bottom: 1px solid var(--line);
}
.site-header .wrap {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; min-height: 68px;
}
.brand { display: flex; align-items: center; gap: .6rem; color: var(--text); text-decoration: none; font-weight: 800; font-size: 1.05rem; }
.brand img { width: 150px; height: 34px; object-fit: contain; }

.site-nav { display: none; }
.site-nav ul { display: flex; gap: 1.4rem; list-style: none; margin: 0; padding: 0; }
.site-nav a { color: var(--text); text-decoration: none; font-size: .93rem; font-weight: 500; }
.site-nav a:hover { color: var(--brand-deep); text-decoration: underline; }

/* Was an outline button — transparent, hairline green border. On a site whose conversion IS the
   phone call, the number was styled as the quietest thing in the header. Solid now, and the
   largest type in the bar. Measured: white on --call-deep is 5.49:1, white on the hover shade
   7.18:1 — both clear AA for normal text at any size. */
.header-call {
  position: relative;                       /* for the ring halo below */
  display: inline-flex; align-items: center; gap: .5rem;
  background: var(--call-deep); color: #fff;
  border: 1px solid var(--call-deep);
  font-weight: 800; text-decoration: none;
  padding: .62rem 1.05rem; border-radius: var(--radius);
  white-space: nowrap; font-size: 1.02rem; letter-spacing: -.01em;
  box-shadow: 0 6px 16px -8px rgba(6,122,42,.65);
}
.header-call:hover {
  background: #05661F; border-color: #05661F;
  box-shadow: 0 8px 20px -8px rgba(6,122,42,.8);
}

@media (min-width: 900px) {
  .site-nav { display: block; }
}

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

.hero {
  background: var(--accent);
  color: var(--hero-ink);
  padding-block: clamp(2rem, 5vw, 3.5rem);
}
.hero .wrap { display: grid; gap: 2rem; }
.hero h1 {
  font-size: clamp(1.75rem, 5vw, 2.9rem);
  letter-spacing: -.01em;
}
.hero-points { list-style: none; margin: 1.5rem 0 0; padding: 0; display: grid; gap: .9rem; }
.hero-points li {
  display: grid; grid-template-columns: 1.6rem 1fr; gap: .7rem;
  color: var(--hero-ink-2); font-size: 1rem; line-height: 1.5;
}
.hero-points .ic { font-size: 1.1rem; line-height: 1.4; }

.trust-bar {
  display: flex; flex-wrap: wrap; gap: .5rem 1.1rem;
  margin-top: 1.5rem; padding-top: 1.1rem;
  border-top: 1px solid rgba(0,0,0,.16);
  font-size: .87rem; color: var(--hero-ink-2);
}

/* ----------------------------------------------------------------------------
   HERO COPY PANEL — the frosted window the hero text sits in.

   Added so the artwork could be scaled up. Before this, headline legibility was the only thing
   holding the art down to 42% and .62 opacity: the copy sat directly on the artwork, so every
   gain in the art was a loss in the text. Giving the copy its own ground breaks that trade -
   the art can be as large as it wants and the text gets BETTER contrast than it had on the
   raw blue, not worse.

   White rather than a blue tint, to match the form card on the other side of the grid. Two
   panels of the same material read as one system; two different materials read as an accident.
   ---------------------------------------------------------------------------- */
.hero-copy {
  position: relative;
  padding: clamp(1.35rem, 2.5vw, 2.1rem) clamp(1.25rem, 2.3vw, 2rem);
  border-radius: var(--radius);
  background: rgba(255,255,255,.72);
  border: 1px solid rgba(255,255,255,.55);
  box-shadow: 0 22px 48px -30px rgba(6,40,58,.5);
  -webkit-backdrop-filter: blur(12px) saturate(1.08);
  backdrop-filter: blur(12px) saturate(1.08);
}

/* Without backdrop-filter the artwork would show through unblurred, so the panel has to carry
   the contrast on opacity alone. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .hero-copy { background: rgba(255,255,255,.85); }
}

/* The trust bar divider was drawn for the blue ground; on the panel it wants to be lighter. */
.hero-copy .trust-bar { border-top-color: rgba(12,27,36,.13); }

@media (max-width: 900px) {
  /* No blur on small screens: it is the one genuinely expensive paint on this page and the
     subject artwork is hidden here anyway, so opacity alone is enough. */
  .hero-copy {
    background: rgba(255,255,255,.8);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    padding: 1.15rem 1rem;
  }
}
.trust-bar span { display: inline-flex; align-items: center; gap: .35rem; }

@media (min-width: 960px) {
  .hero .wrap { grid-template-columns: 1.1fr .9fr; align-items: start; gap: 3rem; }
  /* The hero is sized by the form card, which is taller than the copy. This keeps a clear
     band under the trust bar on every page so the map pin never lands on text. */
  .hero { padding-bottom: 4.5rem; }
}

/* ---------- form card ---------- */

.form-card {
  background: var(--paper);
  color: var(--text);
  border-radius: 18px;
  padding: 1.7rem;
  box-shadow: 0 18px 40px -20px rgba(0,0,0,.55);
}
.form-card h2 { font-size: 1.2rem; margin-bottom: .35rem; }
.form-card .sub { color: var(--muted); font-size: .9rem; margin-bottom: 1rem; }

.field { display: grid; gap: .3rem; margin-bottom: .85rem; }
.field label { font-size: .85rem; font-weight: 700; color: var(--text); }
.field .req { color: var(--danger); }
.field input, .field textarea {
  font: inherit; font-size: 16px; /* 16px prevents iOS zoom-on-focus */
  padding: .65rem .7rem;
  border: 1px solid #C6D2DE; border-radius: 10px;
  background: #fff; color: var(--text);
  width: 100%;
}
.field textarea { min-height: 92px; resize: vertical; }
.field input:invalid:not(:placeholder-shown) { border-color: var(--danger); }

.hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .5rem;
  font: inherit; font-weight: 800; font-size: 1.02rem;
  padding: .85rem 1.2rem; min-height: 48px;
  border: 0; border-radius: var(--radius); cursor: pointer;
  text-decoration: none; width: 100%;
  background: var(--brand-deep); color: #fff;
}
.btn:hover { background: var(--accent); }
.btn--ghost { background: rgba(0,0,0,.06); color: var(--hero-ink); border: 2px solid rgba(0,0,0,.28); }

.form-note { font-size: .8rem; color: var(--muted); margin: .7rem 0 0; }

.form-error {
  background: var(--danger-wash); border: 1px solid var(--danger);
  color: #7A1A13; padding: .7rem .8rem; border-radius: var(--radius);
  font-size: .9rem; margin-bottom: 1rem;
}

/* ---------- offer band ---------- */

.offer-band {
  background: var(--ok-wash);
  border-block: 1px solid #BFE0CE;
  padding-block: 1.6rem;
}
.offer-band .inner { display: grid; gap: .8rem; align-items: center; }
.offer-band h2 { font-size: clamp(1.2rem, 3.2vw, 1.6rem); color: #0B5537; }
.offer-band .price {
  display: inline-block; background: var(--ok); color: #fff;
  font-weight: 800; padding: .35rem .8rem; border-radius: 999px; font-size: .95rem;
}
.offer-band .qualifier { font-size: .9rem; color: #2C4A3C; margin: 0; max-width: 68ch; }
.offer-band a { color: #0B5537; }

@media (min-width: 800px) {
  .offer-band .inner { grid-template-columns: auto 1fr; column-gap: 1.5rem; }
  .offer-band .price { grid-row: span 2; align-self: start; }
}

/* ---------- sections ---------- */

.section { padding-block: clamp(2.8rem, 6vw, 4.6rem); }
.section--tint { background: var(--body); }
.section h2 { font-size: clamp(1.4rem, 3.6vw, 2rem); margin-bottom: 1rem; }
.section h3 { font-size: 1.1rem; font-weight: 700; font-family: var(--f-body); margin-bottom: .4rem; }
.section p, .section li { max-width: 72ch; }

.checklist { list-style: none; margin: 0 0 1.2em; padding: 0; display: grid; gap: .6rem; }
.checklist li { display: grid; grid-template-columns: 1.3rem 1fr; gap: .6rem; }
.checklist li::before { content: "▸"; color: var(--accent); font-weight: 700; }

.router {
  background: var(--paper); border: 1px solid var(--line); border-left: 4px solid var(--accent);
  border-radius: var(--radius); padding: 1rem 1.1rem; margin-top: 1.2rem;
}
.router p { margin: 0 0 .5rem; font-weight: 700; }
.router ul { list-style: none; margin: 0; padding: 0; display: grid; gap: .45rem; }
.router li { font-size: .95rem; }

.steps { display: grid; gap: 1.2rem; margin-top: 1.4rem; }
.step { background: var(--paper); border: 1px solid var(--line); border-radius: var(--radius); padding: 1.4rem 1.5rem; }
.step .n {
  display: inline-flex; align-items: center; justify-content: center;
  width: 1.9rem; height: 1.9rem; border-radius: 50%;
  background: var(--ink); color: #fff; font-weight: 800; font-size: .95rem;
  margin-bottom: .6rem;
}
.step p { margin: 0; font-size: .96rem; }
@media (min-width: 820px) { .steps { grid-template-columns: repeat(3, 1fr); } }

/* ---------- reviews ---------- */

.reviews { display: grid; gap: 1rem; margin-top: 1.2rem; }
.review { background: var(--paper); border: 1px solid var(--line); border-radius: var(--radius); padding: 1.4rem; }
.review .stars { color: #E8A317; letter-spacing: .1em; font-size: .95rem; }
.review blockquote { margin: .5rem 0 .8rem; font-size: .96rem; }
.review cite { font-style: normal; font-weight: 700; font-size: .92rem; display: block; }
.review .src { font-size: .8rem; color: var(--muted); }
.review figcaption { display: flex; align-items: center; gap: .7rem; margin-top: .2rem; }
.review-who { display: block; min-width: 0; }
.review-avatar { width: 52px; height: 52px; border-radius: 50%; object-fit: cover; flex: none; background: var(--body); }
.review-avatar--initial { display: grid; place-items: center; background: var(--brand-wash); color: var(--brand-deep); font-family: var(--f-head); font-weight: 700; font-size: 1.15rem; }
@media (min-width: 780px) { .reviews { grid-template-columns: repeat(2, 1fr); } }
.reviews-more { margin-top: 1.2rem; text-align: center; font-weight: 700; }

/* ---------- platform logos ---------- */

.platforms { text-align: center; }
.platforms h2 { font-size: 1.1rem; font-family: var(--f-body); font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); }
.platform-row { display: flex; flex-wrap: wrap; justify-content: center; align-items: center; gap: 1.6rem 2.4rem; margin-top: 1.2rem; }
.platform-row a { display: inline-flex; }
/* Every logo gets the same 210x84 box and object-fit: contain, which is correct for the three
   that are roughly square-to-landscape — Yelp (1:1), Meta (1.78:1) and Google Ads (1.7:1) all
   fill the 84px height. The BBB asset is a 300x68 STRIP at 4.4:1, so contain fits it to the
   width and it lands ~48px tall: visibly the smallest mark in the row, not because it is styled
   smaller but because its aspect ratio starves it of height in a shared box.
   A wider box gives contain more room to work with, so the BBB mark comes up to ~64px without
   needing a different asset. Matching the full 84px would make it 370px wide and it would then
   dominate the row, which is the opposite problem. */
.platform-row img[src$="bbb.webp"] { width: 282px; height: 72px; }

.platform-row img {
  width: 210px; height: 84px; object-fit: contain;
  /* No grayscale/opacity filter. The Meta asset is a dark mark on a large white canvas —
     greyed and faded inside a small box it renders effectively invisible. Same failure as the
     white-on-white logo bug on this site in June: a trust signal nobody can see is not one. */
}

/* ---------- BBB accreditation seal (footer) ---------- */
/* White chip so the seal stays legible on the dark footer regardless of its own background. */
.bbb-seal { display: inline-block; margin-top: 1rem; background: #fff; padding: .4rem .55rem; border-radius: 8px; line-height: 0; }
/* The official seal is served at 293x61 and renders at that size. It used to be forced to 200px
   wide, which was fine for a self-hosted crop but is the wrong thing to do to BBB's own asset —
   their guidelines expect it displayed as issued. max-width keeps it inside a narrow footer
   column on a phone, and height:auto means the scale stays proportional when it does. */
.bbb-seal img { width: 293px; max-width: 100%; height: auto; }

/* ---------- FAQ ---------- */

.faq { display: grid; gap: .6rem; margin-top: 1.2rem; max-width: 78ch; }
.faq details { background: var(--paper); border: 1px solid var(--line); border-radius: var(--radius); }
.faq summary {
  cursor: pointer; padding: .9rem 1rem; font-weight: 700; font-size: 1rem;
  list-style: none; display: flex; justify-content: space-between; gap: 1rem;
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after { content: "+"; color: var(--accent); font-weight: 800; }
.faq details[open] summary::after { content: "–"; }
.faq .a { padding: 0 1rem 1rem; }
.faq .a p { margin: 0; font-size: .96rem; }

/* ---------- closing CTA ---------- */

.closing { background: var(--accent); color: var(--hero-ink); text-align: center; }
.closing h2 { font-size: clamp(1.4rem, 4vw, 2rem); }
.closing p { color: var(--hero-ink-2); max-width: 60ch; margin-inline: auto; }
.closing .actions { display: grid; gap: .8rem; max-width: 420px; margin: 1.4rem auto 0; }
@media (min-width: 620px) { .closing .actions { grid-template-columns: 1fr 1fr; } }

/* ---------- footer ---------- */

.site-footer { background: var(--ink-deep); color: #D6EAF6; padding-block: 2.4rem 6rem; font-size: .93rem; }
.site-footer a { color: #D6EAF6; }
.site-footer a:hover { color: #fff; }
.footer-grid { display: grid; gap: 1.8rem; }
.footer-grid h3 { font-family: var(--f-body); font-size: .8rem; letter-spacing: .1em; text-transform: uppercase; color: #9FCDE6; font-weight: 700; margin-bottom: .6rem; }
.footer-grid ul { list-style: none; margin: 0; padding: 0; display: grid; gap: .4rem; }
.footer-bottom { border-top: 1px solid rgba(255,255,255,.16); margin-top: 1.8rem; padding-top: 1.2rem; font-size: .85rem; color: #9FCDE6; }
@media (min-width: 760px) { .footer-grid { grid-template-columns: 2fr 1fr 1fr; } }
@media (min-width: 900px) { .site-footer { padding-bottom: 2.4rem; } }

/* ---------- sticky mobile call bar ----------
   Labelled, not a bare icon. The live site used an unlabelled 60px icon overlaying the footer. */

.callbar {
  position: fixed; inset-inline: 0; bottom: 0; z-index: 50;
  display: grid; grid-template-columns: 1fr 1fr; gap: 1px;
  background: rgba(255,255,255,.25);
  box-shadow: 0 -4px 18px rgba(0,0,0,.28);
}
.callbar a {
  display: flex; align-items: center; justify-content: center; gap: .4rem;
  min-height: 56px; font-weight: 800; text-decoration: none; font-size: 1rem;
}
.callbar .call { background: var(--call-deep); color: #fff; }
.callbar .quote { background: var(--hero-from); color: #fff; }
@media (min-width: 900px) { .callbar { display: none; } }

/* ---------- motion ---------- */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: .01ms !important; animation-iteration-count: 1 !important; transition-duration: .01ms !important; scroll-behavior: auto !important; }
}

/* ---------- legal pages ---------- */

.legal { padding-block: clamp(2rem, 5vw, 3.4rem); }
.legal h1 { font-size: clamp(1.7rem, 4.5vw, 2.4rem); margin-bottom: .6rem; }
.legal .legal-dates { color: var(--muted); font-size: .9rem; margin-bottom: 1.4rem; }
.legal .lede { font-size: 1.02rem; max-width: 72ch; }
.legal section { margin-top: 2.2rem; }
.legal h2 { font-size: 1.25rem; margin-bottom: .6rem; scroll-margin-top: 1.5rem; }
.legal h3 { font-size: 1rem; font-family: var(--f-body); font-weight: 700; margin: 1.1rem 0 .3rem; }
.legal p, .legal li { max-width: 76ch; }
.legal ul, .legal ol { padding-left: 1.3rem; }
.legal li { margin-bottom: .45rem; }

.legal-toc {
  background: var(--body); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 1.1rem 1.3rem; margin-top: 1.6rem;
}
.legal-toc h2 { font-size: .8rem; letter-spacing: .1em; text-transform: uppercase; color: var(--muted); font-family: var(--f-body); font-weight: 700; margin-bottom: .5rem; }
.legal-toc ol { margin: 0; padding-left: 1.2rem; columns: 1; }
.legal-toc li { margin-bottom: .3rem; font-size: .93rem; }
@media (min-width: 720px) { .legal-toc ol { columns: 2; column-gap: 2rem; } }

.legal-note {
  background: var(--ok-wash); border-left: 4px solid var(--ok);
  padding: .8rem 1rem; border-radius: var(--radius); font-size: .93rem;
}

.tablewrap { overflow-x: auto; margin: 1rem 0; }
.legal table { border-collapse: collapse; width: 100%; min-width: 520px; font-size: .93rem; }
.legal th, .legal td { border: 1px solid var(--line); padding: .55rem .7rem; text-align: left; vertical-align: top; }
.legal thead th { background: var(--body); font-size: .78rem; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); }

/* Visible input-needed marker. A missing business fact must be obvious on the page, never guessed. */
.needs-input {
  background: var(--danger-wash); border: 1px dashed var(--danger); color: #7A1A13;
  font-weight: 700; padding: .1em .45em; border-radius: 3px; font-size: .92em;
}

/* ---------- case studies ---------- */

.cases { display: grid; gap: 1.1rem; margin-top: 1.2rem; }
.case { background: var(--paper); border: 1px solid var(--line); border-radius: var(--radius); padding: 1.5rem; }
.case h3 { margin-bottom: .4rem; }
.case p { font-size: .96rem; }
.case .results { list-style: none; margin: .8rem 0 0; padding: 0; display: grid; gap: .35rem; }
.case .results li { font-weight: 700; font-size: .95rem; display: grid; grid-template-columns: 1.1rem 1fr; gap: .5rem; }
.case .results li::before { content: "▲"; color: var(--ok); font-size: .8rem; }
.case-note { font-size: .88rem; color: var(--muted); margin-top: 1rem; max-width: 72ch; }
@media (min-width: 780px) { .cases { grid-template-columns: repeat(2, 1fr); } }

.offer-band--plain .inner { grid-template-columns: 1fr; }
.section-note {
  background: var(--surface-2, var(--body)); border-left: 4px solid var(--accent);
  padding: .85rem 1.05rem; border-radius: var(--radius); font-size: .93rem;
  color: var(--text); margin-top: 1.1rem; max-width: 74ch;
}

/* ---------- home service grid ---------- */

.svc-grid { display: grid; gap: 1rem; margin-top: 1.4rem; }
.svc {
  background: var(--paper); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 1.5rem; text-decoration: none; color: var(--text); display: block;
  border-top: 3px solid var(--accent);
}
.svc:hover { border-color: var(--accent); box-shadow: 0 6px 20px -12px rgba(10,17,32,.35); }
.svc h3 { margin-bottom: .4rem; }
.svc p { font-size: .95rem; margin-bottom: .7rem; }
.svc-go { font-weight: 700; font-size: .88rem; color: var(--brand-deep); }
@media (min-width: 700px) { .svc-grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1000px) { .svc-grid { grid-template-columns: repeat(3, 1fr); } }

/* ---------- simple pages ---------- */

.simple { padding-block: clamp(2rem, 5vw, 3.4rem); }
.simple h1 { font-size: clamp(1.8rem, 4.6vw, 2.6rem); margin-bottom: .7rem; }
.simple .lede { font-size: 1.05rem; color: var(--ink-soft, var(--text)); max-width: 72ch; }
.simple .narrow { max-width: 640px; }
.portrait { border-radius: var(--radius); margin-top: 1.4rem; max-width: 420px; width: 100%; height: auto; }

.contact-card {
  background: var(--body); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 1.3rem 1.5rem; margin-top: 2rem; max-width: 640px;
}
.contact-card h2 { font-size: 1.15rem; margin-bottom: .7rem; }
.contact-list { list-style: none; margin: 0; padding: 0; display: grid; gap: .55rem; }
.contact-list li { font-size: 1rem; }
.contact-list a { font-weight: 700; }
.contact-list .muted { color: var(--muted); font-weight: 400; font-size: .92rem; }

/* ============================================================================
   ICONS
   Inline SVG sprite, stroke-based, inheriting currentColor. Replaces the emoji
   that rendered differently on every OS and read as placeholder art.
   ============================================================================ */

.ico {
  width: 1.25em; height: 1.25em;
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.75;
  stroke-linecap: round;
  stroke-linejoin: round;
  vertical-align: -.22em;
}

.hero-points .ic { display: inline-flex; align-items: center; justify-content: center;
  width: 2.1rem; height: 2.1rem; border-radius: 10px;
  background: rgba(0,0,0,.10); color: var(--hero-ink);
  border: 1px solid rgba(0,0,0,.18);
}
.hero-points .ic .ico { width: 1.1rem; height: 1.1rem; vertical-align: 0; }
.hero-points li { grid-template-columns: 2rem 1fr; gap: .85rem; align-items: start; }

.trust-bar .ico { color: var(--hero-ink); width: 1rem; height: 1rem; }
.header-call .ico, .callbar .ico, .btn .ico { width: 1.05em; height: 1.05em; }

/* Checklist marker. NOTE: the li contains a single <span> holding the text, so never
   hide span:first-child here — that blanks the list. The marker is the ::before. */
.checklist li { grid-template-columns: 1.4rem 1fr; }
.checklist li::before {
  content: "";
  width: 1.15rem; height: 1.15rem; margin-top: .22rem; border-radius: 50%;
  background: rgba(55,158,215,.16);
  border: 1px solid rgba(55,158,215,.45);
  background-image: url("data:image/svg+xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 24 24%27 fill=%27none%27 stroke=%27%231F719E%27 stroke-width=%273%27 stroke-linecap=%27round%27 stroke-linejoin=%27round%27%3E%3Cpath d=%27m4 12 5 5L20 6%27/%3E%3C/svg%3E");
  background-size: .8rem; background-repeat: no-repeat; background-position: center;
}

/* ============================================================================
   MOTION
   Rules: transform and opacity only — never a property that reflows, so CLS
   stays where it is. The hidden initial state is gated behind .js, so with
   JavaScript off every section is simply visible. And all of it is switched
   off wholesale by prefers-reduced-motion at the bottom of this file.
   ============================================================================ */

@media (prefers-reduced-motion: no-preference) {

  /* --- hero entrance: one orchestrated sequence on load, not scattered effects --- */
  .js .hero h1,
  .js .hero-points li,
  .js .hero .trust-bar,
  .js .hero .form-card {
    animation: rise .55s cubic-bezier(.22,.61,.36,1) both;
  }
  .js .hero h1              { animation-delay: .04s; }
  .js .hero-points li:nth-child(1) { animation-delay: .12s; }
  .js .hero-points li:nth-child(2) { animation-delay: .18s; }
  .js .hero-points li:nth-child(3) { animation-delay: .24s; }
  .js .hero .trust-bar      { animation-delay: .30s; }
  .js .hero .form-card      { animation-delay: .10s; }

  @keyframes rise {
    from { opacity: 0; transform: translate3d(0, 14px, 0); }
    to   { opacity: 1; transform: none; }
  }

  /* --- scroll reveal --- */
  .js .reveal { opacity: 0; transform: translate3d(0, 18px, 0); }
  .js .reveal.in {
    opacity: 1; transform: none;
    transition: opacity .5s cubic-bezier(.22,.61,.36,1), transform .5s cubic-bezier(.22,.61,.36,1);
  }
  /* stagger children of a revealed grid */
  .js .reveal.in > * { animation: rise .5s cubic-bezier(.22,.61,.36,1) both; }
  .js .reveal.in > :nth-child(2) { animation-delay: .06s; }
  .js .reveal.in > :nth-child(3) { animation-delay: .12s; }
  .js .reveal.in > :nth-child(4) { animation-delay: .18s; }
  .js .reveal.in > :nth-child(5) { animation-delay: .24s; }
  .js .reveal.in > :nth-child(6) { animation-delay: .30s; }

  /* --- micro-interactions --- */
  .btn, .svc, .case, .step, .review, .header-call, .form-card {
    transition: transform .2s cubic-bezier(.22,.61,.36,1),
                box-shadow .2s cubic-bezier(.22,.61,.36,1),
                border-color .2s ease, background-color .2s ease;
  }
  .btn:hover        { transform: translateY(-2px); box-shadow: 0 10px 22px -10px rgba(55,158,215,.65); }
  .btn:active       { transform: translateY(0); }
  .header-call:hover{ transform: translateY(-1px); }
  .svc:hover, .case:hover { transform: translateY(-3px); }
  .svc:hover .svc-go .ico { transform: translateX(3px); }
  .svc-go .ico      { transition: transform .2s cubic-bezier(.22,.61,.36,1); }
  .faq summary      { transition: background-color .15s ease; }
  .faq summary:hover{ background: var(--body); }
}

/* ============================================================================
   POLISH
   ============================================================================ */

/* Hero: a lit ground rather than a flat panel. Pure CSS, no image, no request. */
.hero {
  position: relative;
  overflow: hidden;
  /* One clean blue ramp. The two radial "glows" that used to sit on top of a near-black ground,
     plus the luminous grid, were what made this read as Tron rather than as a brand. */
  background: linear-gradient(160deg, var(--hero-from) 0%, var(--hero-to) 100%);
}
.hero .wrap { position: relative; z-index: 1; }

.hero h1 { letter-spacing: -.02em; }
.form-card { box-shadow: 0 30px 70px -34px rgba(6,40,58,.45), 0 2px 8px rgba(6,40,58,.06); }
.form-card:focus-within { box-shadow: 0 30px 70px -30px rgba(6,40,58,.5), 0 0 0 2px rgba(55,158,215,.45); }

/* Service cards: an icon plate, and a rail that fills on hover. */
.svc { position: relative; overflow: hidden; border-top-width: 1px; }
.svc::after {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px;
  background: var(--accent); transform: scaleY(0); transform-origin: top;
  transition: transform .25s cubic-bezier(.22,.61,.36,1);
}
.svc:hover::after { transform: scaleY(1); }
.svc-ic {
  display: inline-flex; align-items: center; justify-content: center;
  width: 2.4rem; height: 2.4rem; border-radius: 10px; margin-bottom: .8rem;
  background: rgba(55,158,215,.14); color: var(--brand-deep); border: 1px solid rgba(55,158,215,.32);
}
.svc-ic .ico { width: 1.25rem; height: 1.25rem; vertical-align: 0; }
.svc-go { display: inline-flex; align-items: center; gap: .35rem; }

/* Step numbers read as tokens, not bullets. */
.step { position: relative; }
.step .n {
  background: linear-gradient(145deg, var(--hero-from) 0%, var(--accent) 100%);
  box-shadow: 0 4px 12px -6px rgba(10,17,32,.6);
}

/* Review cards: a quote mark that does not shout. */
.review { position: relative; }
.review blockquote { position: relative; z-index: 1; }
.review::before {
  content: "\201C";
  position: absolute; top: .1rem; right: .8rem;
  font-family: var(--f-head); font-size: 4rem; line-height: 1;
  color: var(--accent); opacity: .16;
}

.offer-band { position: relative; overflow: hidden; }
.offer-band::before {
  content: ""; position: absolute; inset: 0;
  background: radial-gradient(600px 200px at 8% 50%, rgba(6,122,42,.09), transparent 70%);
  pointer-events: none;
}
.offer-band .inner { position: relative; z-index: 1; }

/* Sticky header once scrolled — keeps the phone number reachable without stealing space. */
.site-header { position: sticky; top: 0; z-index: 40; }
.site-header.stuck { box-shadow: 0 6px 24px -14px rgba(0,0,0,.8); }

/* One more time, and this one wins: motion off means motion off. */
@media (prefers-reduced-motion: reduce) {
  .js .reveal { opacity: 1 !important; transform: none !important; }
  *, *::before, *::after {
    animation-duration: .01ms !important; animation-iteration-count: 1 !important;
    transition-duration: .01ms !important; scroll-behavior: auto !important;
  }
}

/* ============================================================================
   HERO MAP
   Abstract street map with one profile being found — the literal picture of what
   the Map Pack work does. Inline SVG: no request, no intrinsic size, no CLS.
   Masked so it never competes with the headline for legibility.
   ============================================================================ */

.hero-map {
  position: absolute; inset: 0;
  pointer-events: none;
  overflow: hidden;
  /* Strongest to the right and low, faint behind the H1 — text legibility wins. */
  -webkit-mask-image: radial-gradient(112% 105% at 38% 84%, #000 14%, rgba(0,0,0,.5) 48%, transparent 80%);
  mask-image: radial-gradient(112% 105% at 38% 84%, #000 14%, rgba(0,0,0,.5) 48%, transparent 80%);
}
.hero-map svg { width: 100%; height: 100%; display: block; }

.hm-blocks   { fill: rgba(0,0,0,.05); }
.hm-roads    { stroke: rgba(0,0,0,.10); }
.hm-roads line:nth-child(3),
.hm-roads line:nth-child(9),
.hm-roads line:last-child { stroke: rgba(0,0,0,.18); }

.hm-radius {
  stroke: rgba(0,0,0,.28);
  stroke-width: 1.5;
  stroke-dasharray: 5 9;
}

.hm-pin path   { fill: none; stroke-width: 1.9; stroke-linecap: round; stroke-linejoin: round; }
.hm-pin circle { fill: none; stroke-width: 1.9; }

.hm-pin--dim path, .hm-pin--dim circle { stroke: rgba(0,0,0,.26); }

.hm-pin--found path   { fill: var(--hero-ink); stroke: var(--hero-ink); }
.hm-pin--found circle { fill: var(--accent); stroke: var(--accent); }

.hm-base  { fill: rgba(0,0,0,.5); }
.hm-ring  { stroke: var(--hero-ink); stroke-width: 2; opacity: 0; }

/* Mobile: the map sits behind a much denser column of text, so pull it back further. */
@media (max-width: 700px) {
  .hero-map {
    -webkit-mask-image: radial-gradient(110% 70% at 70% 88%, #000 8%, rgba(0,0,0,.4) 44%, transparent 76%);
    mask-image: radial-gradient(110% 70% at 70% 88%, #000 8%, rgba(0,0,0,.4) 44%, transparent 76%);
  }
}

@media (prefers-reduced-motion: no-preference) {
  /* The pin drops in once, on load. */
  .js .hm-found .hm-pin--found {
    animation: pin-drop .7s cubic-bezier(.2,.8,.3,1.2) .35s both;
  }
  .js .hm-found .hm-base { animation: rise .4s ease-out .95s both; }

  @keyframes pin-drop {
    0%   { opacity: 0; transform: translate(-21px, -104px) scale(1.75); }
    100% { opacity: 1; transform: translate(-21px, -60px) scale(1.75); }
  }

  /* Then a slow "found" pulse. Two rings, offset, 3.4s — deliberately unhurried:
     this is an ambient state, not an alert. Transform/opacity only, so it stays on
     the compositor, and browsers throttle it in background tabs. */
  .js .hm-ring--1 { animation: ping 3.4s ease-out 1.1s infinite; }
  .js .hm-ring--2 { animation: ping 3.4s ease-out 2.8s infinite; }

  @keyframes ping {
    0%   { opacity: .5;  transform: scale(.35); }
    70%  { opacity: 0;   transform: scale(1.9); }
    100% { opacity: 0;   transform: scale(1.9); }
  }
}

/* Reduced motion: the pin is simply there, already found. Nothing moves. */
@media (prefers-reduced-motion: reduce) {
  .hm-ring { opacity: .18; }
}

/* ============================================================================
   HOME — deliberately distinct from the ad landing pages.
   Landing page: form above the fold, one offer, capture immediately.
   Home: no form in the hero, a problem-router in its place, one form at the end.
   ============================================================================ */

.hero--home { padding-block: clamp(3rem, 8vw, 6rem); text-align: left; }
.hero--home .wrap { display: block; max-width: 900px; }

.eyebrow {
  font-size: .78rem; letter-spacing: .16em; text-transform: uppercase;
  font-weight: 700; color: var(--hero-ink); opacity: .72; margin: 0 0 1rem;
}
.hero--home h1 { font-size: clamp(2.1rem, 6.4vw, 4rem); max-width: 16ch; }
.hero-lede {
  font-size: clamp(1.02rem, 2.2vw, 1.2rem); color: var(--hero-ink-2);
  max-width: 60ch; margin: 1.2rem 0 0;
}
.hero-actions { display: flex; flex-wrap: wrap; gap: .8rem; margin-top: 1.8rem; }
.btn--wide { width: auto; padding-inline: 1.8rem; }
/* White-on-blue is for the hero ACTION buttons only. Scoped to .hero-actions on purpose:
   `.hero .btn` also matched the submit button inside the white form card, which rendered it
   white-on-white and made the primary CTA on five ad landing pages invisible. */
.hero-actions .btn:not(.btn--call) { background: var(--hero-ink); color: #fff; }
.hero-actions .btn:not(.btn--call):hover { background: #17323F; }
/* Was translucent grey next to a solid dark primary, so the phone number read as the lesser of
   the two actions. Both are solid now and the hierarchy is carried by COLOUR instead of weight:
   dark = read on, green = call. Green on the blue hero is the strongest contrast available. */
.btn--call {
  position: relative;
  width: auto; padding-inline: 1.4rem;
  background: var(--call-deep); color: #fff;
  border: 1px solid var(--call-deep);
  box-shadow: 0 8px 20px -10px rgba(6,122,42,.7);
}
.btn--call:hover { background: #05661F; border-color: #05661F; }
.hero--home .trust-bar { margin-top: 2rem; }

/* --- the router: the homepage's primary action --- */

.router-band { background: var(--paper); }
.section-lede { font-size: 1.03rem; color: var(--muted); max-width: 70ch; }

.routes { display: grid; gap: .9rem; margin-top: 1.6rem; }
@media (min-width: 760px)  { .routes { grid-template-columns: repeat(2, 1fr); } }

.route {
  display: grid;
  grid-template-columns: 2.9rem 1fr;
  grid-template-areas: "ic q" "ic a";
  gap: .15rem .9rem;
  align-items: start;
  padding: 1.15rem 1.25rem;
  background: var(--body);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  text-decoration: none;
  color: var(--text);
}
.route:hover { border-color: var(--accent); background: var(--brand-wash); }
.route-ic {
  grid-area: ic;
  display: inline-flex; align-items: center; justify-content: center;
  width: 2.5rem; height: 2.5rem; border-radius: 9px;
  background: #fff; color: var(--brand-deep);
  border: 1px solid var(--line);
}
.route-ic .ico { width: 1.3rem; height: 1.3rem; vertical-align: 0; }
.route-q { grid-area: q; font-weight: 700; font-size: 1.02rem; line-height: 1.35; }
.route-a {
  grid-area: a; display: inline-flex; align-items: center; gap: .35rem;
  font-size: .92rem; color: var(--muted);
}
.route-a .ico { width: 1rem; height: 1rem; color: var(--brand-deep); transition: transform .2s ease; }
.route:hover .route-a .ico { transform: translateX(3px); }

/* --- the single form, at the end --- */

.form-band { background: var(--accent); color: var(--hero-ink); }
.form-band h2 { color: var(--hero-ink); }
.form-band p { color: var(--hero-ink-2); max-width: 52ch; }
.form-band .checklist li { color: var(--hero-ink-2); }
.form-band-inner { display: grid; gap: 2rem; align-items: start; }
@media (min-width: 900px) {
  .form-band-inner { grid-template-columns: 1fr 1fr; gap: 3rem; }
}
.form-band .form-card { margin-top: 0; }

/* The home hero is a single column, so it is shorter than the landing-page hero and the map pin
   landed on the trust bar. The map is centred on the hero box, so the fix is to give the box a
   deeper foot — that moves the pin down without moving the copy. */
@media (min-width: 900px) {
  .hero--home { padding-bottom: 9rem; }
}
@media (max-width: 899px) {
  .hero--home { padding-bottom: 6rem; }
}

/* Home hero: single column capped at 900px, so the map focus belongs on the empty right side
   rather than the lower-left band the two-column landing pages leave. */
.hero--home .hero-map {
  -webkit-mask-image: radial-gradient(90% 110% at 74% 52%, #000 14%, rgba(0,0,0,.5) 46%, transparent 78%);
  mask-image: radial-gradient(90% 110% at 74% 52%, #000 14%, rgba(0,0,0,.5) 46%, transparent 78%);
}
@media (max-width: 899px) {
  .hero--home .hero-map {
    -webkit-mask-image: radial-gradient(110% 70% at 66% 86%, #000 8%, rgba(0,0,0,.4) 44%, transparent 76%);
    mask-image: radial-gradient(110% 70% at 66% 86%, #000 8%, rgba(0,0,0,.4) 44%, transparent 76%);
  }
}



/* ============================================================================
   REASSURANCE — under the submit button, where the hesitation actually is.
   Someone handing over a suspended Google profile is trusting us with their
   livelihood. One grey line of small print does not answer that.
   ============================================================================ */

.reassure {
  list-style: none;
  margin: 1.1rem 0 0;
  padding: 1.05rem 0 0;
  border-top: 1px solid var(--line);
  display: grid;
  gap: .6rem;
}
.reassure li {
  display: grid;
  grid-template-columns: 1.55rem 1fr;
  gap: .55rem;
  align-items: start;
  font-size: .88rem;
  color: var(--muted);
  line-height: 1.45;
}
.reassure .ico {
  width: 1.05rem; height: 1.05rem;
  color: var(--ok);
  margin-top: .16rem;
  justify-self: center;
}

/* Warmer surfaces: the cards sit on a soft tint rather than snapping against pure white. */
.section--tint { background: var(--body); }
.svc, .case, .step, .review { box-shadow: 0 1px 2px rgba(6,40,58,.04); }
.svc:hover, .case:hover { box-shadow: 0 12px 28px -16px rgba(6,40,58,.28); }

/* The router cards are the first thing a nervous visitor touches — give them room. */
.route { padding: 1.35rem 1.45rem; }

/* ============================================================================
   HERO ART — one per service, two scenes, 13s cycle (~6.5s each).
   Scene A holds 0-50%, scene B 50-100%. Inner animations run on the same clock,
   placed with keyframe percentages inside their own window.
   NOTE: the local-SEO map is NOT part of this system. It was approved as-is and
   keeps its own single-scene .hero-map styling above.
   ============================================================================ */

/* ----------------------------------------------------------------------------
   HERO ART — a bed and a subject.

   The local-SEO map (.hero-map, its own container and hm-* classes) is untouched and stays the
   reference. Everything below exists to give the other four services the same full-bleed feel
   WITHOUT the failure that killed three earlier attempts: those artworks carry real headlines and
   real prices, so stretching one to cover the hero magnifies its type to roughly ours and it
   starts competing with the copy instead of sitting behind it.

   BED     full-bleed, sliced to cover, same mask and same tonal register as the map. Repeating
           geometry only - no text nodes at all - so it crops on any edge and survives any scale.
   SUBJECT the animated mock, fitted with meet so it is never magnified, sitting in the lower-left
           free zone. This is the role the map gives its pin.
   ---------------------------------------------------------------------------- */

.hero-art {
  position: absolute; inset: 0;
  pointer-events: none;
  overflow: hidden;
  /* Opened up from the map's mask now that .hero-copy protects the text. This only softens the
     top and far edges so the art does not end on a hard line. (.hero-map keeps its own tighter
     mask - that page was approved as-is and its artwork is untouched.) */
  -webkit-mask-image: radial-gradient(132% 122% at 40% 78%, #000 46%, rgba(0,0,0,.6) 76%, transparent 96%);
  mask-image: radial-gradient(132% 122% at 40% 78%, #000 46%, rgba(0,0,0,.6) 76%, transparent 96%);
}

.hero-art .ha-bed-svg {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  display: block;
}

.hero-art .ha-subj-svg {
  position: absolute;
  inset: auto auto 0 0;
  width: min(1000px, 64%);
  height: auto;
  display: block;
  /* Was 42% and heavily damped because the copy sat directly on it and legibility had to win.
     The copy now has its own panel (.hero-copy), so the artwork no longer has to stay out of the
     way - it can be big enough to actually read on a desktop, which is the whole point of having
     built it. It still passes behind the panel; that is fine, the panel is opaque enough. */
  opacity: .85;
}

/* Bed palette. Deliberately the same rgba-black-over-blue register as .hm-blocks / .hm-roads,
   because two different registers in one hero read as two unrelated graphics. */
.ha-bd               { fill: rgba(0,0,0,.055); }
.ha-bd--strong       { fill: rgba(0,0,0,.10); }
.ha-bd--faint        { fill: rgba(0,0,0,.032); }
.ha-bd--kw           { fill: rgba(0,0,0,.085); }
.ha-bd--str          { fill: rgba(255,255,255,.11); }
.ha-bd-panel         { fill: rgba(255,255,255,.055); }
.ha-bd-panel--inner  { fill: rgba(0,0,0,.045); }
.ha-bd-dot           { fill: rgba(0,0,0,.065); }
.ha-bd-rule          { stroke: rgba(0,0,0,.05); stroke-width: 1; }
/* A <path> with no fill declared defaults to fill:black — which rendered the dashboard bed
   sparklines as solid black wedges across the hero. Stroke-only, explicitly. */
.ha-bd-spark         { fill: none; stroke: rgba(0,0,0,.075); stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round; }

/* Below 900px the form stacks under the copy, so there is no free zone left. The subject would
   land on top of the text, so it goes and the bed carries the hero on its own - which it can,
   because the bed is exactly the kind of texture that does not need a free zone. */
@media (max-width: 900px) {
  .hero-art .ha-subj-svg { display: none; }
  .hero-art .ha-bed-svg  { opacity: .75; }
}

/* Under 900px the form stacks below the copy and the art has nowhere free to sit, so it
   goes back to full width behind the whole hero and drops further in opacity. */
@media (max-width: 900px) {
  .hero-art:not(.hero-art--map) { width: 100%; opacity: .22; }
}

/* Without JS, and under reduced motion, scene A simply IS the artwork. */
.ha-scene--a { opacity: 1; }
.ha-scene--b { opacity: 0; }

@media (prefers-reduced-motion: no-preference) {
  .js .ha-scene--a { animation: ha-a 13s linear infinite; }
  .js .ha-scene--b { animation: ha-b 13s linear infinite; }
  @keyframes ha-a { 0%, 46% { opacity: 1 } 50%, 96% { opacity: 0 } 100% { opacity: 1 } }
  @keyframes ha-b { 0%, 46% { opacity: 0 } 50%, 96% { opacity: 1 } 100% { opacity: 0 } }
}

/* ---- shared marks: dark linework and white UI panels on the bright blue field ---- */

.ha-blocks { fill: rgba(0,0,0,.05); }
.ha-roads  { stroke: rgba(0,0,0,.10); }
.ha-roads line:nth-child(3), .ha-roads line:nth-child(9), .ha-roads line:last-child { stroke: rgba(0,0,0,.17); }
.ha-radius { stroke: rgba(0,0,0,.26); stroke-width: 1.5; stroke-dasharray: 5 9; }

.ha-pin path   { fill: none; stroke-width: 1.9; stroke-linecap: round; stroke-linejoin: round; }
.ha-pin circle { fill: none; stroke-width: 1.9; }
.ha-pin--dim path, .ha-pin--dim circle { stroke: rgba(0,0,0,.24); }
.ha-pin--found path   { fill: var(--hero-ink); stroke: var(--hero-ink); }
.ha-pin--found circle { fill: var(--accent); stroke: var(--accent); }
.ha-base { fill: rgba(0,0,0,.45); }
.ha-ring { stroke: var(--hero-ink); stroke-width: 2; opacity: 0; }

.ha-card      { fill: rgba(255,255,255,.94); }
.ha-card--sub { fill: rgba(255,255,255,.72); }
.ha-line      { fill: rgba(12,27,36,.20); }
.ha-body      { fill: rgba(12,27,36,.22); }
.ha-name      { fill: rgba(12,27,36,.62); }
.ha-meta      { fill: rgba(12,27,36,.28); }
.ha-sect      { fill: rgba(12,27,36,.40); }
.ha-avatar    { fill: rgba(12,27,36,.16); }
.ha-avatar--sm { fill: rgba(12,27,36,.16); }
.ha-h1        { fill: rgba(12,27,36,.66); }
.ha-media     { fill: rgba(55,158,215,.30); }
.ha-nav       { fill: rgba(12,27,36,.80); }
.ha-navlogo   { fill: rgba(255,255,255,.92); }
.ha-navlink   { fill: rgba(255,255,255,.45); }
.ha-navcta    { fill: var(--accent); }
.ha-btn       { fill: var(--accent); }
.ha-btn--2    { fill: rgba(12,27,36,.18); }
.ha-btn--3    { fill: #fff; }
.ha-band      { fill: rgba(12,27,36,.86); }
.ha-tile rect { fill: rgba(12,27,36,.06); }
.ha-tile-l    { fill: rgba(12,27,36,.20); }
.ha-tile-ic   { fill: rgba(55,158,215,.55); }
.ha-stars circle { fill: #E8A317; }

/* ---- SERP: Google Ads ---- */

.ha-searchbar { fill: #fff; stroke: rgba(12,27,36,.14); }
.ha-search-ic { stroke: rgba(12,27,36,.42); stroke-width: 1.8; fill: none; stroke-linecap: round; }
.ha-query     { fill: rgba(12,27,36,.62); font: 500 15px var(--f-body); }
.ha-adhl      { fill: rgba(55,158,215,.12); }
.ha-chip      { fill: rgba(12,27,36,.82); }
.ha-chiptx    { fill: #fff; font: 700 10px var(--f-body); text-anchor: middle; letter-spacing: .04em; }
.ha-url       { fill: rgba(12,27,36,.46); font: 400 11px var(--f-body); }
.ha-headline  { fill: #1A57A8; font: 700 17px var(--f-body); }
.ha-desc      { fill: rgba(12,27,36,.55); font: 400 12px var(--f-body); }
.ha-sitelink  { fill: #1A57A8; font: 600 11px var(--f-body); }
.ha-line--url   { fill: rgba(12,27,36,.16); }
.ha-line--title { fill: rgba(26,87,168,.42); }

/* Organic results, positions 2-4. These were grey placeholder bars; they are real listings now,
   because a SERP with one filled-in result and three blank ones does not look like a SERP - it
   looks like a loading state. Every business is invented.
   Sized and coloured DOWN from the sponsored result on purpose: the ad is 17px and sits on a
   tinted highlight, these are 15px on plain white. That contrast is the whole point of the
   artwork on the Google Ads page. */
.ha-o-url   { fill: rgba(12,27,36,.46); font: 400 11px var(--f-body); }
.ha-o-title { fill: #1A57A8; font: 600 15px var(--f-body); }
.ha-o-desc  { fill: rgba(12,27,36,.55); font: 400 11.5px var(--f-body); }

@media (prefers-reduced-motion: no-preference) {
  .js .hero-art--serp .ha-scene--a .ha-adrow { animation: ha-adrow 13s ease-out infinite; }
  .js .hero-art--serp .ha-scene--b .ha-adrow { animation: ha-adrow-b 13s ease-out infinite; }
  @keyframes ha-adrow   { 0%, 2% { opacity: 0; transform: translateY(-10px) } 9%, 46% { opacity: 1; transform: none } 50%, 100% { opacity: 0 } }
  @keyframes ha-adrow-b { 0%, 50% { opacity: 0; transform: translateY(-10px) } 58%, 96% { opacity: 1; transform: none } 100% { opacity: 0 } }

  .js .hero-art--serp .ha-scene--a .ha-organic { animation: ha-org 13s ease-out infinite; animation-delay: calc(var(--i) * .18s); }
  .js .hero-art--serp .ha-scene--b .ha-organic { animation: ha-org-b 13s ease-out infinite; animation-delay: calc(var(--i) * .18s); }
  @keyframes ha-org   { 0%, 6% { opacity: 0 } 14%, 46% { opacity: 1 } 50%, 100% { opacity: 0 } }
  @keyframes ha-org-b { 0%, 54% { opacity: 0 } 62%, 96% { opacity: 1 } 100% { opacity: 0 } }
}

/* ---- PROFILE: a listing being actively maintained ---- */

.ha-photo { fill: rgba(55,158,215,.26); }
.ha-reply { fill: rgba(6,122,42,.42); }

@media (prefers-reduced-motion: no-preference) {
  .js .hero-art--profile .ha-photo  { animation: ha-in 13s ease-out infinite; animation-delay: calc(var(--i) * .07s); }
  .js .hero-art--profile .ha-review { animation: ha-in-b 13s ease-out infinite; animation-delay: calc(var(--i) * .16s); }
  .js .hero-art--profile .ha-stars circle { animation: ha-in-b 13s cubic-bezier(.2,.8,.3,1.4) infinite; animation-delay: calc(var(--i) * .09s); }
  @keyframes ha-in   { 0%, 4%  { opacity: 0; transform: scale(.86) } 16%, 100% { opacity: 1; transform: none } }
  @keyframes ha-in-b { 0%, 54% { opacity: 0; transform: scale(.86) } 66%, 100% { opacity: 1; transform: none } }
}

/* ---- RETARGET: they leave, the ad brings them back ---- */

.ha-arrow     { stroke: rgba(12,27,36,.42); stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round; }
.ha-visitor   { fill: rgba(12,27,36,.30); }
.ha-media--ad { fill: rgba(55,158,215,.42); }
.ha-chip--ad  { fill: rgba(12,27,36,.55); }
.ha-btn--live { fill: var(--ok); }

@media (prefers-reduced-motion: no-preference) {
  .js .hero-art--retarget .ha-leave    { animation: ha-slide 13s ease-in-out infinite; }
  .js .hero-art--retarget .ha-return   { animation: ha-slide-b 13s ease-in-out infinite; }
  .js .hero-art--retarget .ha-feedcard { animation: ha-in-b 13s ease-out infinite; animation-delay: calc(var(--i) * .14s); }
  @keyframes ha-slide   { 0%, 4%  { opacity: 0; transform: translateX(-14px) } 16%, 46% { opacity: 1; transform: none } 50%, 100% { opacity: 0 } }
  @keyframes ha-slide-b { 0%, 72% { opacity: 0; transform: translateX(14px) }  84%, 96% { opacity: 1; transform: none } 100% { opacity: 0 } }
}

/* Reduced motion: scene A, finished, nothing moving. */
@media (prefers-reduced-motion: reduce) {
  .ha-code { opacity: 0; }
  .ha-ring { opacity: .16; }
}



/* ---- BUILD: three scenes, 18s ------------------------------------------------
   Home services, professional firm, storefront. Three scenes on the shared 13s cycle would be
   4.3s each, under the 5s floor where a scene stops being readable — so this hero runs its own
   18s clock and each scene still gets ~6s. Scene A 0-33%, B 33-66%, C 66-100%.
   ------------------------------------------------------------------------------ */

.ha-prod rect      { fill: rgba(12,27,36,.06); }
.ha-prod-img       { fill: rgba(55,158,215,.30) !important; }
.ha-price          { fill: rgba(12,27,36,.42) !important; }
.ha-addcart        { fill: var(--accent) !important; }
.ha-cartic         { fill: none; stroke: rgba(255,255,255,.9); stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.ha-cartic circle  { fill: rgba(255,255,255,.9); stroke: none; }

.hero-art--build .ha-scene--c { opacity: 0; }

@media (prefers-reduced-motion: no-preference) {
  /* Override the shared two-scene timing for this hero only. */
  .js .hero-art--build .ha-scene--a { animation: ha3-a 18s ease-out infinite; }
  .js .hero-art--build .ha-scene--b { animation: ha3-b 18s ease-out infinite; }
  .js .hero-art--build .ha-scene--c { animation: ha3-c 18s ease-out infinite; }

  @keyframes ha3-a { 0%,12% { opacity:0; transform: scale(.985) } 20%,30% { opacity:1; transform:none } 33%,100% { opacity:0 } }
  @keyframes ha3-b { 0%,45% { opacity:0; transform: scale(.985) } 53%,63% { opacity:1; transform:none } 66%,100% { opacity:0 } }
  @keyframes ha3-c { 0%,78% { opacity:0; transform: scale(.985) } 86%,97% { opacity:1; transform:none } 100% { opacity:0 } }

  /* Code appears three times, once before each build. */
  .js .hero-art--build .ha-code { animation: ha3-code 18s ease-in-out infinite; }
  @keyframes ha3-code {
    0% { opacity:0 } 3% { opacity:1 } 10% { opacity:1 } 17% { opacity:0 }
    33% { opacity:0 } 36% { opacity:1 } 43% { opacity:1 } 50% { opacity:0 }
    66% { opacity:0 } 69% { opacity:1 } 76% { opacity:1 } 83% { opacity:0 }
    100% { opacity:0 }
  }
  .js .hero-art--build .ha-tok { animation: ha3-tok 18s ease-out infinite; animation-delay: calc(var(--i) * .035s); }
  @keyframes ha3-tok {
    0%,2% { transform: translateX(-6px) } 6%,32% { transform: none }
    35% { transform: translateX(-6px) } 39%,65% { transform: none }
    68% { transform: translateX(-6px) } 72%,100% { transform: none }
  }

  .js .hero-art--build .ha-tile { animation: ha3-tile 18s ease-out infinite; animation-delay: calc(var(--i) * .08s); }
  @keyframes ha3-tile {
    0%,17% { opacity:0; transform: translateY(8px) } 24%,50% { opacity:1; transform:none }
    52% { opacity:0; transform: translateY(8px) } 57%,83% { opacity:1; transform:none }
    85% { opacity:0; transform: translateY(8px) } 90%,100% { opacity:1; transform:none }
  }
}

/* ============================================================================
   BUILD — code editor that types, then morphs into the site it produces.
   21s / 3 scenes / 7s each. Inside each scene: type ~3s, morph, site holds ~3.5s.
   The site is the payoff and holds longest; the code is the setup.
   ============================================================================ */

.ha-ed-win    { fill: #12202B; }
.ha-ed-bar    { fill: #1B2E3C; }
.ha-ed-gutter { fill: rgba(255,255,255,.04); }
.ha-ed-dot--1 { fill: #E5654B; }
.ha-ed-dot--2 { fill: #E3B341; }
.ha-ed-dot--3 { fill: #4FBF6B; }
.ha-ed-tab    { fill: #12202B; }
.ha-ed-tabtx  { fill: rgba(255,255,255,.72); font: 500 12px var(--f-body); }
.ha-gutter    { fill: rgba(255,255,255,.26); font: 400 12px ui-monospace, Menlo, Consolas, monospace; text-anchor: end; }
.ha-code-tx   { font: 400 13.5px ui-monospace, Menlo, Consolas, monospace; }

/* syntax colours, tuned for legibility at this size on the dark editor ground */
.ha-c-p { fill: #7FD3F7; }   /* tags        */
.ha-c-a { fill: #C9A6F5; }   /* attributes  */
.ha-c-o { fill: rgba(255,255,255,.5); }
.ha-c-s { fill: #9BE7A6; }   /* strings     */
.ha-c-t { fill: #F2F6FA; }   /* text nodes  */
.ha-c-i { fill: transparent; }

/* Typing: each line wipes in left to right. steps() gives it the character-by-character
   feel; the stagger comes from a per-line delay set in the markup, offset per scene so
   each editor types when ITS scene is on screen rather than all three at t=0. */

.hero-art--build .ha-editor { opacity: 1; }
.hero-art--build .ha-site   { opacity: 0; }

@media (prefers-reduced-motion: no-preference) {
  .js .hero-art--build .ha-scene--a { animation: ha3-a 21s linear infinite; }
  .js .hero-art--build .ha-scene--b { animation: ha3-b 21s linear infinite; }
  .js .hero-art--build .ha-scene--c { animation: ha3-c 21s linear infinite; }
  @keyframes ha3-a { 0%,31% { opacity:1 } 34%,97% { opacity:0 } 100% { opacity:1 } }
  @keyframes ha3-b { 0%,31% { opacity:0 } 34%,64% { opacity:1 } 67%,100% { opacity:0 } }
  @keyframes ha3-c { 0%,64% { opacity:0 } 67%,97% { opacity:1 } 100% { opacity:0 } }


  .js .hero-art--build .ha-scene--a .ha-editor { animation: ha-ed-a 21s ease-out infinite; }
  .js .hero-art--build .ha-scene--b .ha-editor { animation: ha-ed-b 21s ease-out infinite; }
  .js .hero-art--build .ha-scene--c .ha-editor { animation: ha-ed-c 21s ease-out infinite; }
  @keyframes ha-ed-a { 0%,16% { opacity:1; transform:none } 20%,100% { opacity:0; transform: scale(1.02) } }
  @keyframes ha-ed-b { 0%,33% { opacity:0 } 34%,49% { opacity:1; transform:none } 53%,100% { opacity:0; transform: scale(1.02) } }
  @keyframes ha-ed-c { 0%,67% { opacity:0 } 68%,83% { opacity:1; transform:none } 87%,100% { opacity:0; transform: scale(1.02) } }

  .js .hero-art--build .ha-scene--a .ha-site { animation: ha-si-a 21s ease-out infinite; }
  .js .hero-art--build .ha-scene--b .ha-site { animation: ha-si-b 21s ease-out infinite; }
  .js .hero-art--build .ha-scene--c .ha-site { animation: ha-si-c 21s ease-out infinite; }
  @keyframes ha-si-a { 0%,16% { opacity:0; transform: scale(.965) } 22%,33% { opacity:1; transform:none } 100% { opacity:0 } }
  @keyframes ha-si-b { 0%,49% { opacity:0; transform: scale(.965) } 55%,66% { opacity:1; transform:none } 100% { opacity:0 } }
  @keyframes ha-si-c { 0%,83% { opacity:0; transform: scale(.965) } 89%,100% { opacity:1; transform:none } }

  .js .hero-art--build .ha-tile { animation: ha-tile3 21s ease-out infinite; animation-delay: calc(var(--i) * .07s); }
  @keyframes ha-tile3 {
    0%,20%  { opacity:0; transform: translateY(7px) } 26%,33% { opacity:1; transform:none }
    53%     { opacity:0; transform: translateY(7px) } 59%,66% { opacity:1; transform:none }
    87%     { opacity:0; transform: translateY(7px) } 93%,100%{ opacity:1; transform:none }
  }
}

/* Reduced motion: the code is already typed and the finished site is what shows. */
@media (prefers-reduced-motion: reduce) {
  .hero-art--build .ha-editor { opacity: 0; }
  .hero-art--build .ha-scene--a .ha-site { opacity: 1; }
}

/* ============================================================================
   RESTORE — the notification the owner actually receives.
   Two scenes, 14s, 7s each: SOFT then HARD suspension. They are genuinely
   different problems, and knowing the difference is the expertise being sold.
   ============================================================================ */

.ha-mailbar     { fill: #E8EEF4; }
.ha-mailbar-tx  { fill: rgba(12,27,36,.5); font: 600 12px var(--f-body); }
.ha-sender-av   { fill: rgba(12,27,36,.12); }
.ha-sender      { fill: rgba(12,27,36,.82); font: 700 14px var(--f-body); }
.ha-sender-sub  { fill: rgba(12,27,36,.42); font: 400 11.5px var(--f-body); }
.ha-subject     { fill: rgba(12,27,36,.88); font: 700 17px var(--f-body); }
.ha-mailbody    { fill: rgba(12,27,36,.58); font: 400 13px var(--f-body); }

.ha-status      { }
.ha-status--soft { fill: rgba(224,169,63,.20); }
.ha-status--hard { fill: rgba(179,38,30,.16); }
.ha-status-tx   { font: 700 11px var(--f-body); text-anchor: middle; letter-spacing: .02em; }
.ha-notice--soft .ha-status-tx { fill: #8A5A00; }
.ha-notice--hard .ha-status-tx { fill: #B3261E; }

.ha-impact       { }
.ha-impact--soft { fill: rgba(224,169,63,.13); }
.ha-impact--hard { fill: rgba(179,38,30,.11); }
.ha-impact-ic    { fill: none; stroke-width: 1.9; stroke-linecap: round; stroke-linejoin: round; }
.ha-impact-ic--soft { stroke: #8A5A00; }
.ha-impact-ic--hard { stroke: #B3261E; }
.ha-impact-tx    { font: 700 13px var(--f-body); }
.ha-notice--soft .ha-impact-tx { fill: #7A5000; }
.ha-notice--hard .ha-impact-tx { fill: #9E2019; }
.ha-impact-sub   { fill: rgba(12,27,36,.55); font: 400 11.5px var(--f-body); }

.ha-appeal    { fill: var(--accent); }
.ha-appeal-tx { fill: #fff; font: 700 12px var(--f-body); text-anchor: middle; }

@media (prefers-reduced-motion: no-preference) {
  .js .hero-art--restore .ha-scene--a { animation: ha-a 14s linear infinite; }
  .js .hero-art--restore .ha-scene--b { animation: ha-b 14s linear infinite; }

  /* the notice slides in like a message arriving, once per scene */
  .js .hero-art--restore .ha-scene--a .ha-notice-inner { animation: none; }
  .js .hero-art--restore .ha-impact { animation: ha-impact-in 14s ease-out infinite; }
  @keyframes ha-impact-in {
    0%,4%   { opacity: 0; transform: translateY(6px) }
    11%,46% { opacity: 1; transform: none }
    50%,54% { opacity: 0; transform: translateY(6px) }
    61%,96% { opacity: 1; transform: none }
    100%    { opacity: 0 }
  }
}

/* ============================================================================
   FINISHED SITE MOCKUPS — the payoff of the code-to-site morph.
   These replaced grey wireframe blocks. Real text is doing most of the work
   here: a grey bar is a placeholder, "Same-Day AC Repair" is a website.
   ============================================================================ */

.sm-page { fill: #FFFFFF; }

.sm-brand   { fill: #14181D; font: 800 14px var(--f-body); letter-spacing: -.01em; }
.sm-navlink { fill: rgba(20,24,29,.62); font: 500 10.5px var(--f-body); }
.sm-navcta    { fill: #14181D; }
.sm-navcta-tx { fill: #fff; font: 700 10.5px var(--f-body); text-anchor: middle; }

.sm-h1  { fill: #FFFFFF; font: 800 21px var(--f-body); letter-spacing: -.015em; }
.sm-sub { fill: rgba(255,255,255,.88); font: 400 11.5px var(--f-body); }

.sm-btn--warm  { fill: #FFC24D; }
.sm-btn--ghost { fill: rgba(255,255,255,.16); stroke: rgba(255,255,255,.7); stroke-width: 1.2; }
.sm-btn--navy  { fill: #14324A; }
.sm-btn--dark  { fill: #2B241E; }
.sm-btn-tx        { fill: #fff; font: 700 11px var(--f-body); text-anchor: middle; }
.sm-btn-tx--dark  { fill: #2B1F08; }

.sm-card rect:first-child { fill: #F5F8FB; }
.sm-card-ic-bg { fill: rgba(20,50,74,.10); }
.sm-card-ic    { fill: none; stroke: #14324A; stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round; }
.sm-card-t     { fill: #14181D; font: 700 11.5px var(--f-body); }
.sm-card-s     { fill: rgba(20,24,29,.56); font: 400 10px var(--f-body); }
.sm-card-link  { fill: #1F719E; font: 700 9.5px var(--f-body); }

.sm-band   { fill: #14324A; }
.sm-band-t { fill: #fff; font: 700 12.5px var(--f-body); }
.sm-band-s { fill: rgba(255,255,255,.66); font: 400 10px var(--f-body); }

/* professional firm — restrained, more type-led */
.sm--pro .sm-page { fill: #FBFAF8; }
.sm-eyebrow      { fill: #8A7A55; font: 700 9px var(--f-body); letter-spacing: .16em; }
.sm-h1--pro      { fill: #16202A; font: 700 19px var(--f-head); }
.sm-sub--pro     { fill: rgba(22,32,42,.6); font: 400 11px var(--f-body); }
.sm-card--pro rect:first-child { fill: #F2F0EB; }
.sm-rule         { fill: #8A7A55; }
.sm-card-t--pro  { fill: #16202A; font: 700 12px var(--f-head); }

/* storefront — warmer, product-led */
.sm--shop .sm-page { fill: #FFFDFA; }
.sm-h1--shop  { fill: #2B241E; font: 800 17px var(--f-body); }
.sm-sub--shop { fill: rgba(43,36,30,.62); font: 400 10px var(--f-body); }
.sm-cart      { fill: none; stroke: #2B241E; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }
.sm-cart circle { fill: #2B241E; stroke: none; }
.sm-prod rect:first-child { fill: #FAF6F1; }
.sm-prod-n { fill: #2B241E; font: 600 10.5px var(--f-body); }
.sm-prod-p { fill: #2B241E; font: 800 12px var(--f-body); }
.sm-add    { fill: #2B241E; }
.sm-add-tx { fill: #fff; font: 700 9px var(--f-body); text-anchor: middle; }
.sm-band--shop   { fill: #F0E7DA; }
.sm-band-s--shop { fill: rgba(43,36,30,.6); }

/* ============================================================================
   EDITOR TYPING — generated by _tools/gen-typing-css.mjs, do not hand-edit.
   Zero animation-delay on purpose: a delay shifts an element's whole loop, and the
   restart then drifts into a scene that is still on screen. The stagger lives in
   the keyframe percentages so every line stays locked to the same 21s clock.
   ============================================================================ */

.ha-codeline { clip-path: inset(0 100% 0 0); }

@media (prefers-reduced-motion: no-preference) {
  .js .hero-art--build .ha-scene--a .ha-codeline:nth-of-type(1) { animation: ha-t-a0 21s steps(20) infinite; }
  @keyframes ha-t-a0 { 0%,2% { clip-path: inset(0 100% 0 0) } 7%,97% { clip-path: inset(0 0 0 0) } 98%,100% { clip-path: inset(0 100% 0 0) } }
  .js .hero-art--build .ha-scene--a .ha-codeline:nth-of-type(2) { animation: ha-t-a1 21s steps(20) infinite; }
  @keyframes ha-t-a1 { 0%,4% { clip-path: inset(0 100% 0 0) } 9%,97% { clip-path: inset(0 0 0 0) } 98%,100% { clip-path: inset(0 100% 0 0) } }
  .js .hero-art--build .ha-scene--a .ha-codeline:nth-of-type(3) { animation: ha-t-a2 21s steps(20) infinite; }
  @keyframes ha-t-a2 { 0%,6% { clip-path: inset(0 100% 0 0) } 11%,97% { clip-path: inset(0 0 0 0) } 98%,100% { clip-path: inset(0 100% 0 0) } }
  .js .hero-art--build .ha-scene--a .ha-codeline:nth-of-type(4) { animation: ha-t-a3 21s steps(20) infinite; }
  @keyframes ha-t-a3 { 0%,8% { clip-path: inset(0 100% 0 0) } 13%,97% { clip-path: inset(0 0 0 0) } 98%,100% { clip-path: inset(0 100% 0 0) } }
  .js .hero-art--build .ha-scene--a .ha-codeline:nth-of-type(5) { animation: ha-t-a4 21s steps(20) infinite; }
  @keyframes ha-t-a4 { 0%,10% { clip-path: inset(0 100% 0 0) } 15%,97% { clip-path: inset(0 0 0 0) } 98%,100% { clip-path: inset(0 100% 0 0) } }
  .js .hero-art--build .ha-scene--b .ha-codeline:nth-of-type(1) { animation: ha-t-b0 21s steps(20) infinite; }
  @keyframes ha-t-b0 { 0%,35% { clip-path: inset(0 100% 0 0) } 40%,97% { clip-path: inset(0 0 0 0) } 98%,100% { clip-path: inset(0 100% 0 0) } }
  .js .hero-art--build .ha-scene--b .ha-codeline:nth-of-type(2) { animation: ha-t-b1 21s steps(20) infinite; }
  @keyframes ha-t-b1 { 0%,37% { clip-path: inset(0 100% 0 0) } 42%,97% { clip-path: inset(0 0 0 0) } 98%,100% { clip-path: inset(0 100% 0 0) } }
  .js .hero-art--build .ha-scene--b .ha-codeline:nth-of-type(3) { animation: ha-t-b2 21s steps(20) infinite; }
  @keyframes ha-t-b2 { 0%,39% { clip-path: inset(0 100% 0 0) } 44%,97% { clip-path: inset(0 0 0 0) } 98%,100% { clip-path: inset(0 100% 0 0) } }
  .js .hero-art--build .ha-scene--b .ha-codeline:nth-of-type(4) { animation: ha-t-b3 21s steps(20) infinite; }
  @keyframes ha-t-b3 { 0%,41% { clip-path: inset(0 100% 0 0) } 46%,97% { clip-path: inset(0 0 0 0) } 98%,100% { clip-path: inset(0 100% 0 0) } }
  .js .hero-art--build .ha-scene--b .ha-codeline:nth-of-type(5) { animation: ha-t-b4 21s steps(20) infinite; }
  @keyframes ha-t-b4 { 0%,43% { clip-path: inset(0 100% 0 0) } 48%,97% { clip-path: inset(0 0 0 0) } 98%,100% { clip-path: inset(0 100% 0 0) } }
  .js .hero-art--build .ha-scene--c .ha-codeline:nth-of-type(1) { animation: ha-t-c0 21s steps(20) infinite; }
  @keyframes ha-t-c0 { 0%,69% { clip-path: inset(0 100% 0 0) } 74%,97% { clip-path: inset(0 0 0 0) } 98%,100% { clip-path: inset(0 100% 0 0) } }
  .js .hero-art--build .ha-scene--c .ha-codeline:nth-of-type(2) { animation: ha-t-c1 21s steps(20) infinite; }
  @keyframes ha-t-c1 { 0%,71% { clip-path: inset(0 100% 0 0) } 76%,97% { clip-path: inset(0 0 0 0) } 98%,100% { clip-path: inset(0 100% 0 0) } }
  .js .hero-art--build .ha-scene--c .ha-codeline:nth-of-type(3) { animation: ha-t-c2 21s steps(20) infinite; }
  @keyframes ha-t-c2 { 0%,73% { clip-path: inset(0 100% 0 0) } 78%,97% { clip-path: inset(0 0 0 0) } 98%,100% { clip-path: inset(0 100% 0 0) } }
  .js .hero-art--build .ha-scene--c .ha-codeline:nth-of-type(4) { animation: ha-t-c3 21s steps(20) infinite; }
  @keyframes ha-t-c3 { 0%,75% { clip-path: inset(0 100% 0 0) } 80%,97% { clip-path: inset(0 0 0 0) } 98%,100% { clip-path: inset(0 100% 0 0) } }
  .js .hero-art--build .ha-scene--c .ha-codeline:nth-of-type(5) { animation: ha-t-c4 21s steps(20) infinite; }
  @keyframes ha-t-c4 { 0%,77% { clip-path: inset(0 100% 0 0) } 82%,97% { clip-path: inset(0 0 0 0) } 98%,100% { clip-path: inset(0 100% 0 0) } }
}

@media (prefers-reduced-motion: reduce) {
  .ha-codeline { clip-path: none; }
}

/* ============================================================================
   CREDIBILITY TIMELINE — home only.
   Replaces the home form band. The brief was explicit: do not lead with years in
   the industry, show what changed across them. So the structure IS the argument —
   a dated sequence nobody could reconstruct without having been there.
   Numbering is a real sequence here, which is the only reason it earns markers.
   ============================================================================ */

.credibility { background: var(--body); }

.eras { list-style: none; margin: 1.8rem 0 0; padding: 0; display: grid; gap: 0; }

.era {
  display: grid;
  grid-template-columns: 110px 1fr;
  gap: 0 1.6rem;
  padding: 1.4rem 0;
  border-top: 1px solid var(--line);
  position: relative;
}
.era:first-child { border-top: 0; }

.era-year {
  font: 800 .92rem var(--f-body);
  letter-spacing: .01em;
  color: var(--brand-deep);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  padding-top: .18rem;
}
.era-body h3 { font-size: 1.08rem; font-weight: 700; font-family: var(--f-body); margin-bottom: .4rem; }
.era-body p  { margin: 0; max-width: 68ch; color: var(--text); }

/* A rail down the years column — the sequence made visible without numbering it. */
.era::before {
  content: "";
  position: absolute;
  left: 100px; top: 0; bottom: 0;
  width: 2px;
  background: linear-gradient(180deg, rgba(55,158,215,.10), rgba(55,158,215,.34), rgba(55,158,215,.10));
}
.era::after {
  content: "";
  position: absolute;
  left: 95px; top: 1.55rem;
  width: 12px; height: 12px; border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 0 4px var(--body);
}

.era-close {
  margin-top: 1.8rem;
  padding: 1.2rem 1.4rem;
  background: var(--paper);
  border: 1px solid var(--line);
  border-left: 4px solid var(--accent);
  border-radius: var(--radius);
  max-width: 76ch;
  font-size: .98rem;
}

@media (max-width: 700px) {
  .era { grid-template-columns: 1fr; gap: .35rem; padding-left: 1.4rem; }
  .era-year { padding-top: 0; }
  .era::before { left: 0; }
  .era::after { left: -5px; top: .45rem; }
}

/* ============================================================================
   DASHBOARD — Google Ads hero. Green above, silence below.
   The green is the real brand --call green, not a decorative chart colour: it is
   the same green the site uses for "call us", which is the joke the page is making.
   ============================================================================ */

.ha-dash-bar    { fill: #F4F7FA; }
.ha-dash-title  { fill: var(--hero-ink); font: 700 13px var(--f-body); }
.ha-dash-range  { fill: rgba(12,27,36,.46); font: 400 11px var(--f-body); }
.ha-dash-rule   { stroke: rgba(12,27,36,.12); stroke-width: 1; }

.ha-metric-label      { fill: rgba(12,27,36,.56); font: 600 12px var(--f-body); }
.ha-metric-num        { fill: var(--hero-ink); font: 800 40px var(--f-body); letter-spacing: -.02em; }
.ha-metric-num--zero  { fill: rgba(12,27,36,.26); }
.ha-metric-num--real  { fill: var(--brand-deep); }
.ha-quiet             { fill: rgba(12,27,36,.40); font: 400 11px var(--f-body); }

.ha-chip-up     { fill: rgba(59,198,79,.16); }
.ha-chip-up-tx  { fill: var(--call-deep); font: 700 11px var(--f-body); text-anchor: middle; }

.ha-area     { fill: rgba(59,198,79,.18); }
.ha-areaLine { fill: none; stroke: var(--call); stroke-width: 2.6; stroke-linecap: round; stroke-linejoin: round; }
.ha-area-dot { fill: var(--call-deep); }

/* The empty call rows are dashed outlines rather than grey fills: a filled row reads as
   content that happens to be blank, an outline reads as a slot nothing arrived in. */
.ha-empty-row { fill: none; stroke: rgba(12,27,36,.16); stroke-width: 1.4; stroke-dasharray: 5 6; }

.ha-break-label     { fill: rgba(12,27,36,.62); font: 400 11.5px var(--f-body); }
.ha-break-val       { fill: var(--hero-ink); font: 700 11.5px var(--f-body); text-anchor: end; }
.ha-break-bar       { fill: rgba(12,27,36,.22); }
.ha-break-bar--real { fill: var(--brand-deep); }

@media (prefers-reduced-motion: no-preference) {
  .js .hero-art--dashboard .ha-scene--a { animation: ha-a 14s linear infinite; }
  .js .hero-art--dashboard .ha-scene--b { animation: ha-b 14s linear infinite; }

  /* The climb draws itself once per loop — the chart going UP is the whole hook. */
  .js .hero-art--dashboard .ha-areaLine {
    stroke-dasharray: 340; animation: ha-draw 14s ease-out infinite;
  }
  @keyframes ha-draw { 0% { stroke-dashoffset: 340 } 26%,100% { stroke-dashoffset: 0 } }

  .js .hero-art--dashboard .ha-break {
    animation: ha-in-b 14s ease-out infinite; animation-delay: calc(var(--i) * .12s);
  }
}

/* ============================================================================
   CALL ATTENTION — the phone number earns motion, nothing else does.

   Calls are the conversion on this site, so the number gets the one piece of
   recurring motion on the page. Two rules kept it from becoming a nuisance:

   1. LONG CYCLE, SHORT MOTION. An 8s loop that moves for ~0.8s and is still for
      the other 7.2. Continuous movement next to text is why "attention" effects
      get read as spam; a periodic nudge reads as a phone actually ringing.
   2. The handset and the halo run on the SAME 8s clock, so they fire together as
      one event rather than as two competing fidgets.

   Everything here is transform/opacity only, so it composites on the GPU and
   cannot cause layout shift — this site is sold on speed and has to be fast.
   ============================================================================ */

/* The halo. A ring that expands out of the button edge and fades. Drawn on a
   pseudo-element so it never affects the button box or its hit area. */
.header-call::after,
.btn--call::after,
.callbar .call::after {
  content: '';
  position: absolute; inset: -2px;
  border-radius: inherit;
  border: 2px solid var(--call);
  opacity: 0;
  pointer-events: none;
}

@media (prefers-reduced-motion: no-preference) {
  .js .header-call .ico,
  .js .btn--call .ico,
  .js .callbar .call .ico { animation: call-shake 8s ease-in-out infinite; transform-origin: 50% 60%; }

  .js .header-call::after,
  .js .btn--call::after,
  .js .callbar .call::after { animation: call-halo 8s ease-out infinite; }

  /* The handset rocks the way a ringing phone does: a hard first swing, then
     progressively smaller ones, then stillness. Even swings read mechanical. */
  @keyframes call-shake {
    0%   { transform: rotate(0deg) }
    1.5% { transform: rotate(-15deg) }
    3%   { transform: rotate(13deg) }
    4.5% { transform: rotate(-9deg) }
    6%   { transform: rotate(6deg) }
    7.5% { transform: rotate(-3deg) }
    9%   { transform: rotate(0deg) }
    100% { transform: rotate(0deg) }
  }

  @keyframes call-halo {
    0%   { opacity: .7; transform: scale(1) }
    11%  { opacity: 0;  transform: scale(1.16) }
    100% { opacity: 0;  transform: scale(1.16) }
  }
}

/* The sticky bar is already the loudest thing on a phone screen, so its halo is
   drawn inside the edge — an outside ring would be clipped by the viewport. */
.callbar .call::after { inset: 3px; border-radius: 6px; }

/* ---------- area contrast band (opens the About page) ----------
   Two images, nothing else. They share the same 4:3 source aspect, so a plain two-column grid
   aligns them exactly and the eye compares like for like — which is the entire job of the band.
   A small gap rather than none: butted together they read as one panorama, and the contrast
   only works if you can see there are two frames. */
.area-band { padding-top: clamp(1rem, 2.5vw, 1.6rem); }
.area-pair {
  display: grid; gap: .6rem;
  grid-template-columns: 1fr 1fr;
}
.area-pair img {
  width: 100%; height: 100%; display: block;
  object-fit: cover;
  aspect-ratio: 4 / 3;
  border-radius: var(--radius);
  border: 1px solid var(--line);
}
/* Deliberately quiet. It has to be present, but it is a disclosure, not a caption. */
.area-credit {
  margin: .5rem 0 0;
  font-size: .72rem; letter-spacing: .02em;
  color: var(--muted); opacity: .7;
}
@media (max-width: 640px) {
  .area-pair { grid-template-columns: 1fr; }
}
