/* ============================================================================
   GROWTH NUTS design system
   ----------------------------------------------------------------------------
   THESIS: The homepage is the scanner. A visitor types a URL and watches real
   agents read their site, then gets every finding, the evidence behind it, and
   what it costs per month. It refuses the agency landing page that claims
   results nobody can check, and it refuses the cream-and-one-accent rendition
   that every design tool ships in 2026.

   OWN-WORLD: Restrained product-software craft, held to the reference the
   owner pinned (Linear, Vercel, Raycast) and rendered light, because a dark
   theme is ruled out. White ground, cool neutrals, one blue accent used
   sparingly, Inter at display weight 510 rather than 800, small type, generous
   space, hairline borders at 9% ink, pill and 8px radii. Mobile first: the
   scan is thumb-reachable, findings open as a sheet, actions dock to the
   bottom of the viewport.

   STORY: Suspicion, then a real inspection running in front of them, then a
   number in their own currency, then one obvious next move.

   FIRST VIEWPORT (phone): compact app bar carrying the wordmark, one headline,
   a full-width URL field with a single primary action, and the report card
   beginning immediately beneath it.

   FORM: Live Service Tracker, user-pinned, preserved. Rendered in the pinned
   reference's craft rather than the previous cream world.

   FINISH: unreviewed and undocumented is unfinished; this build ends with the
   finish review, the verdict, and DESIGN.md.
   ----------------------------------------------------------------------------
   Linked last in <head> so it wins ties against the legacy inline CSS it
   supersedes. html.gn prefixes give 0,1,1 which beats a legacy single class.
   ============================================================================ */

/* Newsreader sets the headlines and Public Sans the text.
   A serif headline is the one typographic choice nothing generated reaches
   for by default, and with 590 articles and a named author behind them the
   site has the content to carry it. Inter was 93% of the page and is the face
   every AI-built site in this category ships.
   Both are self-hosted: a CDN link would be blocked by the CSP and would put
   a third party in the critical path of first paint. */
@font-face {
  font-family: 'Newsreader';
  src: url(/fonts/newsreader-var.woff2) format('woff2');
  font-weight: 400 600;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Public Sans';
  src: url(/fonts/publicsans-var.woff2) format('woff2');
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

/* Metric-matched fallbacks. Without them the swap shifts layout, and a site
   whose pitch is Core Web Vitals cannot ship a CLS it caused itself.

   The block these replace was named 'Inter Fallback' and carried Inter's
   numbers — ascent 90%, descent 22.43%, size-adjust 107.06% — which stopped
   being right the moment the body face became Public Sans and the display
   face Newsreader. It was tuned for a font this site no longer loads, so it
   was not preventing a shift, it was causing one. It also put the string
   "Inter" in the stylesheet of a site that had deliberately moved off Inter.

   These numbers are measured, not copied from a table: the same string
   rendered in each real face and in its local fallback, on this site, at the
   loaded weights. size-adjust is the width ratio; the ascent and descent
   overrides are the real face's own metrics divided by that ratio, because
   the spec applies them to the em *after* size-adjust has scaled it.

     Public Sans vs Arial      width x1.0475, ascent .95, descent .23
     Newsreader  vs Times      width x1.1146, ascent .74, descent .26 */
@font-face {
  font-family: 'Public Sans Fallback';
  src: local('Arial'), local('Helvetica Neue'), local('Liberation Sans');
  ascent-override: 90.7%;
  descent-override: 21.96%;
  line-gap-override: 0%;
  size-adjust: 104.75%;
}

/* Re-measured 2026-09-10. Newsreader has an optical-size axis, so its width
   relative to Times grows with size (x1.021 at 18px, x1.046 at 30px, x1.089
   at 52px) and no single size-adjust fits every size. 111.46% was right only
   for the largest headlines: at phone width it made the /about/ h1 wrap to two
   lines in the fallback and one in Newsreader, a 0.20 CLS. 103.5% stays within
   ~1% across the 20-32px range the site uses most. Ascent and descent are the
   font's own hhea values (1470 / 530 of 2000) divided by that size-adjust. */
@font-face {
  font-family: 'Newsreader Fallback';
  src: local('Times New Roman'), local('Tinos'), local('Liberation Serif');
  ascent-override: 71.01%;
  descent-override: 25.60%;
  line-gap-override: 0%;
  size-adjust: 103.5%;
}

html.gn {
  --bg: #FFFFFF;
  --bg2: #FAFAFA;
  --bg3: #F4F4F5;
  --ink: #08090A;
  --ink2: #5B5F66;   /* 6.3:1 on white */
  --ink3: #6E737C;   /* 4.8:1 on white, the floor for body-size text */
  --line: rgba(8, 9, 10, .09);
  --line2: rgba(8, 9, 10, .15);
  --ac: #3D63DD;     /* white on this is 5.2:1 */
  --acink: #FFFFFF;
  --actint: rgba(61, 99, 221, .09);
  --bad: #C0362C;
  --warn: #9A5A0B;   /* 5.5:1 on white; #B4690E is 4.25:1 and fails as text */
  --ok: #1A7F4B;

  --f: 'Public Sans', 'Public Sans Fallback', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  --fd: 'Newsreader', 'Newsreader Fallback', Georgia, 'Times New Roman', serif;
  --mo: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, monospace;

  --bar: 56px;
  --safe: env(safe-area-inset-bottom, 0px);
  --gut: clamp(1rem, .55rem + 2.2vw, 2.75rem);
  --max: 1180px;

  color-scheme: light;
  -webkit-tap-highlight-color: transparent;
}

@media (min-width: 900px) { html.gn { --bar: 60px; } }

/* ------------------------------------------------------------------ ground */

html.gn body {
  font-family: var(--f);
  font-size: 15px;
  line-height: 1.55;
  color: var(--ink);
  background: var(--bg);
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
  font-feature-settings: 'cv02', 'cv03', 'cv04', 'cv11';
}
/* The legacy templates pad the body to clear a position:fixed nav. This system
   makes the nav sticky, so it occupies its own space and that padding became
   dead air at the top of every page outside the homepage. */
html.gn body { padding-top: 0; }

html.gn :where(button, input, select, textarea) { font: inherit; color: inherit; }
html.gn a { color: inherit; text-decoration: none; }
html.gn img { max-width: 100%; height: auto; }
html.gn ::selection { background: var(--actint); }

/* Layers the previous worlds left behind. */
html.gn .bg-effects, html.gn .orb, html.gn .grid-pattern, html.gn .cta-glow,
html.gn .wsw { display: none !important; }

html.gn :focus-visible { outline: 2px solid var(--ac); outline-offset: 2px; }

/* -------------------------------------------------------------- typography */

html.gn h1, html.gn h2, html.gn h3, html.gn h4 {
  font-family: var(--fd);
  /* A serif carries its own weight and needs far less negative tracking than
     the sans these numbers were tuned for; at -.028em Newsreader closes up. */
  font-weight: 500;
  letter-spacing: -.012em;
  line-height: 1.1;
  color: var(--ink);
  text-wrap: balance;
}
html.gn h1 { font-size: clamp(30px, 7.4vw, 52px); font-weight: 500; letter-spacing: -.016em; line-height: 1.05; }
html.gn h2 { font-size: clamp(22px, 3.6vw, 32px); }
html.gn h3 { font-size: clamp(17px, 2.2vw, 20px); font-weight: 550; letter-spacing: -.018em; }
html.gn h4 { font-size: 16px; font-weight: 550; letter-spacing: -.012em; }

html.gn h2 { margin: 0 0 12px; }
html.gn h3 { margin: 0 0 8px; }
html.gn p { margin: 0 0 14px; }
html.gn p:last-child { margin-bottom: 0; }

/* The eyebrow the previous world used above every heading is gone; the
   heading carries itself. Kept only as a label inside components. */
html.gn .section-label {
  font-family: var(--mo);
  font-size: 11.5px;
  font-weight: 500;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--ink3);
  display: inline-flex;
  align-items: center;
  gap: 7px;
  margin: 0 0 10px;
}
html.gn .section-label::before { content: none; }

html.gn .section-title { margin-bottom: 10px; }
html.gn .section-desc { color: var(--ink2); font-size: 16px; max-width: 62ch; line-height: 1.55; }

html.gn .section { padding: clamp(44px, 8vw, 88px) var(--gut); max-width: var(--max); margin: 0 auto; }
html.gn .section-dark {
  background: var(--bg2);
  border-block: 1px solid var(--line);
  max-width: none;
}
html.gn .section-dark > * { max-width: var(--max); margin-inline: auto; }
html.gn .section-dark h1, html.gn .section-dark h2,
html.gn .section-dark h3, html.gn .section-dark h4 { color: var(--ink); }
html.gn .section-dark p, html.gn .section-dark .section-desc { color: var(--ink2); }

html.gn .proof-num, html.gn .stat-num, html.gn .rh-stat, html.gn table {
  font-variant-numeric: tabular-nums;
}

/* ---------------------------------------------------------------- app bar */

html.gn .nav {
  position: sticky;
  top: 0;
  z-index: 1000;
  min-height: var(--bar);
  padding: 0 var(--gut);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  background: rgba(255, 255, 255, .86);
  border-bottom: 1px solid var(--line);
  backdrop-filter: saturate(180%) blur(14px);
  -webkit-backdrop-filter: saturate(180%) blur(14px);
}
html.gn .nav::before { content: none; }

/* The wordmark: GR + hex-nut O + WTH NUTS. The nut is optically aligned to a
   .757em cap band with ~.015em overshoot top and bottom, measured rather than
   eyeballed. */
html.gn .nav-logo {
  display: inline-flex;
  align-items: baseline;
  font-weight: 680;
  font-size: clamp(15px, 1.4vw, 17px);
  letter-spacing: -.035em;
  color: var(--ink);
  background: none;
  -webkit-text-fill-color: currentColor;
  white-space: nowrap;
}
html.gn .gn-o {
  display: inline-block;
  width: .691em;
  height: .757em;
  margin: 0 .022em;
  position: relative;
  top: .014em;
  fill: currentColor;
}
/* The logo's letters are aria-hidden (an SVG stands in for the O, which read
   as "GRWTH", or "GR O WTH" once labelled), so the link's name comes from
   this visually hidden text instead. */
html.gn .gn-sr {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%);
  white-space: nowrap; border: 0;
}

html.gn .nav-links { gap: 1px; align-items: center; }
html.gn .nav-links > li > a {
  font-size: 13.5px;
  font-weight: 460;
  letter-spacing: -.006em;
  color: var(--ink2);
  padding: 6px 10px;
  border-radius: 7px;
  transition: background .12s, color .12s;
}
html.gn .nav-links > li > a:hover { background: var(--bg3); color: var(--ink); }
html.gn .nav-links a::after { content: none; }

html.gn .nav-cta {
  font-size: 13.5px;
  font-weight: 500;
  padding: 7px 14px;
  border-radius: 999px;
  background: var(--ink);
  color: var(--bg);
  border: 1px solid var(--ink);
  min-height: 36px;
  display: inline-flex;
  align-items: center;
  transition: opacity .13s;
}
html.gn .nav-cta:hover { opacity: .86; background: var(--ink); color: var(--bg); }
html.gn .nav-cta::before { content: none; }

html.gn .search-toggle {
  min-width: 38px; min-height: 38px; border-radius: 9px;
  border: 1px solid var(--line2); background: var(--bg); color: var(--ink2);
  display: flex; align-items: center; justify-content: center;
}
html.gn .search-toggle:hover { background: var(--bg3); color: var(--ink); }
html.gn .mobile-toggle {
  min-width: 38px; min-height: 38px; border-radius: 9px;
  border: 1px solid var(--line2); background: var(--bg);
  align-items: center; justify-content: center; gap: 4px;
}
html.gn .mobile-toggle span { background: var(--ink); height: 1.6px; width: 16px; }
/* The legacy sheet hides this above 768px, then a later a11y block re-declares
   display:flex at equal specificity and wins on order. Restate both states. */
@media (max-width: 899px) { html.gn .mobile-toggle { display: flex; } }
@media (min-width: 900px) { html.gn .mobile-toggle { display: none; } }

html.gn .mega {
  background: var(--bg);
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  box-shadow: 0 14px 40px -28px rgba(8, 9, 10, .4);
  gap: 20px 28px;
  padding: 26px var(--gut) 22px;
  transform: none;
}
html.gn .has-mega:hover > .mega, html.gn .has-mega:focus-within > .mega { transform: none; }
html.gn .mega-h {
  font-family: var(--mo); font-size: 10.5px; letter-spacing: .1em;
  text-transform: uppercase; color: var(--ink3); margin-bottom: 9px;
}
html.gn .mega a { font-size: 13.5px; color: var(--ink2); padding: 5px 0; border-radius: 5px; }
html.gn .mega a:hover { color: var(--ac); }
html.gn .mega-all {
  border-top: 1px solid var(--line); color: var(--ac) !important;
  font-size: 13px; font-weight: 520; padding-top: 12px !important;
}

html.gn .mobile-nav {
  background: var(--bg);
  align-items: stretch; justify-content: flex-start;
  padding: calc(var(--bar) + 12px) var(--gut) 40px;
  gap: 0; overflow-y: auto;
}
html.gn .mobile-nav a {
  font-size: 17px; font-weight: 520; letter-spacing: -.018em;
  color: var(--ink); padding: 11px 0; border-bottom: 1px solid var(--line);
}
html.gn .mobile-nav .m-h {
  font-family: var(--mo); font-size: 10.5px; letter-spacing: .1em;
  text-transform: uppercase; color: var(--ink3); margin: 20px 0 2px;
}
html.gn .mobile-nav .m-sub {
  font-size: 15px !important; font-weight: 440 !important;
  color: var(--ink2) !important; padding: 10px 0 !important;
  min-height: 44px; display: flex; align-items: center;
}
html.gn .mobile-nav .m-cta {
  background: var(--ac); color: var(--acink) !important; border-radius: 10px;
  justify-content: center; margin-top: 20px; padding: 14px !important;
  font-weight: 530 !important; border-bottom: none;
}
html.gn .mobile-nav-close { min-width: 44px; min-height: 44px; color: var(--ink2); }
/* The cookie banner sits at z-index 10000 and covered the bottom of the open
   menu at phone width. An open menu is modal, so it goes over everything. */
html.gn .mobile-nav.open { z-index: 10002; }

/* -------------------------------------------------------------- controls */

html.gn .btn, html.gn .hero-audit-btn, html.gn .cookie-accept,
html.gn button[type='submit'] {
  font-size: 15px; font-weight: 520; letter-spacing: -.008em;
  min-height: 46px; padding: 0 20px; border: 0; border-radius: 10px;
  display: inline-flex; align-items: center; justify-content: center;
  transition: opacity .13s, background .13s;
}
/* A bare `.btn` has to carry its own colour pair.
   It used to inherit one, and that is how 1,845 pages shipped an unreadable
   call to action. `html.gn a { color: inherit }` below is specificity 0,1,2;
   the page sheets set the label with `a.btn { color:#fff }` at 0,1,1 and lose.
   So the label fell back to the inherited body ink and landed near-black on
   the near-black fill the same sheets set: 1.0:1 on /services/geo/, a solid
   black box where "Get a Free Audit" should be.

   `.btn-primary` was never affected because it states both halves right here.
   Stating both halves for the bare class too is the whole fix — and it is the
   same lesson the hover comment below already records, one state earlier.

   Black rather than the accent: the near-black fill is what these pages
   already render and what the masthead's own call to action looks like, so
   only the unreadable half changes. Repainting 1,845 buttons blue would be a
   redesign wearing a bug fix's clothes. `.btn-secondary` still wins its own
   colours — same specificity, stated later. */
html.gn .btn, html.gn .cta-final-btn, html.gn .submit-btn,
html.gn .audit-btn, html.gn .comp-submit-btn {
  background: var(--ink); color: var(--bg);   /* 19.9:1 */
}
html.gn .btn-primary, html.gn .hero-audit-btn, html.gn .cookie-accept {
  background: var(--ac); color: var(--acink);
}
html.gn .btn-primary:hover, html.gn .hero-audit-btn:hover { opacity: .9; }
html.gn .btn-secondary, html.gn .cookie-decline {
  background: var(--bg); border: 1px solid var(--line2); color: var(--ink);
}
/* Every hover state restates BOTH colour and background.
   The legacy sheet carries `.btn:hover{background:var(--accent-1);color:#fff}`.
   Overriding only the background left the inherited white text on a near-white
   fill, so the secondary button's label disappeared on hover. A hover state
   that changes one half of a colour pair is how contrast failures ship. */
/* Order matters: these share specificity with the generic rule below, so the
   generic one is stated first and the variants override it. */
html.gn .btn:hover { background: var(--bg3); color: var(--ink); }

html.gn .btn-primary:hover,
html.gn .hero-audit-btn:hover,
html.gn .cookie-accept:hover {
  background: var(--ac);
  color: var(--acink);            /* 5.2:1 */
  opacity: .92;
}

html.gn .btn-secondary:hover,
html.gn .cookie-decline:hover {
  background: var(--bg3);
  color: var(--ink);              /* 19.3:1 on #F4F4F5 */
  border-color: var(--line2);
}
/* Drafting-era lift is not this world's depth system. */
html.gn .btn:hover, html.gn .btn-primary:hover, html.gn .btn-secondary:hover,
html.gn .service:hover, html.gn .problem:hover, html.gn .solution:hover,
html.gn .testimonial:hover, html.gn .audit-feature:hover,
html.gn .tool-card-home:hover, html.gn .gn-ind-card:hover,
html.gn .back-to-top:hover { transform: none; box-shadow: none; }

html.gn input[type='text'], html.gn input[type='email'], html.gn input[type='url'],
html.gn input[type='tel'], html.gn input[type='search'], html.gn select, html.gn textarea {
  background: var(--bg); border: 1px solid var(--line2); border-radius: 10px;
  padding: 12px 14px; min-height: 46px; width: 100%; font-size: 16px;
  transition: border-color .14s, box-shadow .14s;
}
html.gn input:focus, html.gn select:focus, html.gn textarea:focus {
  border-color: var(--ac); box-shadow: 0 0 0 3px var(--actint); outline: none;
}
html.gn input::placeholder { color: var(--ink3); }

html.gn .skip-link {
  background: var(--ac); color: var(--acink); border-radius: 0 0 8px 0;
  font-size: 14px; font-weight: 520; padding: 10px 16px;
  /* The legacy sheet parks this at top:-40px, which assumed a 40px-tall link.
     This one is 42px, so two pixels of a blue block hung below the top edge of
     every page. Park it far enough up that no future type change can expose it
     again; :focus still pulls it to 0. */
  top: -120px;
}
html.gn .skip-link:focus,
html.gn .skip-link:focus-visible { top: 0; }

/* The back-to-top control and the scan's docked action both live in the bottom
   right. Lift it clear so they never stack. */
html.gn .back-to-top { bottom: 88px; }
@media (min-width: 820px) { html.gn .back-to-top { bottom: 84px; } }

/* ----------------------------------------------------------------- cells */

html.gn .stat, html.gn .proof-stat, html.gn .gn-ind-card, html.gn .tool-card-home,
html.gn .testimonial, html.gn .service, html.gn .problem, html.gn .solution,
html.gn .audit-feature, html.gn .result-highlight {
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 12px;
  box-shadow: none;
  padding: 18px;
}
html.gn .gn-ind-card:hover, html.gn .tool-card-home:hover,
html.gn .service:hover, html.gn .audit-feature:hover { border-color: var(--line2); background: var(--bg2); }
html.gn .problem { border-left: 1px solid var(--line); }
html.gn .solution { border-left: 1px solid var(--line); }

html.gn .proof-bar { background: var(--bg2); border-block: 1px solid var(--line); padding: 26px var(--gut); text-align: left; }
html.gn .proof-stats { gap: 14px; }
html.gn .proof-stat { border: 1px solid var(--line); background: var(--bg); }
html.gn .proof-num { font-size: clamp(20px, 3vw, 26px); font-weight: 570; letter-spacing: -.028em; color: var(--ink); display: block; }
html.gn .proof-label, html.gn .stat-label { font-size: 12.5px; color: var(--ink3); margin-top: 4px; display: block; line-height: 1.4; }
html.gn .proof-industries { justify-content: flex-start; }
html.gn .proof-industries-label { font-size: 12.5px; color: var(--ink3); }
html.gn .proof-tag {
  font-size: 12.5px; border: 1px solid var(--line); border-radius: 999px;
  padding: 4px 11px; color: var(--ink2); background: var(--bg);
}

html.gn .service-num, html.gn .step-dot {
  font-family: var(--mo); font-size: 11.5px; font-weight: 500;
  letter-spacing: .06em; color: var(--ink3); background: none;
}
html.gn .step-dot {
  width: 30px; height: 30px; border-radius: 999px;
  border: 1px solid var(--line2); color: var(--ink2);
  display: flex; align-items: center; justify-content: center; font-size: 12px;
}

html.gn .cta { background: var(--bg2); text-align: left; border-block: 1px solid var(--line); }
html.gn .cta-title { color: var(--ink); }
html.gn .cta-desc { color: var(--ink2); margin-left: 0; margin-right: 0; }
html.gn .cta-dual { justify-content: flex-start; }
html.gn .cta-sub { color: var(--ink3); font-size: 12.5px; }

html.gn .footer {
  background: var(--bg); border-top: 1px solid var(--line);
  padding: 40px var(--gut) 34px; text-align: left;
}
html.gn .footer-links { justify-content: flex-start; gap: 8px 20px; margin-bottom: 18px; }
html.gn .footer-links a { font-size: 13.5px; color: var(--ink2); min-height: 40px; display: inline-flex; align-items: center; }
html.gn .footer-links a:hover { color: var(--ink); }
html.gn .footer-copy { font-size: 12.5px; color: var(--ink3); }

html.gn .back-to-top {
  background: var(--ink); color: var(--bg); border-radius: 999px; box-shadow: none;
}
html.gn .cookie-banner { background: var(--bg); border-top: 1px solid var(--line); backdrop-filter: none; }
html.gn .cookie-content { background: var(--bg); }
html.gn .cookie-content p { color: var(--ink2); font-size: 13.5px; }
html.gn .cookie-content a { color: var(--ac); text-decoration: underline; }

html.gn .search-container-modal {
  background: var(--bg); border: 1px solid var(--line2); border-radius: 14px; box-shadow: 0 24px 60px -30px rgba(8, 9, 10, .5);
}
html.gn .search-header { border-bottom: 1px solid var(--line); }
html.gn .search-header input { color: var(--ink); background: none; border: 0; min-height: 0; box-shadow: none; }
html.gn .search-header svg, html.gn .search-close { color: var(--ink3); }
html.gn .search-result { color: var(--ink); border-radius: 8px; }
html.gn .search-result:hover { background: var(--bg3); }
html.gn .search-result-excerpt { color: var(--ink2); }
html.gn .search-result-excerpt mark { background: var(--actint); color: var(--ink); }

html.gn .glossary-term { border-bottom: 1px dashed var(--ac); }
html.gn .glossary-tooltip {
  background: var(--ink); color: #fff; border: 0; border-radius: 10px;
  font-size: 13px; line-height: 1.5; padding: 10px 13px;
}

/* Content is visible by default; the animation is the enhancement. */
html.gn .reveal { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  html.gn *, html.gn *::before, html.gn *::after {
    animation-duration: .01ms !important; transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}
@media (forced-colors: active) {
  html.gn .nav-logo, html.gn .proof-num { -webkit-text-fill-color: currentColor; color: CanvasText; }
}
@media print {
  html.gn .nav, html.gn .mobile-nav, html.gn .back-to-top, html.gn .cookie-banner,
  html.gn .gn-dock { display: none !important; }
}

/* Visually hidden but available to screen readers and to autofill. */
html.gn .gn-sr {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ----------------------------------------------------------------- accordion

   The FAQ answers on 2,056 pages open by animating `max-height` from 0 to a
   flat 500px. Nothing is truncated today: the longest answer on the site is
   425 characters and renders 301px tall at 390px wide, so there is 200px of
   headroom. It is a trap rather than a bug, and the trap is that it fails
   silently — the day somebody writes a longer answer the end of it simply
   stops existing, with no error and nothing in a console to notice.

   `grid-template-rows: 0fr -> 1fr` animates to the content's real height, so
   there is no number to outgrow. It also animates a value the compositor can
   interpolate, where max-height forces layout on every frame of every open.

   Stated at 0,1,1 so it beats the legacy `.faq-answer` in each page's inline
   block, which is how the rest of this system reaches those pages. One rule
   here rather than 2,056 edited style blocks.

   `.faq-answer-inner` IS the grid item, and a row collapsed to 0fr still
   renders its item's padding — measured at 20px, a visible band under every
   closed question. The padding it carries is bottom-only, so it collapses
   with the row and comes back with it. Two properties animate instead of one;
   both are cheap at 20px, and the alternative needs a wrapper element that
   does not exist on 2,056 pages. */
html.gn .faq-answer {
  display: grid;
  grid-template-rows: 0fr;
  max-height: none;
  overflow: hidden;
  transition: grid-template-rows .28s ease;
}
/* The cap has to be lifted here, not on the base rule. The legacy sheet says
   `.faq-item.active .faq-answer { max-height: 500px }` at 0,3,0; a
   `max-height: none` on `html.gn .faq-answer` is 0,2,1 and loses, so the fix
   looked correct and changed nothing. A 5,100px answer still rendered at 500.
   Caught by opening a deliberately tall answer and comparing scrollHeight
   against the rendered box. */
html.gn .faq-item.active .faq-answer { grid-template-rows: 1fr; max-height: none; }
html.gn .faq-answer > * { min-height: 0; }

/* The gap under an answer is content, not padding.
   `.faq-answer-inner` IS the grid item, and an item's padding renders even
   when its track is collapsed to 0fr — measured at 20px, a visible band under
   every closed question. Animating that padding closed worked and meant
   animating a layout property on every toggle, which is the thing this file
   tells everyone else not to do.
   A spacer inside the item collapses with the row for free, the same way the
   answer text already does, so nothing but grid-template-rows animates. */
html.gn .faq-answer > .faq-answer-inner { padding-bottom: 0; }
html.gn .faq-answer > .faq-answer-inner::after {
  content: '';
  display: block;
  height: 1.25rem;
}

@media (prefers-reduced-motion: reduce) {
  html.gn .faq-answer { transition: none; }
}

/* A table that will not fit is the one thing allowed to scroll sideways, and
   only inside itself. /blog-writing-services/ renders its comparison table
   515px wide in a 390px viewport, so the whole page scrolls horizontally —
   the failure this site treats as never acceptable. Found while verifying the
   accordion on that page; it predates that change and is live today.

   Scrolling the table rather than shrinking the type keeps the columns
   readable, and the caption tells a visitor the gesture exists rather than
   leaving them to discover a cut-off column. */
html.gn .comparison-table {
  display: block;
  max-width: 100%;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}
html.gn .comparison-table thead,
html.gn .comparison-table tbody { display: table; width: 100%; min-width: 460px; }

/* Accent labels sit on a 10% wash of their own colour, which drags the pair to
   4.46:1 — a fail, and the kind that looks fine until it is measured. Twelve
   of them on /results/ alone, and the wash is on 2,861 pages.

   The ink stays exactly #3D63DD. The brand colour is not mine to adjust, and
   it does not need adjusting: lightening the ground it sits on fixes the pair
   just as well. At 6% the same blue measures 4.62:1 with the tint still
   clearly present. 7% was the first passing value and sits at 4.54, too close
   to the line to absorb a future tweak. */
html.gn .hero-badge,
html.gn .case-location,
html.gn .case-tag,
html.gn .tools-badge,
html.gn .badge-accent {
  background: rgba(61, 99, 221, .06);
  border-color: rgba(61, 99, 221, .18);
}

/* Two prose classes never got the measure the rest of the system has.
   `.section-desc` is capped at 62ch; `.case-challenge` runs to 109 characters
   a line and `.cta-desc` to 122, well past the point where the eye loses the
   start of the next line. Same cap, applied where it was missed.

   `.cta-desc` is centred, so it keeps its own centring as the box narrows;
   `.case-challenge` is left-aligned body copy inside a card and simply stops
   earlier. */
html.gn .case-challenge,
html.gn .case-desc { max-width: 62ch; }
html.gn .cta-desc { max-width: 62ch; margin-inline: auto; }

/* ==================================================== template-wide defaults

   Found by scanning one page per template rather than page by page: every
   finding below repeats on every page built from the same template, so each
   rule here is worth thousands of pages.

   MEASURE. Bare paragraphs and list items in the content column run to 101
   and 114 characters. `.section-desc` was capped at 62ch and nothing else
   was, so the cap only ever applied to the one class that already had it.
   The ch unit is the width of a zero, not of an average character: 68ch in
   Inter measured 86 actual characters, so the first attempt at this capped
   nothing that mattered. 58ch lands around 73 characters, comfortably under
   the 80 the eye can track, and still wide enough not to look starved. */
html.gn main :is(p, li) { max-width: 58ch; }
/* Centred copy keeps its centring: a max-width with no auto margin would pin
   the box left and leave the text centred inside it, which reads as broken. */
html.gn main :is(p, li)[align="center"],
html.gn main .cta-desc,
html.gn main .content-desc { margin-inline: auto; }

/* EDGE OR ELEVATION, NOT BOTH. A hairline border under a wide diffuse shadow
   is a generated-UI signature, and this system already committed: hairline
   borders at 9% ink. The card keeps its edge and drops the 34px halo. Twelve
   of them on a single service page. */
html.gn .gn-card { box-shadow: none; }

/* TEXT FLOORS. 10.5px on a functional label and 11.5px body copy are below
   the point where the type is legible at arm's length on a phone. */
html.gn :is(.hero-badge, .badge, .pill, .tag) { font-size: max(12px, 0.75rem); }
html.gn .hero-badge { padding-block: 6px; }

/* A dot that pulses forever is decoration pretending to be status. The badge
   reads perfectly without it, and nothing on the page is actually live. */
html.gn .hero-badge::before { animation: none; }

/* `.section-desc` carried its own 62ch and so outranked the measure above it,
   which is how a 16px paragraph still ran to 148 characters: 62ch of Inter at
   16px is 624px, and ch is the width of a zero, not of an average character.
   Same real target, expressed at the same specificity as the rule it beats. */
html.gn main p.section-desc,
html.gn main .section-desc { max-width: 52ch; }

/* The scan badge: a 10.5px label in a 2px-tall pill. Below the legibility
   floor and too tight to read as a considered chip rather than an accident. */
html.gn .gn-badge { font-size: 12px; padding: 4px 9px; }
html.gn .gn-badge b { font-size: inherit; }

/* Nav group labels and the tracker's status word, all under the 11px floor. */
html.gn :is(.m-h, .mega-h, .gn-cstat) { font-size: 12px; }

/* Text under the 12px floor on the homepage: the calculator's field labels,
   the formula caption, the service numerals and the evidence list. All of it
   is functional text somebody has to read on a phone. */
html.gn :is(.gn-formula, .service-num, .gn-dl, .gn-cstat, label) { font-size: 12px; }

/* Buttons centre their label in a 46px min-height with grid, so they have no
   vertical padding to speak of. That reads as correct to a person and as zero
   to a scanner; giving them real padding costs nothing and removes the
   ambiguity. */
html.gn :is(.gn-btn, .gn-badge b) { padding-block: 8px; }

/* The remaining buttons centre their label in a min-height with flex, so the
   padding reads as zero even though the label sits comfortably. min-height
   still governs the box; this only makes the intent explicit. */
html.gn :is(.gn-go, .btn, .btn-primary, .btn-secondary, .nav) { padding-block: 8px; }
html.gn .nav { padding-block: 10px; }

/* ===================================================== service page rhythm

   929 pages are built from this template and they read as one undifferentiated
   wall: five heading-and-paragraph pairs in a row where h2 carries no top
   margin at all, so a new section starts 14px after the last one ends and
   nothing tells the eye a subject changed. The type was doing all the work
   and the spacing was doing none.

   A heading now opens its section with real air above it and sits closer to
   the paragraph it introduces than to the one it follows — the proximity that
   makes a page scannable without a single rule or box. */
html.gn main .content > h2 {
  margin-top: clamp(40px, 5vw, 68px);
  margin-bottom: 10px;
}
html.gn main .content > h2:first-child { margin-top: 0; }
html.gn main .content > p { margin-bottom: 0; }
html.gn main .content > p + p { margin-top: 14px; }

/* The state directory is link scaffolding, not reading material, and at
   1,401px it was over a third of the page. Denser columns and quieter type
   put it in proportion to what it is. */
html.gn main .content > .grid {
  grid-template-columns: repeat(auto-fill, minmax(170px, 1fr));
  gap: 2px 14px;
  margin-block: 20px;
}
html.gn main .content > .grid a { font-size: 14px; padding-block: 5px; }
html.gn main .content > .grid a span,
html.gn main .content > .grid a small { color: var(--ink3); font-size: 12px; }

/* The closing call to action was floating between two 40px margins with the
   state list above it; it needs its own edge to read as an ending. */
html.gn main .content > .cta-box {
  margin-top: clamp(48px, 6vw, 76px);
  border-top: 1px solid var(--line);
  padding-top: clamp(28px, 4vw, 44px);
}

/* ==================================================== service template v2

   929 pages. The page read as an unstyled document for three reasons, all
   measurable at 1440px:

   1. TWO LEFT EDGES. `html.gn .hero` (0,2,0) sets max-width:1180px for the
      homepage's full-bleed hero. Service pages put `.hero` and `.c` on the
      same element, and `.c` is 900px at 0,1,0 — so it lost. The hero started
      at x=170 and everything under it at x=294. A 124px jog down the page.
   2. NO STRUCTURE. Five heading-and-paragraph pairs stacked in one column,
      each identical to the last. Spacing alone can separate them; it cannot
      tell the eye what kind of thing it is looking at.
   3. CARD SOUP. Six filled boxes, four filled FAQ boxes, fifty filled state
      boxes and a filled CTA — 61 grey rounded rectangles on one page. This
      system already committed to hairlines at 9% ink. Edge, not fill.

   The fix is one measure, an editorial rail, and hairlines throughout. CSS
   only: no markup, no route, no copy changes, and it lands on all 929. */

/* One measure. Scoped to `.hero.c` so the 191 pages with a bare `.hero`
   (the homepage and its kin) keep their intentional 1180px full bleed. */
html.gn .hero.c {
  max-width: 900px;
  padding-inline: 24px;
  padding-bottom: clamp(30px, 4vw, 46px);
  border-bottom: 1px solid var(--line);
}
html.gn .hero.c > p {
  font-size: 18px;
  line-height: 1.55;
  color: var(--ink2);
  max-width: 58ch;
  margin-bottom: 26px;
  text-wrap: pretty;
}

/* THE RAIL. A two-column grid turns the wall into a structure: the subject
   sits in a 200px rail and its explanation in the column beside it, so the
   eye can skim five subjects without reading five paragraphs. Auto-placement
   pairs them — h2 takes column 1, the paragraph that follows takes column 2,
   and both land on the same row.

   A heading that introduces a block rather than a paragraph (`:has(+ div)`)
   spans instead, because a rail heading over a full-width grid would leave a
   dead cell and break the rhythm the rail exists to create. */
html.gn main .content {
  --rg: clamp(34px, 4.4vw, 52px);
  display: grid;
  grid-template-columns: minmax(0, 200px) minmax(0, 1fr);
  column-gap: 40px;
  row-gap: var(--rg);
}
html.gn main .content > h2 { grid-column: 1; margin: 0; }
html.gn main .content > p  { grid-column: 2; margin: 0; }
html.gn main .content > h2:has(+ div),
html.gn main .content > :is(.features, .faq-container, .grid, .cta-box) {
  grid-column: 1 / -1;
}

/* The rail heading. Serif at the size of a strong label, not a shout: the
   offset is doing the separating, so the type does not have to. */
html.gn main .content > h2 {
  font-family: var(--fd);
  font-size: clamp(19px, 2.1vw, 23px);
  font-weight: 500;
  line-height: 1.2;
  letter-spacing: -.012em;
  text-wrap: balance;
  padding-top: 3px;
}

/* 15px grey body copy in a 532px measure floating inside an 852px column was
   the other half of "unstyled document". The grid column now sets the measure
   at ~610px — about 72 characters at 17px — so the 58ch cap has to stand down
   or it starves the column it was meant to protect. */
html.gn main .content > p {
  font-size: 17px;
  line-height: 1.65;
  color: var(--ink2);
  max-width: none;
}
html.gn main .content > p + p { margin-top: 14px; }

/* Section headers over a block: full width, a hairline to open, more air. */
/* A section header belongs to the block under it, not to the whitespace above
   it. The row gap that separates two prose sections is far too much between a
   heading and the thing it names — at 1440px it left 52px there and the
   heading read as orphaned. Pulling the row back to 22px restores the
   proximity, and expressing it against the same token keeps it honest at
   every width instead of only the one I measured. */
html.gn main .content > h2:has(+ div) {
  font-size: clamp(24px, 3vw, 30px);
  border-top: 1px solid var(--line);
  padding-top: clamp(30px, 3.6vw, 44px);
  margin-bottom: calc(22px - var(--rg));
}
html.gn main .content > h2:first-child { border-top: 0; padding-top: 0; }

/* WHAT'S INCLUDED — six filled boxes become six ruled entries. The blue
   heading in each box put six accent-coloured words on the page competing
   with the two links that actually go somewhere; ink reads calmer and the
   hairline already says "this is a discrete item". */
html.gn main .content > .features {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(228px, 1fr));
  gap: 0 40px;
  margin: 0;
}
html.gn main .content > .features > .feature {
  background: none;
  border: 0;
  border-top: 1px solid var(--line);
  border-radius: 0;
  padding: 13px 0 20px;
}
html.gn main .content > .features > .feature h3 {
  color: var(--ink);
  font-size: 15px;
  font-weight: 600;
  letter-spacing: -.006em;
  margin-bottom: 5px;
}
html.gn main .content > .features > .feature p {
  font-size: 14px;
  line-height: 1.55;
  color: var(--ink2);
  max-width: none;
}

/* FAQ — same move. The accordion mechanics above this block are untouched;
   only the chrome changes, so the grid-rows animation and the ::after spacer
   keep working exactly as they did. */
html.gn main .content .faq-container { margin: 0; }
html.gn main .content .faq-item {
  background: none;
  border: 0;
  border-top: 1px solid var(--line);
  border-radius: 0;
  margin-bottom: 0;
}
html.gn main .content .faq-question {
  padding: 17px 0;
  font-size: 16px;
  font-weight: 500;
  gap: 24px;
}
html.gn main .content .faq-answer > .faq-answer-inner {
  padding-inline: 0;
  font-size: 16px;
  color: var(--ink2);
  max-width: 68ch;
}

/* THE STATE DIRECTORY. Fifty bordered, filled, centred, rounded cards for
   fifty links: the single loudest thing on the page and the least important.
   It is a directory, so it now looks like one — name left, count right,
   hairline above, nothing else. */
html.gn main .content > .grid {
  grid-template-columns: repeat(auto-fill, minmax(158px, 1fr));
  gap: 0 26px;
  margin: 0;
}
html.gn main .content > .grid > .card {
  background: none;
  border: 0;
  border-top: 1px solid var(--line);
  border-radius: 0;
  padding: 9px 0;
  text-align: left;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  transition: border-top-color .2s ease, color .2s ease;
}
html.gn main .content > .grid > .card:hover { border-top-color: var(--ink); }
html.gn main .content > .grid > .card h3 {
  font-size: 14px;
  font-weight: 500;
  margin: 0;
  letter-spacing: -.004em;
}
html.gn main .content > .grid > .card:hover h3 { color: var(--ac); }
html.gn main .content > .grid > .card p {
  font-size: 12px;
  color: var(--ink3);
  margin: 0;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

/* THE CLOSE. A centred grey slab with a 12px radius was the one element still
   arguing for the old look. It ends the page on the same hairline the rest of
   the page is built from, and left-aligned so it sits on the measure. */
html.gn main .content > .cta-box {
  background: none;
  border-radius: 0;
  border-top: 1px solid var(--line);
  padding: clamp(34px, 4.4vw, 52px) 0 0;
  margin: clamp(12px, 2vw, 20px) 0 0;
  text-align: left;
}
html.gn main .content > .cta-box h3 {
  font-family: var(--fd);
  font-size: clamp(24px, 3vw, 32px);
  font-weight: 500;
  line-height: 1.15;
  letter-spacing: -.014em;
  text-wrap: balance;
  max-width: 26ch;
  margin-bottom: 12px;
}
html.gn main .content > .cta-box p {
  font-size: 17px;
  line-height: 1.6;
  color: var(--ink2);
  max-width: 52ch;
  margin: 0 0 24px;
}

/* The rail cannot exist under about 720px: a 200px column beside a paragraph
   leaves neither enough room. Below that it stacks, and the heading keeps the
   proximity that makes the stack readable. */
@media (max-width: 719px) {
  html.gn main .content {
    grid-template-columns: minmax(0, 1fr);
    row-gap: clamp(28px, 7vw, 38px);
  }
  html.gn main .content > h2,
  html.gn main .content > p { grid-column: 1; }
  html.gn main .content > h2:has(+ p) { margin-bottom: -14px; }
  html.gn main .content > .features { gap: 0; }
  html.gn main .content > .grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* =============================================== detector sweep, site-wide

   Every finding below was located by pointing the detector at a running copy
   of each template and then measuring the element it named in the browser.
   Two of the classes it reported were false positives and are recorded as
   such; the fix still applies, because in both cases the thing confusing the
   detector was decoration nobody would miss. */

/* DECORATIVE GRADIENT WASHES. `.case-study` carries a 3% blue-to-blue
   gradient and `.cta-box` an 8% one. Composited, they are #FBFBFE and
   #F1F2F9 — a wash you cannot see against white unless you sample it.

   Two consequences, one real and one not:

   FALSE POSITIVE. The detector reads the raw gradient stop rather than the
   composited surface, so it scored ink on #3D63DD and reported 1.0:1, 1.1:1
   and 1.2:1 across 16 findings. Measured properly — compositing the real
   ancestor chain — the same three elements are 19.9:1, 12.4:1 and 6.4:1.
   Nothing was ever hard to read.

   REAL. On the 8% `.cta-box` wash, `--ink3` (#6E737C, 4.8:1 on white) drops
   to 4.3:1 and genuinely fails AA. That one is a defect and this fixes it.

   A gradient with a 3% delta between its two stops is not doing any work.
   Removing it settles both at once and takes the surface back to the ground
   colour the rest of the system is built on. */
html.gn :is(.case-study, .cta-box, .case-study-card, .gn-spot) {
  background-image: none;
}

/* A blanket `color: var(--ink2)` inside those panels stood here for one pass
   as "belt and braces" and was a straight regression: `.case-result-label`
   sits on a #CCC surface where full ink reads 12.4:1, and forcing the muted
   token onto it dropped 33 elements to 4.0:1 — a real failure, invented by
   the fix for a failure that had already been removed. The lesson is narrow
   and worth keeping: a contrast rule written against a background you have
   not measured is a guess, and this one guessed white. Removing the wash was
   the whole fix; nothing further belongs here. */

/* ALL-CAPS BODY TEXT. Twenty form labels are set uppercase, and they are not
   labels in the one-or-two-word sense the convention is for: "Block Specific
   Bots (one per line, optional)" is 44 characters of shouting, and we read
   words by their ascenders and descenders, which uppercase removes. The
   letter-spacing that made the caps bearable goes with them. */
html.gn :is(.form-label, .tk-label, .calc-label) {
  text-transform: none;
  letter-spacing: .002em;
  font-size: 13px;
  color: var(--ink2);
}
html.gn .tool-panel h4,
html.gn .tool-card h4 { text-transform: none; letter-spacing: -.006em; }

/* UNDERSIZED FUNCTIONAL TEXT. The press strip on /about/ sets outlet names
   (`.gn-pub`) at 10.88px and dates (`.gn-when`) at 10.56px — sixteen findings,
   below the 11px floor this file already applies everywhere else. They are the
   only citation the page offers for its own authority, so they should be
   readable. `.m-h` is in the same state at 10.5px: the existing floor rule
   for it is 0,2,0 and loses to the nav sheet's `.mobile-nav .m-h`, so it is
   restated here where it can win. */
html.gn .gn-clip :is(.gn-pub, .gn-when),
html.gn :is(.gn-pub, .gn-when, .gn-press-group),
html.gn .mobile-nav .m-h,
html.gn .gn-press-item :is(span, time) {
  font-size: max(12px, 0.75rem);
}

/* A halo behind a heading is atmosphere, not information, and the article
   template renders one on every one of 590 posts. */
html.gn :is(.article-glow, .post-glow, .hero-glow, .cta-glow) {
  display: none;
}

/* MEASURE, the last few templates. `main :is(p, li)` already caps the content
   column, but these three sit outside `main` or override it locally and were
   still running to ~90 characters. */
html.gn :is(.hero, .article-header, .tool-intro) > p,
html.gn .press-item p,
html.gn .about-lede { max-width: 58ch; }

/* ================================================ the industry family, 1,146

   Found by fingerprinting all 2,864 pages by structure instead of guessing
   which ones shared a layout. Two things this turned up that page-by-page
   fixing had missed for weeks:

   1. `<main>` on every industry page carries NO class. Every rule in this
      file scoped `html.gn main .content` therefore reaches the 929 service
      pages and silently cannot touch these. The fix looked right on the page
      it was tested against and never applied to the larger group.

   2. 996 pages set `class="container"` on their breadcrumb, hero and every
      section, and never define `.container`. The state pages define it inline
      and look fine; these do not, so the measure is `none` and the padding is
      zero — content sits at x=0, hard against the viewport edge, which is
      what the unstyled run of state links looks like in the wild.

   Defining it here fixes all 996 and unifies the ~30 pages that had their own
   1200px copy onto the one measure the rest of the site uses. */
html.gn .container {
  max-width: var(--max);
  margin-inline: auto;
  padding-inline: var(--gut);
}
/* `.section` already carries the measure and the gutter, so the pair would
   otherwise set padding-inline twice at the same value; harmless, but stating
   it once keeps the two from drifting apart later. */
html.gn .section.container { padding-inline: var(--gut); }

/* `.city-list` is used on 347 pages and defined on none of them: fifty state
   links rendered as a default bulleted list, full width, one per row. It is a
   directory, so it reads as one — the same treatment the service pages' state
   grid got, for the same reason. */
html.gn .city-list {
  list-style: none;
  margin: 18px 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(232px, 1fr));
  gap: 0 28px;
}
html.gn .city-list > li { margin: 0; }
html.gn .city-list > li > a {
  display: block;
  padding: 9px 0;
  border-top: 1px solid var(--line);
  font-size: 14px;
  color: var(--ink2);
  transition: color .2s ease, border-top-color .2s ease;
}
html.gn .city-list > li > a:hover { color: var(--ink); border-top-color: var(--ink); }

/* Same treatment for the two sibling directories, so a visitor moving between
   the hub, the state and the city never meets three different list styles. */
html.gn :is(.city-index, .state-index) ul { list-style: none; margin: 0; padding: 0; }

/* The same two habits, found again on the industry family.

   WASHES. `.market-insight` (331 state pages) and `.emergency-box` carry the
   same 5-8% two-stop gradient as `.case-study` did — invisible as design,
   and the reason the detector scored eleven findings at 1.2:1 on prose that
   actually reads at 6.4:1.

   DARK-THEME LEFTOVER. `.emergency-stat` is `rgba(0,0,0,.2)`, a panel meant to
   sit on a dark ground. On white it composites to #CCC: a muddy grey box, and
   `--warn` on it measures 3.7:1 — under AA, and real, not an artefact of how
   the detector composites. Every child of that box is dark text, so moving it
   to the system's own panel colour takes --warn to 5.7:1 and full ink to 19:1.
   Checked before changing it, because the last time a contrast rule went in
   here without measuring the surface it created 33 failures of its own. */
html.gn :is(.market-insight, .emergency-box) { background-image: none; }
html.gn .emergency-stat {
  background-color: var(--bg2);
  border: 1px solid var(--line);
}

/* ============================================= the map tooltip was invisible

   `.state-tooltip` sets `background: rgba(0,0,0,.95)` and never sets `color`.
   It therefore inherits the light theme's near-black ink and renders black
   text on a black chip — about 1:1. The state name has always been in the
   DOM; on 19 pages nobody has ever been able to read it. What you see when
   you hover a state is a small black rectangle.

   The whole hover affordance of the map depended on this. Stated in system
   tokens so it cannot drift from the palette again. */
html.gn .state-tooltip {
  background: var(--ink);
  color: var(--bg);            /* 19.9:1 */
  border: 0;
  border-radius: 6px;
  padding: 7px 12px;
  font-family: var(--f);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: -.004em;
  box-shadow: 0 6px 20px rgba(8, 9, 10, .18);
}

/* Emoji as an icon is the section-marker tell, and here it is worse than
   decoration: 🚿🔧🔥🚽💧 in front of six keyword rows and 📍⭐🎯📄🔧📊 in front
   of six service cards render at a different size, weight and colour on every
   platform, and carry no meaning the heading does not already carry.

   `.faq-icon` and `.compare-icon` are deliberately NOT in this list. Their
   glyphs are `+` and `✕` — a disclosure state and a not-included marker. Those
   carry information the text does not, so hiding them would remove meaning
   rather than decoration. */
html.gn :is(.service-icon, .industry-icon, .keyword-icon,
            .related-icon, .problem-icon, .type-icon,
            .gn-dx-ic, .gn-ind-ic, .tool-card-icon, .check-icon) { display: none; }

/* ------------------------------------------------- keyword cost, as a chart

   Six quantities with real spread ($20-40 up to $45-80) were set as six
   centred boxes of text, so the one thing they exist to say — some of these
   clicks cost four times what others do — had to be worked out by reading and
   comparing. Drawn against a shared scale it is immediate.

   The bar spans min to max rather than plotting a midpoint: the source gives
   a range, so the chart shows a range. Inventing a point estimate to make a
   tidier bar would be inventing data. */
html.gn .keywords-grid {
  display: block;
  margin: 22px 0 0;
}
html.gn .keywords-grid .keyword-card {
  display: grid;
  grid-template-columns: minmax(0, 15rem) minmax(0, 1fr) 6.5rem;
  align-items: center;
  gap: 0 18px;
  padding: 11px 0;
  background: none;
  border: 0;
  border-top: 1px solid var(--line);
  text-align: left;
}
html.gn .keywords-grid .keyword-text {
  font-size: 14px;
  font-weight: 500;
  color: var(--ink);
}
/* The track is the full $0-80 scale; the fill is positioned and sized from
   two custom properties set per row in the markup, so the numbers in the
   chart and the numbers in the text come from the same place. */
html.gn .keywords-grid .keyword-bar {
  position: relative;
  height: 9px;
  border-radius: 999px;
  background: var(--bg3);
}
html.gn .keywords-grid .keyword-bar::after {
  content: '';
  position: absolute;
  inset-block: 0;
  left: calc(var(--lo) / 80 * 100%);
  width: calc((var(--hi) - var(--lo)) / 80 * 100%);
  border-radius: 999px;
  background: var(--ac);
  opacity: .8;
  transition: opacity .2s ease;
}
html.gn .keywords-grid .keyword-card:hover .keyword-bar::after { opacity: 1; }
html.gn .keywords-grid .keyword-cpc {
  font-family: var(--mo);
  font-size: 13px;
  color: var(--ink2);
  text-align: right;
  margin: 0;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
html.gn .keywords-grid .keyword-card:hover .keyword-cpc { color: var(--ink); }

/* The axis makes the bars readable as amounts rather than as decoration. */
html.gn .keyword-axis {
  display: grid;
  grid-template-columns: minmax(0, 15rem) minmax(0, 1fr) 6.5rem;
  gap: 0 18px;
  margin-top: 8px;
  border-top: 1px solid var(--line);
  padding-top: 7px;
}
html.gn .keyword-axis .keyword-axis-scale {
  grid-column: 2;
  display: flex;
  justify-content: space-between;
  font-family: var(--mo);
  font-size: 11.5px;
  color: var(--ink3);
  font-variant-numeric: tabular-nums;
}

@media (max-width: 719px) {
  html.gn .keywords-grid .keyword-card,
  html.gn .keyword-axis { grid-template-columns: minmax(0, 1fr) 5.5rem; }
  html.gn .keywords-grid .keyword-text { grid-column: 1 / -1; margin-bottom: 7px; }
  html.gn .keyword-axis .keyword-axis-scale { grid-column: 1; }
}

/* ---------------------------------------------- the rest of what we do

   The industry pages sold one service. Everything on /industries/plumbers/
   below the hero was organic search, on a site that has eighteen service
   pages — paid, campaigns, conversion, reputation, answer engines. A plumber
   reading it would reasonably conclude we do SEO and nothing else.

   Ruled columns rather than another row of cards: this page already had 83
   bordered boxes before today, and the fix for "too much text in boxes" is
   not six more boxes. */
html.gn .gn-cap-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(268px, 1fr));
  gap: 0 44px;
  margin-top: 26px;
}
html.gn .gn-cap {
  border-top: 1px solid var(--line);
  padding: 16px 0 26px;
}
html.gn .gn-cap h3 {
  font-size: 16px;
  font-weight: 600;
  letter-spacing: -.008em;
  margin: 0 0 7px;
}
html.gn .gn-cap p {
  font-size: 14.5px;
  line-height: 1.6;
  color: var(--ink2);
  margin: 0;
  max-width: none;
}
/* The service links are the point of the block — they have to look like the
   way through, not like footnotes under the paragraph. */
html.gn .gn-cap-links {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 16px;
  margin-top: 11px !important;
}
html.gn .gn-cap-links a {
  font-size: 13px;
  font-weight: 500;
  color: var(--ac);
  border-bottom: 1px solid transparent;
  transition: border-color .2s ease;
}
html.gn .gn-cap-links a:hover { border-bottom-color: var(--ac); }

html.gn .gn-cap-foot {
  margin-top: 6px;
  padding-top: 20px;
  border-top: 1px solid var(--line);
  font-size: 14.5px;
  line-height: 1.6;
  color: var(--ink2);
  max-width: 76ch;
}
/* Underlined at rest: the link blue against this grey is 1.23:1, so colour
   alone does not mark it as a link (Lighthouse link-in-text-block). */
html.gn .gn-cap-foot a { color: var(--ac); text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: .18em; }
html.gn .gn-cap-foot a:hover { text-decoration-thickness: 2px; }

/* --------------------------------------------- /results/ scrolled sideways

   At 320px the page scrolled 43px horizontally — the one failure this project
   treats as never acceptable.

   `.case-results` is `repeat(3, 1fr)`, and a `1fr` track carries an implicit
   `min-width: auto`, so it cannot shrink below its own content. The three
   tracks measured 98.95 + 52 + 47.95 = 199px plus 32px of gap inside a 214px
   box, and the surplus pushed the document wider than the viewport. The
   classic grid blowout: `1fr` does not mean "at most this share".

   minmax(0, 1fr) lets the tracks shrink, and below 420px three columns of
   figures are unreadable anyway, so they go to two. */
html.gn .case-results {
  grid-template-columns: repeat(3, minmax(0, 1fr));
  background: var(--bg2);
}
@media (max-width: 419px) {
  html.gn .case-results { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ------------------------------------------------- last of the detector list

   KICKER, one more class. The sweep that removed 195 of these matched five
   badge classes by name and missed `.badge`, which on /free-site-audit/ sits
   directly above the h1 reading "FREE SITE AUDIT TOOL • INSTANT RESULTS" —
   38 characters of tracked uppercase saying what the headline underneath it
   already says. Matched structurally this time, so a sixth class name cannot
   slip through: any badge immediately followed by a heading is a kicker. */
html.gn :is(.badge, .pill, .eyebrow):has(+ :is(h1, h2, h3)) { display: none; }

/* A source credit is a citation, not a label. Uppercase made "Unbounce Page
   Speed Report" harder to read than the statistic it supports. */
html.gn .stat-source { text-transform: none; letter-spacing: .002em; }

/* The submit button centres its label in a 46px min-height, so its computed
   vertical padding is zero. It looks right and scans as a cramped control;
   the same fix the other buttons in this file already carry. */
html.gn :is(.comp-submit-btn, .audit-submit, button[type="submit"]) { padding-block: 8px; }

/* HEADING RHYTHM on /industries/. Each trade is a header, a paragraph and a
   link, and the header carried 37px above against 113px below — so it read as
   the end of the entry before it rather than the start of its own. Hiding the
   emoji removed what little separation there was. A heading belongs to what
   follows it. */
html.gn .industry-header {
  margin-top: clamp(32px, 4vw, 52px);
  margin-bottom: 6px;
}
html.gn .industry-header h2 { margin: 0; }
html.gn .industry-header:first-child { margin-top: 0; }

/* The ROI calculator's prose headings sit inside `.content-section`, one level
   deeper than the service-page rule reaches, so they kept `margin-top: 0` —
   0px above against 12px below, which reads as belonging to the paragraph
   before rather than the section it names. */
html.gn .content-section { margin-top: clamp(36px, 4.5vw, 60px); }
html.gn .content-section:first-child { margin-top: 0; }
html.gn .content-section > h2 { margin-top: 0; margin-bottom: 10px; }

/* ------------------------------------------------ what those clicks cost you

   These pages asserted a price per click - $40 to $200 depending on the trade -
   that nothing on the site sourced, to an audience who buy those clicks and
   know the real figure. A number a prospect can disprove from their own
   dashboard costs more credibility than it buys attention, and the honest
   replacement is not a different asserted number: it is theirs.

   Two inputs and a multiply. Nothing is transmitted, so there is no claim to
   defend and no privacy surface. */
html.gn .gn-calc {
  margin-top: 26px;
  border-top: 1px solid var(--line);
  padding-top: 24px;
  max-width: 660px;
}
html.gn .gn-calc-in {
  display: grid;
  gap: 16px 26px;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
}
html.gn .gn-calc-in label {
  display: block;
  font-size: 13px;
  font-weight: 500;
  color: var(--ink2);
  letter-spacing: .002em;
}
/* The currency mark belongs inside the field, not floating beside the label,
   so the box reads as one control. */
html.gn .gn-calc-field {
  display: flex;
  align-items: center;
  gap: 2px;
}
html.gn .gn-calc-field > span {
  padding-left: 12px;
  font-size: 16px;
  color: var(--ink3);
}
html.gn .gn-calc-field > span + input { padding-left: 4px; }
/* The box is drawn by whichever element is the outermost control: the bare
   input, or the `$` wrapper when there is one. Targeting `label > *` rather
   than `input` keeps the currency-prefixed field from getting a second border
   inside the first — an `:is(input, .gn-calc-field)` here computed to (0,3,1)
   and outranked the (0,2,2) reset meant to strip it, so the money field
   rendered as a box inside a box. */
html.gn .gn-calc-in label > :is(input, .gn-calc-field) {
  margin-top: 7px;
  width: 100%;
  height: 46px;
  border: 1px solid var(--line2);
  border-radius: 10px;
  background: var(--bg);
}
html.gn .gn-calc-in input {
  padding: 0 12px;
  /* 16px: anything smaller and iOS Safari zooms the page on focus. */
  font-size: 16px;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
/* The inner input of a prefixed field is a plain text surface; the wrapper
   already carries the edge. */
html.gn .gn-calc-field > input {
  margin-top: 0;
  height: 100%;
  border: 0;
  background: none;
  border-radius: 0 10px 10px 0;
}
html.gn .gn-calc-in label > input:focus,
html.gn .gn-calc-in label > .gn-calc-field:focus-within {
  border-color: var(--ac);
  box-shadow: 0 0 0 3px var(--actint);
  outline: none;
}
html.gn .gn-calc-field > input:focus { outline: none; box-shadow: none; }

html.gn .gn-calc-out { margin: 22px 0 20px; min-height: 44px; }
html.gn .gn-calc-hint { font-size: 14px; color: var(--ink3); margin: 0; }
html.gn .gn-calc-rows { margin: 0; display: grid; gap: 0; }
html.gn .gn-calc-rows > div {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 16px;
  padding: 10px 0;
  border-top: 1px solid var(--line);
}
html.gn .gn-calc-rows dt { font-size: 14px; color: var(--ink2); margin: 0; }
html.gn .gn-calc-rows dd {
  margin: 0;
  font-family: var(--mo);
  font-size: 17px;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  white-space: nowrap;
}
/* The whole widget exists to land this one row, so it is the only thing here
   allowed to carry the accent. */
html.gn .gn-calc-rows .gn-calc-free dt { color: var(--ink); font-weight: 500; }
html.gn .gn-calc-rows .gn-calc-free dd { color: var(--ac); font-size: 19px; }
html.gn .gn-calc-note {
  margin: 12px 0 0;
  font-size: 13px;
  color: var(--ink3);
}
html.gn .gn-calc > .btn { margin-top: 4px; }
