/* ==========================================================================
   Graham Cloud Services — shared stylesheet

   Colour discipline (the one rule that holds this design together):
     --signal (teal)  = action, or a fact the visitor can verify themselves.
     --cobalt (blue)  = structure, depth, and secondary emphasis.
   Teal never appears as decoration. When it shows up, something is either
   clickable or true. That is what makes the hero proof panel read as evidence
   rather than ornament.
   ========================================================================== */

@import url("/fonts/fonts.css?v=2");

:root {
  /* Surfaces — a light, teal-accented palette. Teal (--signal) means "a fact
     you can verify yourself" (see the homepage's live proof panel) — that
     convention carried over from the site's earlier dark theme unchanged. */
  --ink: #ffffff;
  --slate: #f7fafa;
  --slate-2: #f4faf8;
  --slate-3: #eef4f2;
  --line: #e4e9ed;
  --line-lit: #cfd8dd;

  --signal: #0a7d64;
  --signal-soft: #e6f5f1;
  --signal-edge: rgba(10, 125, 100, 0.32);
  --signal-glow: rgba(10, 125, 100, 0.22);
  --signal-ink: #ffffff;

  --cobalt: #1f5fd6;
  --cobalt-soft: #e4ecfb;
  --amber: #b45309;
  --amber-soft: #faecdd;
  --danger: #c0392b;

  /* The one CTA color on the site. Deliberately not --signal (teal) or
     --amber (already means "Ads" in the services category) - a distinct
     color so every call to action reads as the one thing to click, sitewide.
     That covers the buttons and the "Get a quote" links in the services
     cards. Footer navigation and inline prose links to /contact are not
     calls to action and stay teal. */
  --cta: #cf4405;
  --cta-ink: #ffffff;
  /* Same orange, darkened for use as text on light surfaces (see .svc-cta). */
  --cta-text: #c23f04;
  --cta-glow: rgba(207, 68, 5, 0.22);

  --text: #0f1c2b;
  --text-2: #46586b;
  --muted: #66727e;

  --display: "Space Grotesk", "Segoe UI", sans-serif;
  --body: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

  --max-width: 1120px;
  --gutter: 1.5rem;
  --radius: 12px;

  /* Vertical rhythm — one scale, used everywhere. */
  --band-y: 7rem;
  --stack: 1.25rem;

  /* Motion — one curve, three speeds. Every animation on the site uses
     these; tune the whole site's feel from here. */
  --ease-out: cubic-bezier(0.2, 0.7, 0.2, 1);
  --t-fast: 0.25s;
  --t-med: 0.7s;
  --t-slow: 0.9s;
}

* {
  box-sizing: border-box;
}

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

body {
  margin: 0;
  font-family: var(--body);
  font-size: 1rem;
  color: var(--text);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  background-color: var(--ink);
}

a {
  color: var(--signal);
  text-decoration: none;
}

a:hover {
  text-decoration: underline;
}

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

:focus-visible {
  outline: 2px solid var(--signal);
  outline-offset: 3px;
  border-radius: 3px;
}

.container {
  width: 100%;
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 0 var(--gutter);
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--signal);
  color: var(--signal-ink);
  padding: 0.7rem 1.1rem;
  font-weight: 500;
  z-index: 200;
}

.skip-link:focus {
  left: 0;
  text-decoration: none;
}

/* --------------------------------------------------------------------------
   Type
   -------------------------------------------------------------------------- */

h1,
h2,
h3,
h4 {
  font-family: var(--display);
  font-weight: 700;
  letter-spacing: -0.03em;
  line-height: 1.05;
  margin: 0;
}

h1 {
  font-size: clamp(2.25rem, 4.4vw, 3.4rem);
  letter-spacing: -0.035em;
  line-height: 1.03;
}

h2 {
  font-size: clamp(1.95rem, 3.8vw, 2.85rem);
}

h3 {
  font-weight: 500;
  font-size: 1.18rem;
  letter-spacing: -0.018em;
  line-height: 1.28;
}

h4 {
  font-weight: 500;
  font-size: 1.05rem;
  letter-spacing: -0.015em;
}

p {
  margin: 0;
}

strong {
  color: var(--text);
  font-weight: 500;
}

/* A single, restrained section opener. Used sparingly — not on every band. */

.kicker {
  font-family: var(--mono);
  font-size: 0.7rem;
  font-weight: 500;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 1rem;
}

.band-head {
  max-width: 34rem;
  margin-bottom: 3.25rem;
}

.band-head .lede {
  color: var(--text-2);
  margin-top: 1.1rem;
  font-size: 1.06rem;
  line-height: 1.65;
}

.band-head--wide {
  max-width: 44rem;
}

/* Pairs a band-head with a flat-shape illustration, side by side. Started
   on /portfolio; also used on /services. Illustrations here are cards on
   a plain light background rather than the dark-navy frame the hero and
   pricing chart use, so a plain light border fits better than that frame. */
.standard-hero-layout {
  display: grid;
  grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr);
  gap: 3rem;
  align-items: center;
  margin-bottom: 3.25rem;
}

.standard-hero-layout .band-head {
  margin-bottom: 0;
}

.standard-graphic-slot {
  border-radius: 16px;
  overflow: hidden;
  border: 1px solid var(--line);
}

.standard-graphic {
  display: block;
  width: 100%;
  height: auto;
}

/* Motion inside the flat-shape illustrations, steam-style: one quiet
   moving detail per graphic, slow and low-contrast, so it never competes
   with the copy beside it. Each class exists in exactly one graphic. */
.standard-graphic .seal,
.standard-graphic .layer,
.standard-graphic .flag,
.credential-badge .shield {
  transform-box: fill-box;
}

/* /portfolio clipboard: the seal breathes. */
.standard-graphic .seal {
  transform-origin: center;
  animation: graphic-gentle-pulse 2.4s ease-in-out infinite alternate;
}

/* /services monitor: a soft highlight steps through the runbook rows. */
.standard-graphic .hl {
  opacity: 0;
  animation: graphic-hl-step 4.5s infinite;
}
.standard-graphic .hl-2 { animation-delay: 1.5s; }
.standard-graphic .hl-3 { animation-delay: 3s; }

/* /services stack: layers bob out of phase; the flag rides the top layer. */
.standard-graphic .layer,
.standard-graphic .flag {
  animation: graphic-float-bob 3.6s ease-in-out infinite alternate;
}
.standard-graphic .layer-2 { animation-delay: 0.45s; }
.standard-graphic .layer-3 { animation-delay: 0.9s; }
.standard-graphic .layer-4,
.standard-graphic .flag { animation-delay: 1.35s; }

/* /about badge: connector dashes march while the shield breathes. */
.credential-badge .link {
  animation: graphic-dash-march 1.2s linear infinite;
}
.credential-badge .shield {
  transform-origin: center;
  animation: graphic-gentle-pulse 2.4s ease-in-out infinite alternate;
}

/* /automation hero: the connectors carry dashes toward the hub and the
   watch badge breathes, the same two motions the /about badge uses. */
.hero-illustration-art .auto-flow {
  animation: graphic-dash-march 1.2s linear infinite;
}
.hero-illustration-art .auto-badge {
  transform-box: fill-box;
  transform-origin: center;
  animation: graphic-gentle-pulse 2.4s ease-in-out infinite alternate;
}

/* Illustrated cards (/, /automation): one quiet detail per tile. Badges
   breathe and anything "in flight" (an envelope, a reply bubble, a stack
   layer) bobs. Arrows stay still: a shape sliding sideways next to body
   copy pulled the eye off the text. */
.card-art .art-pulse,
.card-art .art-bob {
  transform-box: fill-box;
  transform-origin: center;
}
.card-art .art-pulse {
  animation: graphic-gentle-pulse 2.4s ease-in-out infinite alternate;
}
.card-art .art-bob {
  animation: graphic-float-bob 3.6s ease-in-out infinite alternate;
}
/* The Watch tile reuses the hero's dash-march connector. */
.card-art .auto-flow {
  animation: graphic-dash-march 1.2s linear infinite;
}

/* ------------------------------------------------------------------------
   Tap-to-expand tiles on /automation.

   Tapping a tile opens a two-line before/after under it and plays a
   five-second scenario inside the illustration: the chore done by hand
   (a cursor, and one thing going wrong, in orange), then the same chore
   running on its own (things move by themselves and the check lands).
   Scenario-only shapes carry .sc and are invisible at rest, so the tile
   looks exactly as it did before this existed until someone taps it. Under
   reduced motion nothing plays and the tile simply shows its "after" state,
   which is the resting art. The timeline is the same for every tile:

     0-44%   the cursor works (per-tile path)
     44-56%  the mistake shows (.sc-wrong)
     58-76%  things move on their own (.sc-move, per-tile --sc-dx/--sc-dy)
     60-74%  the result brightens (.sc-after)
     78-92%  the check pops (.art-pulse, .sc-pop-item)
   ------------------------------------------------------------------------ */
.card--tap {
  cursor: pointer;
}

.card-toggle {
  all: unset;
  display: inline;
  font: inherit;
  color: inherit;
  cursor: pointer;
}

.card-toggle::after {
  content: "";
  display: inline-block;
  width: 7px;
  height: 7px;
  margin-left: 0.55rem;
  vertical-align: 0.2em;
  border-right: 2px solid var(--muted);
  border-bottom: 2px solid var(--muted);
  transform: rotate(45deg);
  transition: transform var(--t-fast) var(--ease-out);
}

.card--tap.is-open .card-toggle::after {
  transform: rotate(225deg);
  vertical-align: -0.05em;
}

.card-toggle:focus-visible {
  outline: 2px solid var(--signal);
  outline-offset: 4px;
  border-radius: 4px;
}

.card-more {
  margin-top: 0.9rem;
  padding-top: 0.9rem;
  border-top: 1px solid var(--line);
  font-size: 0.92rem;
  line-height: 1.55;
  color: var(--text-2);
}

.card-more strong {
  color: var(--text);
}

.card-more .arrow-link {
  display: inline-block;
  margin-top: 0.55rem;
}

.card-art .sc {
  opacity: 0;
}

.card-art .sc-cursor,
.card-art .sc-move,
.card-art .sc-pop-item {
  transform-box: fill-box;
  transform-origin: center;
}

/* The cursor is positioned by its keyframes alone, so its own origin is
   the SVG's, not its bounding box. */
.card-art .sc-cursor {
  transform-box: view-box;
  transform-origin: 0 0;
}

/* The shorthand would reset animation-name to none, so the per-tile
   name rides in through a custom property set on the tile. */
.card--tap.is-open .card-art .sc-cursor { animation: var(--sc-cursor) 5s ease-in-out forwards; }
.card--tap.is-open .card-art .sc-wrong  { animation: sc-wrong 5s forwards; }
.card--tap.is-open .card-art .sc-move   { animation: sc-move 5s ease-in-out forwards; }
.card--tap.is-open .card-art .sc-after  { animation: sc-after 5s forwards; }
.card--tap.is-open .card-art .art-pulse,
.card--tap.is-open .card-art .sc-pop-item {
  animation: sc-pop 5s forwards;
  animation-delay: calc(var(--sc-i, 0) * 0.25s);
}

[data-task="sync"]      { --sc-cursor: sc-cursor-sync; }
[data-task="documents"] { --sc-cursor: sc-cursor-documents; }
[data-task="reconcile"] { --sc-cursor: sc-cursor-reconcile; }
[data-task="followups"] { --sc-cursor: sc-cursor-followups; }
[data-task="invoices"]  { --sc-cursor: sc-cursor-invoices; }
[data-task="calls"]     { --sc-cursor: sc-cursor-calls; }

/* The calls tile has no cursor; its "cursor" slot is a ring around the
   phone that scales from its own center. */
.card--tap[data-task="calls"] .card-art .sc-cursor {
  transform-box: fill-box;
  transform-origin: center;
}

@keyframes sc-wrong {
  0%, 42%  { opacity: 0; }
  47%, 53% { opacity: 1; }
  58%, 100% { opacity: 0; }
}

@keyframes sc-move {
  0%, 56%   { opacity: 0; transform: translate(0, 0); }
  60%       { opacity: 1; }
  74%       { opacity: 1; transform: translate(var(--sc-dx, 0), var(--sc-dy, 0)); }
  78%, 100% { opacity: 0; transform: translate(var(--sc-dx, 0), var(--sc-dy, 0)); }
}

@keyframes sc-after {
  0%, 58%   { opacity: 0.2; }
  72%, 100% { opacity: 1; }
}

@keyframes sc-pop {
  0%, 78%   { transform: scale(0); }
  86%       { transform: scale(1.15); }
  92%, 100% { transform: scale(1); }
}

/* Three trips, the third to the wrong slot. */
@keyframes sc-cursor-sync {
  0%   { opacity: 0; transform: translate(60px, 46px); }
  4%   { opacity: 1; }
  12%  { transform: translate(216px, 46px); }
  20%  { transform: translate(60px, 64px); }
  28%  { transform: translate(216px, 64px); }
  36%  { transform: translate(60px, 82px); }
  44%  { transform: translate(216px, 100px); }
  50%  { opacity: 1; transform: translate(216px, 100px); }
  54%, 100% { opacity: 0; transform: translate(216px, 100px); }
}

/* Typing lines across; the last one gets retyped. */
@keyframes sc-cursor-documents {
  0%   { opacity: 0; transform: translate(55px, 60px); }
  4%   { opacity: 1; }
  12%  { transform: translate(240px, 44px); }
  20%  { transform: translate(55px, 74px); }
  28%  { transform: translate(240px, 61px); }
  36%  { transform: translate(55px, 88px); }
  44%  { transform: translate(240px, 44px); }
  50%  { opacity: 1; transform: translate(240px, 44px); }
  54%, 100% { opacity: 0; transform: translate(240px, 44px); }
}

/* Draws two matches, then hovers over the one with no partner. */
@keyframes sc-cursor-reconcile {
  0%   { opacity: 0; transform: translate(100px, 40px); }
  4%   { opacity: 1; }
  12%  { transform: translate(200px, 40px); }
  20%  { transform: translate(100px, 60px); }
  28%  { transform: translate(200px, 60px); }
  36%  { transform: translate(100px, 80px); }
  40%  { transform: translate(150px, 80px); }
  44%  { transform: translate(168px, 84px); }
  50%  { opacity: 1; transform: translate(162px, 82px); }
  54%, 100% { opacity: 0; transform: translate(162px, 82px); }
}

/* The days tick past, and the marked one goes by with nothing sent. */
@keyframes sc-cursor-followups {
  0%   { opacity: 0; transform: translate(60px, 62px); }
  4%   { opacity: 1; }
  8%   { transform: translate(60px, 62px); }
  14%  { transform: translate(80px, 62px); }
  20%  { transform: translate(100px, 62px); }
  26%  { transform: translate(120px, 62px); }
  32%  { transform: translate(60px, 80px); }
  38%  { transform: translate(80px, 80px); }
  44%  { transform: translate(100px, 80px); }
  50%  { opacity: 1; transform: translate(120px, 80px); }
  54%, 100% { opacity: 0; transform: translate(120px, 80px); }
}

/* Opens the invoice, drags a reminder out by hand, too late. */
@keyframes sc-cursor-invoices {
  0%   { opacity: 0; transform: translate(120px, 50px); }
  4%   { opacity: 1; }
  12%  { transform: translate(120px, 104px); }
  20%  { transform: translate(150px, 104px); }
  28%  { transform: translate(172px, 82px); }
  36%  { transform: translate(120px, 50px); }
  44%  { transform: translate(160px, 106px); }
  50%  { opacity: 1; transform: translate(160px, 106px); }
  54%, 100% { opacity: 0; transform: translate(160px, 106px); }
}

/* No cursor here: the phone rings three times and nobody is there. */
@keyframes sc-cursor-calls {
  0%, 2%   { opacity: 0; transform: scale(0.6); }
  6%       { opacity: 0.85; transform: scale(0.8); }
  14%      { opacity: 0; transform: scale(1.3); }
  16%      { opacity: 0; transform: scale(0.6); }
  20%      { opacity: 0.85; transform: scale(0.8); }
  28%      { opacity: 0; transform: scale(1.3); }
  30%      { opacity: 0; transform: scale(0.6); }
  34%      { opacity: 0.85; transform: scale(0.8); }
  42%, 100% { opacity: 0; transform: scale(1.3); }
}
.card-art .art-bob-2 { animation-delay: 0.45s; }
.card-art .art-bob-3 { animation-delay: 0.9s; }
/* /automation/never-miss-a-call hero: the three cards drift at their own
   rhythms, the same float the After card's bubble uses. */
.hero-illustration-art .art-drift {
  transform-box: fill-box;
  transform-origin: center;
  animation: graphic-float-bob 3.6s ease-in-out infinite alternate;
}
.hero-illustration-art .art-drift-2 { animation-delay: -1.2s; }
.hero-illustration-art .art-drift-3 { animation-delay: -2.4s; }

@keyframes graphic-gentle-pulse {
  from { transform: scale(1); }
  to   { transform: scale(1.06); }
}

@keyframes graphic-hl-step {
  0%   { opacity: 0; }
  8%   { opacity: 0.09; }
  26%  { opacity: 0.09; }
  34%  { opacity: 0; }
  100% { opacity: 0; }
}

@keyframes graphic-float-bob {
  from { transform: translateY(0); }
  to   { transform: translateY(-4px); }
}

@keyframes graphic-dash-march {
  from { stroke-dashoffset: 14; }
  to   { stroke-dashoffset: 0; }
}

@media (max-width: 860px) {
  .standard-hero-layout {
    grid-template-columns: 1fr;
    gap: 1.5rem;
  }

  .standard-graphic-slot {
    order: -1;
  }
}

/* Heading left, supporting text right. Used on alternate bands so the page
   doesn't settle into one repeated left-aligned rhythm. */
.band-head--split {
  max-width: none;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1rem 4rem;
  align-items: end;
}

.band-head--split .kicker {
  grid-column: 1;
  margin-bottom: 0.5rem;
}

.band-head--split h2 {
  grid-column: 1;
  max-width: 15ch;
}

.band-head--split .lede {
  grid-column: 2;
  margin-top: 0;
  max-width: 32rem;
}

/* --------------------------------------------------------------------------
   Try-it card (never-miss-a-call): the demo as a solid signal-green card,
   copy and numbered steps on the left, the phone on the right.
   -------------------------------------------------------------------------- */

.try-card {
  background: linear-gradient(180deg, #0f8a6f 0%, var(--signal) 100%);
  color: #ffffff;
  border-radius: var(--radius);
  padding: 2.4rem 1.6rem 2.2rem;
  display: grid;
  gap: 2rem;
  box-shadow: 0 18px 48px var(--signal-glow);
}

.try-card h2 {
  color: #ffffff;
  margin: 0 0 1.4rem;
  max-width: 14ch;
}

.try-kicker {
  color: rgba(255, 255, 255, 0.72);
}

.try-steps {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0.9rem;
  font-size: 1.05rem;
  line-height: 1.5;
}

.try-steps li {
  display: flex;
  align-items: flex-start;
  gap: 0.85rem;
}

.try-n {
  flex: none;
  width: 1.9rem;
  height: 1.9rem;
  border-radius: 50%;
  background: #ffffff;
  color: var(--signal);
  font-family: var(--display);
  font-weight: 700;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin-top: 0.05rem;
}

.try-number {
  display: inline-block;
  background: #ffffff;
  color: var(--signal);
  font-family: var(--display);
  font-weight: 700;
  font-size: 1.25rem;
  padding: 0.25rem 0.75rem;
  border-radius: 9px;
  text-decoration: none;
  line-height: 1.3;
}

.try-number:hover,
.try-number:focus-visible {
  background: var(--slate-3);
  color: var(--signal);
}

.try-art {
  width: min(260px, 70%);
  margin: 0 auto;
  display: block;
}

/* One five second loop, told in order: ring (0 to 21%), miss (22 to 30%),
   text pops in (36 to 52%), everything holds, the text fades and it
   starts over. transform-box keeps each origin on the shape itself. */
.try-art .try-ring,
.try-art .try-miss,
.try-art .try-text {
  transform-box: fill-box;
}

.try-art .try-ring {
  transform-origin: 50% 95%;
  animation: try-ring 5s ease-in-out infinite;
}

.try-art .try-miss {
  transform-origin: center;
  animation: try-miss 5s ease-out infinite;
}

.try-art .try-text {
  transform-origin: 20% 90%;
  animation: try-text 5s cubic-bezier(0.34, 1.56, 0.64, 1) infinite;
}

@keyframes try-ring {
  0% { transform: rotate(0); }
  3% { transform: rotate(-4deg); }
  6% { transform: rotate(4deg); }
  9% { transform: rotate(-4deg); }
  12% { transform: rotate(4deg); }
  15% { transform: rotate(-3deg); }
  18% { transform: rotate(3deg); }
  21%, 100% { transform: rotate(0); }
}

@keyframes try-miss {
  0%, 22% { transform: scale(0); }
  30%, 100% { transform: scale(1); }
}

@keyframes try-text {
  0%, 36% { transform: scale(0); opacity: 0; }
  52%, 90% { transform: scale(1); opacity: 1; }
  100% { transform: scale(1); opacity: 0; }
}

@media (min-width: 820px) {
  .try-card {
    grid-template-columns: minmax(0, 1.25fr) minmax(0, 0.75fr);
    align-items: center;
    padding: 3rem 3rem 2.8rem;
    gap: 3rem;
  }
  .try-art {
    width: 100%;
    max-width: 300px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .try-art .try-ring,
  .try-art .try-miss,
  .try-art .try-text {
    animation: none;
  }
}

/* --------------------------------------------------------------------------
   Header / nav
   -------------------------------------------------------------------------- */

.site-header {
  background: rgba(255, 255, 255, 0.85);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--line);
  position: sticky;
  top: 0;
  z-index: 100;
}

.nav-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.95rem var(--gutter);
  max-width: var(--max-width);
  margin: 0 auto;
}

.brand {
  font-family: var(--display);
  font-size: 1rem;
  font-weight: 700;
  letter-spacing: -0.025em;
  color: var(--text);
  display: flex;
  align-items: center;
  /* Tighter than it looks: logo-mark.png is a square canvas and the hexagon
     only fills 87% of its width, so the artwork carries ~1.5px of its own
     side margin at this size. 0.45rem lands the optical gap near 9px. */
  gap: 0.45rem;
  flex-shrink: 0;
  transition: color 0.15s ease;
}

.brand:hover {
  text-decoration: none;
  color: var(--signal);
}

/* The logo replaces what used to be a teal dot here. That dot was the one
   place teal appeared as pure decoration, which the colour rule at the top of
   this file forbids — so the real mark both brands the header and settles that
   inconsistency. Decorative, hence a background rather than an <img>: the
   adjacent text already names the company for screen readers. */
/* logo-mark.png is load-bearing outside this repo: the Auth0 login screen is
   pointed at https://grahamcloud.io/images/logo-mark.png from the Auth0
   dashboard, which nothing here can see. Deleting or renaming it puts a broken
   image on the first screen every customer meets. Registered in
   tools/external-refs/check.mjs; run that before pruning images/. */
.brand-mark {
  width: 24px;
  height: 24px;
  background: url("/images/logo-mark.png") center / contain no-repeat;
  flex-shrink: 0;
}

.nav-toggle {
  display: none;
  background: none;
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 0.45rem 0.6rem;
  cursor: pointer;
}

.nav-toggle-bar {
  display: block;
  width: 18px;
  height: 1.5px;
  background: var(--text);
  margin: 4px 0;
}

.nav-links {
  display: flex;
  align-items: center;
  gap: 1.9rem;
  list-style: none;
  margin: 0;
  padding: 0;
}

/* :not(.btn) matters here: .nav-cta and .nav-login wrap a .btn-primary/
   .btn-outline link inside this same .nav-links list. Without the
   exclusion, ".nav-links a" (0,1,1) beats ".btn-primary" (0,1,0) on
   specificity alone, overriding the button's own white/dark text with
   this muted nav-link gray — on the teal primary button that reads as
   washed-out, low-contrast text. */
.nav-links a:not(.btn) {
  color: var(--muted);
  font-size: 0.89rem;
  font-weight: 500;
  transition: color 0.15s ease;
  position: relative;
}

.nav-links a:not(.btn):hover,
.nav-links a.active {
  color: var(--text);
  text-decoration: none;
}

/* The "Design your site" wizard is in active development; the pill says so
   everywhere the nav does. */
.nav-beta {
  display: inline-block;
  margin-left: 2px;
  padding: 1px 7px;
  border: 1px solid var(--signal);
  border-radius: 100px;
  color: var(--signal);
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  vertical-align: 2px;
}

.nav-links a.active {
  color: var(--signal);
}

.nav-links a::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -4px;
  height: 2px;
  background: var(--signal);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform var(--t-fast) var(--ease-out);
}

.nav-links a:hover::after,
.nav-links a:focus-visible::after,
.nav-links a.active::after {
  transform: scaleX(1);
}

/* Automation has a drop-down: the link itself still goes to /automation,
   and hovering or tabbing into the item reveals the package pages under
   it. CSS only: :hover for the mouse, :focus-within for the keyboard. On
   the mobile sheet the same list is simply always open, indented. */
.nav-has-menu {
  position: relative;
  display: flex;
  align-items: center;
  gap: 0.35rem;
}

.nav-caret {
  width: 6px;
  height: 6px;
  border-right: 1.5px solid var(--muted);
  border-bottom: 1.5px solid var(--muted);
  transform: rotate(45deg) translateY(-2px);
  transition: transform var(--t-fast) var(--ease-out);
}

.nav-has-menu:hover .nav-caret,
.nav-has-menu:focus-within .nav-caret {
  transform: rotate(225deg) translateY(-1px);
}

/* An invisible bridge under the item, so the pointer can cross the
   header's bottom padding on its way to the menu without closing it. */
.nav-has-menu::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 100%;
  height: 1.1rem;
}

.nav-menu {
  position: absolute;
  top: calc(100% + 0.9rem);
  left: -0.85rem;
  min-width: 15.5rem;
  margin: 0;
  padding: 0.45rem 0;
  list-style: none;
  background: var(--ink);
  border: 1px solid var(--line);
  border-radius: 10px;
  box-shadow: 0 18px 40px -18px rgba(15, 28, 43, 0.3);
  opacity: 0;
  visibility: hidden;
  transform: translateY(-4px);
  transition: opacity var(--t-fast) var(--ease-out),
    transform var(--t-fast) var(--ease-out),
    visibility 0s linear var(--t-fast);
  z-index: 20;
}

.nav-has-menu:hover .nav-menu,
.nav-has-menu:focus-within .nav-menu {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
  transition-delay: 0s;
}

.nav-menu li {
  border: 0;
}

.nav-links .nav-menu a {
  display: block;
  padding: 0.5rem 0.95rem;
  white-space: nowrap;
  font-size: 0.86rem;
}

.nav-menu a::after {
  display: none;
}

/* The mobile sheet and any button-styled nav items don't take the underline. */
.nav-cta a::after {
  display: none;
}

@media (max-width: 1040px) {
  .nav-links a::after {
    display: none;
  }

  .nav-has-menu {
    display: block;
  }

  .nav-caret,
  .nav-has-menu::after {
    display: none;
  }

  .nav-menu {
    position: static;
    min-width: 0;
    padding: 0 0 0.35rem;
    border: 0;
    border-radius: 0;
    box-shadow: none;
    opacity: 1;
    visibility: visible;
    transform: none;
    transition: none;
  }

  .nav-links .nav-menu a {
    padding: 0.55rem var(--gutter) 0.55rem calc(var(--gutter) + 1.1rem);
    white-space: normal;
  }
}

/* "Manage your account" is a returning-customer utility, not a conversion
   path, and it used to be a .btn-outline sitting immediately left of the
   primary CTA. Two buttons in the header's highest-value slot split the
   click that section exists to earn, so this is now a plain nav link with a
   hairline separating it from the pages above it. Still one click away for
   the customers who want it, no longer competing for the prospects who
   don't. Demoted in the markup on every page rather than overridden here,
   so the class list matches what the element actually is. */
.nav-login {
  margin-left: 0.6rem;
  padding-left: 1.15rem;
  border-left: 1px solid var(--line);
}

.nav-cta {
  margin-left: 0.4rem;
}

/* --------------------------------------------------------------------------
   Buttons
   -------------------------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  padding: 0.8rem 1.5rem;
  border-radius: 9px;
  font-family: var(--body);
  font-weight: 500;
  font-size: 0.92rem;
  line-height: 1.2;
  cursor: pointer;
  border: 1px solid transparent;
  transition: background var(--t-fast) var(--ease-out),
    border-color var(--t-fast) var(--ease-out),
    color var(--t-fast) var(--ease-out),
    transform var(--t-fast) var(--ease-out),
    box-shadow var(--t-fast) var(--ease-out);
}

.btn:hover {
  text-decoration: none;
}

.btn-primary {
  background: var(--cta);
  color: var(--cta-ink);
}

.btn-primary:hover {
  background: #ad3804;
  transform: translateY(-2px) scale(1.03);
  box-shadow: 0 8px 24px var(--cta-glow);
}

.btn-outline {
  background: transparent;
  color: var(--text);
  border-color: var(--line-lit);
}

.btn-outline:hover {
  border-color: var(--signal-edge);
  color: var(--signal);
  background: var(--signal-soft);
  transform: translateY(-1px);
}

/* "You are already here." Used by the portal for the same "Manage your
   account" button that is .btn-outline on the marketing site: solid instead of
   outlined, so the nav shows which of the two sites you are standing in.

   Solid teal rather than solid --cta: orange means "the one thing to click"
   sitewide, and a button describing where you already are is the opposite of
   that. Teal is the site's marker for a true, verifiable state, which is
   exactly what this is. Deliberately no hover lift or colour change either,
   because the destination is the current page. */
.btn-current {
  background: var(--signal);
  color: var(--signal-ink);
  border-color: var(--signal);
  cursor: default;
}

.btn-current:hover {
  background: var(--signal);
  color: var(--signal-ink);
}

.btn-small {
  padding: 0.55rem 1.05rem;
  font-size: 0.85rem;
}

.btn[disabled],
.btn[aria-disabled="true"] {
  opacity: 0.55;
  pointer-events: none;
}

/* Working state for a button that has fired a request — pressing Subscribe
   goes to our Lambda and on to Zoho, which is a real wait (roughly 1-3s on a
   cold start), and until now the only feedback was the label changing.

   This rule MUST stay below the [aria-disabled] rule above: both are one class
   plus one attribute, so they have identical specificity and source order is
   the only thing deciding the winner. The 0.55 dimming there reads as
   "switched off"; a button that is busy is working, so it stays near full
   strength and takes a progress cursor.

   The ring is a pseudo-element so no JS has to build or tear down DOM, and it
   inherits currentColor, so it is correct on the teal primary and any other
   button without a second rule. .btn is already inline-flex with a 0.5rem gap,
   so the ring spaces itself against the label. */
.btn[aria-busy="true"] {
  opacity: 0.9;
  cursor: progress;
}

.btn[aria-busy="true"]::before {
  content: "";
  width: 0.95em;
  height: 0.95em;
  border-radius: 50%;
  border: 2px solid currentColor;
  /* The missing quarter is what makes the rotation legible. */
  border-top-color: transparent;
  flex: none;
  animation: btn-spin 0.7s linear infinite;
}

@keyframes btn-spin {
  to {
    transform: rotate(1turn);
  }
}

/* Text link with a moving arrow — used to leave a band. */

.arrow-link {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  font-weight: 500;
  font-size: 0.94rem;
}

.arrow-link::after {
  content: "→";
  transition: transform 0.2s ease;
}

.arrow-link:hover {
  text-decoration: none;
}

.arrow-link:hover::after {
  transform: translateX(4px);
}

/* --------------------------------------------------------------------------
   Bands (page sections)

   Class-based rather than element-based, so modifiers never lose a
   specificity fight with the base rule.
   -------------------------------------------------------------------------- */

.band {
  padding: var(--band-y) 0;
  position: relative;
}

.band--tight {
  padding: 4.5rem 0;
}

.band--edge {
  border-top: 1px solid var(--line);
}

/* Any band meant to be a link target (e.g. /portfolio#live-examples from the
   nav) needs this so the sticky header doesn't cover its heading once
   scrolled to. */
.band[id] {
  scroll-margin-top: 84px;
}

.band--raised {
  background: var(--slate);
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}

/* --------------------------------------------------------------------------
   Hero — asymmetric. Claim on the left, evidence on the right.
   -------------------------------------------------------------------------- */

.hero {
  position: relative;
  overflow: hidden;
  padding: 5.5rem 0 4.5rem;
  border-bottom: 1px solid var(--line);
  background:
    radial-gradient(80% 60% at 78% 8%, rgba(31, 95, 214, 0.07), transparent 60%),
    radial-gradient(70% 55% at 12% 30%, rgba(10, 125, 100, 0.06), transparent 62%),
    var(--ink);
}

/* Faint engineering grid — structure, not sparkle. */
.hero::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image:
    linear-gradient(to right, rgba(15, 28, 43, 0.035) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(15, 28, 43, 0.035) 1px, transparent 1px);
  background-size: 64px 64px;
  mask-image: radial-gradient(120% 90% at 50% 0%, #000 10%, transparent 72%);
  -webkit-mask-image: radial-gradient(120% 90% at 50% 0%, #000 10%, transparent 72%);
  pointer-events: none;
}

.hero .container {
  position: relative;
  z-index: 1;
}

.hero-grid {
  display: grid;
  grid-template-columns: 1.06fr 0.94fr;
  gap: 4rem;
  align-items: center;
}

.hero-claim h1 {
  margin-bottom: 1.5rem;
}

.hero-claim h1 em {
  font-style: normal;
  color: var(--signal);
}

.hero-sub {
  font-size: 1.12rem;
  color: var(--text-2);
  max-width: 30rem;
  margin-bottom: 2rem;
  line-height: 1.62;
}

.hero-actions {
  display: flex;
  gap: 0.75rem;
  flex-wrap: wrap;
  margin-bottom: 2.25rem;
}

.hero-note {
  font-size: 0.88rem;
  color: var(--muted);
  display: flex;
  align-items: center;
  gap: 0.55rem;
}

/* A second hero-note (the click-to-call on /durham) would otherwise sit flush
   against the first, since .hero-note carries no bottom margin of its own. */
.hero-note + .hero-note {
  margin-top: 0.55rem;
}

.hero-note .dot {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--signal);
  flex-shrink: 0;
}

/* Faint scroll cue — a nudge, not a call to action. Desktop-only: the
   .hero-grid layout below md stacks the claim above the proof panel, which
   would push a hero-bottom-pinned cue well below the first viewport, so it's
   hidden under that breakpoint instead of chasing a moving target. */
.hero-scroll-cue {
  position: absolute;
  left: 50%;
  bottom: 1.5rem;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2.25rem;
  height: 2.25rem;
  border-radius: 50%;
  background: var(--slate-3);
  color: var(--muted);
  opacity: 0.6;
  transform: translateX(-50%);
  animation: hero-scroll-cue-bob 2.2s var(--ease-out) infinite;
  transition: opacity var(--t-fast) var(--ease-out), color var(--t-fast) var(--ease-out),
    background var(--t-fast) var(--ease-out);
}

.hero-scroll-cue:hover,
.hero-scroll-cue:focus-visible {
  opacity: 1;
  color: var(--signal);
  background: var(--signal-soft);
}

@keyframes hero-scroll-cue-bob {
  0%, 100% {
    transform: translate(-50%, 0);
  }
  50% {
    transform: translate(-50%, 6px);
  }
}

@media (prefers-reduced-motion: reduce) {
  .hero-scroll-cue {
    animation: none;
  }
}

/* Claim on the left, evidence on the right. Single column below 1024px,
   where the copy needs the full measure and the stats read fine stacked
   underneath it. The stats column is nudged down so the first big number
   lines up against the lede rather than the kicker. */
.urgency-grid {
  display: grid;
  gap: 3rem;
}

@media (min-width: 1024px) {
  .urgency-grid {
    grid-template-columns: minmax(0, 1fr) minmax(0, 0.85fr);
    gap: 4rem;
    align-items: start;
  }

  .urgency-figures {
    padding-top: 4.5rem;
  }
}

/* The band-head's own bottom margin is tuned for a full-width header with
   body copy beneath it. Here the button follows directly, so the gap closes
   to something a button-sized element deserves. */
.urgency-copy .band-head {
  margin-bottom: 2.25rem;
}

/* The "why now" stats section. Numbers stay --signal (teal, "a fact you
   can verify yourself") since each one carries a visible source citation
   right below it - these genuinely are verifiable facts. Orange (--cta)
   is reserved for the accent bar and the button, per the site's rule that
   teal never appears as pure decoration. */
.urgency-stat {
  border-left: 3px solid var(--cta);
  padding: 0.25rem 0 0.25rem 1.1rem;
  margin-bottom: 1.5rem;
}

.urgency-num {
  display: block;
  font-family: var(--display);
  font-size: 2.6rem;
  font-weight: 700;
  letter-spacing: -0.025em;
  color: var(--signal);
  line-height: 1;
}

.urgency-label {
  display: block;
  color: var(--text-2);
  font-size: 0.98rem;
  margin-top: 0.3rem;
  max-width: 26rem;
}

.urgency-source {
  display: block;
  color: var(--muted);
  font-size: 0.72rem;
  margin-top: 0.3rem;
}

.urgency-divider {
  border: none;
  border-top: 1px solid var(--line);
  margin: 2.5rem 0;
}

.urgency-followup {
  font-family: var(--display);
  font-weight: 600;
  font-size: 1.15rem;
  color: var(--text);
}

.urgency-promise {
  font-size: 0.88rem;
  color: var(--muted);
  margin-top: 0.9rem;
}

/* --------------------------------------------------------------------------
   SIGNATURE: the live proof panel.

   Every number in here is measured in the visitor's own browser from the
   Performance API on page load. Nothing is hardcoded, which is the whole
   point — the site is the portfolio.
   -------------------------------------------------------------------------- */

.proof {
  background: var(--slate-2);
  border: 1px solid var(--line);
  border-radius: 14px;
  overflow: hidden;
  box-shadow: 0 24px 60px -30px rgba(15, 28, 43, 0.18);
}

.proof-head {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.85rem 1.15rem;
  border-bottom: 1px solid var(--line);
  background: rgba(15, 28, 43, 0.02);
}

.proof-live {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  font-family: var(--mono);
  font-size: 0.66rem;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--signal);
  /* fill, not just color: this class is also used on the hero
     illustration's SVG <text>, where color has no effect on glyph
     paint - fill does. Harmless on the plain-HTML usage. */
  fill: var(--signal);
}

.proof-pulse {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--signal);
  box-shadow: 0 0 0 0 var(--signal-edge);
  animation: pulse 2.4s ease-out infinite;
}

@keyframes pulse {
  0% { box-shadow: 0 0 0 0 var(--signal-edge); }
  70% { box-shadow: 0 0 0 7px rgba(10, 125, 100, 0); }
  100% { box-shadow: 0 0 0 0 rgba(10, 125, 100, 0); }
}

/* SVG equivalent of .proof-pulse's box-shadow ring - box-shadow does not
   apply to SVG shapes, so this animates an actual ring circle's scale and
   stroke-opacity instead. Used by the hero illustration's live indicator. */
.proof-pulse-ring {
  stroke-width: 1.5;
  transform-box: fill-box;
  transform-origin: center;
  animation: proof-pulse-ring 2.4s ease-out infinite;
}

@keyframes proof-pulse-ring {
  0% { stroke-opacity: 0.55; transform: scale(0.4); }
  70% { stroke-opacity: 0; transform: scale(1.7); }
  100% { stroke-opacity: 0; transform: scale(1.7); }
}

.proof-title {
  font-family: var(--mono);
  font-size: 0.72rem;
  color: var(--muted);
  margin-left: auto;
  letter-spacing: 0.02em;
}

.proof-rows {
  padding: 0.35rem 0;
}

.proof-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.72rem 1.15rem;
}

.proof-row + .proof-row {
  border-top: 1px solid var(--line);
}

.proof-label {
  font-size: 0.87rem;
  color: var(--text-2);
  fill: var(--text-2);
}

.proof-value {
  font-family: var(--mono);
  font-size: 1.28rem;
  font-weight: 500;
  color: var(--signal);
  fill: var(--signal);
  letter-spacing: -0.02em;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

.proof-value .unit {
  font-size: 0.74rem;
  color: var(--muted);
  fill: var(--muted);
  margin-left: 0.18rem;
  letter-spacing: 0.04em;
}

/* Measuring state, before the Performance API reports. */
.proof-value[data-pending="true"] {
  color: var(--muted);
  fill: var(--muted);
}

/* Shown only by the <noscript> block on the homepage. */
.proof-noscript {
  display: none;
  padding: 1.1rem 1.15rem;
  font-size: 0.9rem;
  color: var(--text-2);
  line-height: 1.6;
}

.proof-foot {
  border-top: 1px solid var(--line);
  padding: 0.95rem 1.15rem 1.05rem;
  background: var(--slate-3);
}

.proof-stack {
  font-family: var(--mono);
  font-size: 0.7rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-2);
  fill: var(--text-2);
  margin-bottom: 0.45rem;
}

.proof-check {
  font-size: 0.79rem;
  color: var(--text-2);
  line-height: 1.5;
}

/* Dark navy/blue card behind the illustrated scene - a deliberate one-off
   accent, not a site-wide dark mode (nothing else on this light-only site
   has a dark variant). Scoped to this modifier rather than changed on
   .proof itself, since durham.html's plain metrics panel still uses .proof
   alone and must stay on its light background. The monitor's own screen
   stays light regardless - that fill lives on the SVG rect, not here. */
.hero-illustration-frame {
  background: #101c2a;
  border-color: #223349;
}

.hero-illustration-frame .proof-noscript {
  color: #aab8c6;
}

/* The hero illustration replaces the old row-based .proof-rows content on
   the homepage only - it fills the card exactly like the rows used to.
   font-family here (not just on the individual proof-* classes) covers the
   illustration's plain, unlabelled decorative shapes needing no font at
   all, and keeps one place to change it instead of two. */
.hero-illustration-art {
  display: block;
  width: 100%;
  height: auto;
  font-family: var(--mono);
}

.steam {
  transform-box: fill-box;
  transform-origin: center bottom;
  animation: steam-rise 2.4s ease-in-out infinite;
}
.steam-2 { animation-delay: 0.8s; }

@keyframes steam-rise {
  0%   { transform: translateY(0) scaleY(1); opacity: 0; }
  20%  { opacity: 0.85; }
  75%  { opacity: 0.2; }
  100% { transform: translateY(-15px) scaleY(1.25); opacity: 0; }
}

/* --------------------------------------------------------------------------
   Credentials bar — full-bleed done properly.

   The old version had a full-width background with 1140px-wide dividers,
   which left the rules ending in mid-air. Here the background and the
   divided track are the same element, so the seams always line up.
   -------------------------------------------------------------------------- */

.creds {
  border-bottom: 1px solid var(--line);
  background: var(--slate);
}

.creds-inner {
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 0 var(--gutter);
  display: grid;
  grid-template-columns: repeat(3, 1fr);
}

.cred {
  padding: 1.5rem 1.5rem 1.5rem 0;
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
}

.cred + .cred {
  padding-left: 1.75rem;
  border-left: 1px solid var(--line);
}

.cred-value {
  font-family: var(--display);
  font-size: 1.32rem;
  font-weight: 700;
  letter-spacing: -0.025em;
  color: var(--text);
}

/* --cta-text over --cta: same reasoning as .svc-cta above - this sits on the
   creds bar's near-white --slate background, where the base --cta orange
   fails AA contrast for small text. */
.cred-urgent {
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--cta-text);
}

/* margin-top: auto pins every label to the bottom of its column. "AWS
   Certified Solutions Architect" is long enough to wrap onto a second line
   at most desktop widths while its two neighbours stay on one, which pushed
   its label a line lower than theirs and broke the row's shared baseline.
   The grid already stretches all three columns to equal height, so pushing
   the labels down is enough to realign them without capping the value's
   length or hyphenating it. */
.cred-label {
  font-size: 0.85rem;
  color: var(--muted);
  line-height: 1.45;
  margin-top: auto;
}

/* --------------------------------------------------------------------------
   Benefits cycler — auto-advancing icon rail + animated stage. Ships fully
   readable without JS: panel 0 shows permanently (no `hidden` attribute in
   the HTML), and .cycler-rail/.cycler-playtoggle stay hidden since their
   buttons would be dead weight without initBenefitsCycler() to drive them
   — same reasoning as this file's existing .demo-carousel-nav pattern.
   -------------------------------------------------------------------------- */

/* Sits the services cycler on a visibly separate gray tray so the card
   reads as raised, not just outlined. */
.cycler-tray {
  background: var(--slate-3);
}

.cycler {
  display: grid;
  grid-template-columns: 220px 1fr;
  background: var(--slate-2);
  border-radius: var(--radius);
  overflow: hidden;
  box-shadow: 0 24px 60px -30px rgba(15, 28, 43, 0.28), 0 4px 12px rgba(15, 28, 43, 0.08);
}

.cycler-rail {
  display: none;
  flex-direction: column;
  border-right: 1px solid var(--line);
  background: var(--slate);
}

.js .cycler-rail {
  display: flex;
}

.cycler-tab {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 1.1rem 1rem;
  border: none;
  border-bottom: 1px solid var(--line);
  background: transparent;
  color: var(--text-2);
  font-family: var(--body);
  font-size: 0.85rem;
  font-weight: 500;
  text-align: left;
  cursor: pointer;
  position: relative;
  transition: background var(--t-fast) var(--ease-out),
    color var(--t-fast) var(--ease-out);
}

.cycler-tab:hover {
  color: var(--text);
}

.cycler-tab.is-active {
  background: var(--slate-3);
  color: var(--text);
}

.cycler-tab.is-active::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 3px;
  background: var(--signal);
}

.cycler-tab-icon {
  width: 34px;
  height: 34px;
  border-radius: 9px;
  flex-shrink: 0;
  display: grid;
  place-items: center;
  background: var(--slate-3);
  transition: background var(--t-fast) var(--ease-out),
    box-shadow var(--t-fast) var(--ease-out);
}

.cycler-tab.is-active .cycler-tab-icon {
  background: var(--signal-soft);
  box-shadow: 0 0 0 1px var(--signal-edge);
}

.cycler-tab-icon svg {
  width: 17px;
  height: 17px;
  stroke: var(--text-2);
  fill: none;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.cycler-tab.is-active .cycler-tab-icon svg {
  stroke: var(--signal);
}

.cycler-playtoggle {
  display: none;
  align-items: center;
  justify-content: center;
  margin: 0.75rem auto 1rem;
  width: 38px;
  height: 38px;
  border-radius: 9px;
  border: 1px solid var(--line-lit);
  background: transparent;
  color: var(--text-2);
  cursor: pointer;
  transition: border-color var(--t-fast) var(--ease-out),
    color var(--t-fast) var(--ease-out),
    background var(--t-fast) var(--ease-out);
}

.js .cycler-playtoggle {
  display: inline-flex;
}

/* Under prefers-reduced-motion the toggle would control an autoplay loop
   that never runs, so main.js sets `hidden` on it. Needs its own rule
   because the browser's default `[hidden] { display: none }` lives in the
   UA stylesheet, which any author-origin rule — including the one above —
   overrides regardless of specificity. This selector adds `[hidden]` on
   top of `.js .cycler-playtoggle`, so it is strictly more specific and
   wins outright rather than relying on source order. */
.js .cycler-playtoggle[hidden] {
  display: none;
}

.cycler-playtoggle:hover {
  border-color: var(--signal-edge);
  color: var(--signal);
  background: var(--signal-soft);
}

.cycler-playtoggle svg {
  width: 16px;
  height: 16px;
  fill: currentColor;
}

.cycler-playtoggle .icon-play {
  display: none;
}

.cycler-playtoggle.is-paused .icon-play {
  display: block;
}

.cycler-playtoggle.is-paused .icon-pause {
  display: none;
}

.cycler-stage {
  padding: 2.5rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  justify-content: center;
  min-height: 260px;
  /* Without JS, .cycler-rail is display:none and drops out of the grid
     entirely, so .cycler-stage is the only grid item left. Grid
     auto-placement would otherwise put it in the first (220px) column
     track alone, stranding it in a narrow strip with the 1fr track empty
     beside it. Span both tracks by default so it fills the full width;
     .js hands placement back to auto once the rail is a real grid item
     again, matching this file's existing .js-gating convention. */
  grid-column: 1 / -1;
}

.js .cycler-stage {
  grid-column: auto;
}

.cycler-panel-icon {
  width: 72px;
  height: 72px;
  margin: 0 auto 1.25rem;
  display: grid;
  place-items: center;
}

.cycler-panel-icon svg {
  width: 100%;
  height: 100%;
  stroke: var(--signal);
  fill: none;
  stroke-width: 1.3;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.cycler-panel-icon .icon-lock circle {
  fill: var(--signal);
  stroke: none;
}

.cycler-panel h3 {
  font-family: var(--display);
  font-size: 1.35rem;
  margin: 0 0 0.6rem;
  color: var(--text);
}

.cycler-panel p {
  font-size: 0.95rem;
  color: var(--text-2);
  line-height: 1.6;
  max-width: 42ch;
  margin: 0;
}

/* --------------------------------------------------------------------------
   Services cycler — reuses .cycler/.cycler-rail/.cycler-tab/.cycler-tab-icon/
   .cycler-playtoggle and initBenefitsCycler() exactly as shipped for the
   homepage. Only the stage differs: five categories, an alternating
   corner-block frame, and three rotating internal layouts instead of one
   centered icon+h3+p. Scoped under .cycler--services so every rule above
   this block (the homepage's .cycler-stage/.cycler-panel) is untouched.
   -------------------------------------------------------------------------- */

.cycler--services .cycler-stage {
  display: block;
  padding: 0;
  min-height: 0;
}

.cycler--services .cycler-panel {
  display: flex;
  flex-direction: column;
  min-height: 380px;
  text-align: left;
}

/* Author-origin `display` beats the UA stylesheet's `[hidden] { display:
   none }` regardless of specificity — same footgun as
   .js .cycler-playtoggle[hidden] above. Without this, main.js's
   initBenefitsCycler() (main.js:689) still toggles the `hidden` attribute,
   but the rule above keeps every panel visually `display: flex`, so all
   five panels stack and render at once instead of one at a time. */
.cycler--services .cycler-panel[hidden] {
  display: none;
}

/* Larger services cycler.
 *
 * Deliberately scoped to >=1024px and to `.cycler-tray`, for two reasons.
 *
 * The rail flips to a horizontal scrolling row at <=620px (see the
 * .cycler--services .cycler-rail rule above), so widening its grid column
 * unconditionally would squeeze the mobile layout rather than help it. Below
 * this breakpoint everything behaves exactly as it did before.
 *
 * The container override is on `.cycler-tray .container`, not on `.container`,
 * so it widens only this band. The heading, the cycler, and the closing note
 * are all siblings inside that one container, which is what keeps them aligned
 * with each other while the band as a whole sits wider than the rest of the
 * page. Widening the shared --max-width token instead would move every section
 * on every page. */
@media (min-width: 1024px) {
  .cycler-tray .container {
    max-width: 1320px;
  }

  .cycler--services {
    grid-template-columns: 280px 1fr;
  }

  .cycler--services .cycler-panel {
    min-height: 520px;
  }
}

.services-minor-note {
  margin-top: 2rem;
  font-size: 0.9rem;
  color: var(--text-2);
  text-align: center;
}

/* The whole services cycler sits inside the navy monitor bezel from the
   homepage hero. ::before is the camera dot up top; ::after is a small
   teal power LED on the bottom edge, the panel's one quiet moving detail
   alongside the dot-grid drift below. */
.cycler-bezel {
  background: linear-gradient(180deg, #1a2a3d, #0a141f);
  border-radius: 18px;
  padding: 12px;
  box-shadow: 0 24px 60px -30px rgba(15, 28, 43, 0.4), 0 4px 12px rgba(15, 28, 43, 0.12);
  position: relative;
}

.cycler-bezel::before {
  content: "";
  position: absolute;
  top: 5px;
  left: 50%;
  width: 4px;
  height: 4px;
  margin-left: -2px;
  border-radius: 50%;
  background: #2a3b4d;
}

.cycler-bezel::after {
  content: "";
  position: absolute;
  bottom: 4.5px;
  left: 50%;
  width: 5px;
  height: 5px;
  margin-left: -2.5px;
  border-radius: 50%;
  background: #2f9e84;
  animation: cycler-led-pulse 2.4s ease-in-out infinite;
}

@keyframes cycler-led-pulse {
  0%, 100% { opacity: 1;    box-shadow: 0 0 6px 1px rgba(47, 158, 132, 0.7); }
  50%      { opacity: 0.35; box-shadow: 0 0 2px 0 rgba(47, 158, 132, 0.3); }
}

/* Inside the bezel the cycler is the screen: tight radius, and the bezel
   carries the drop shadow so the cycler must not double it. */
.cycler-bezel .cycler {
  border-radius: 8px;
  box-shadow: none;
}

.svc-headline {
  font-family: var(--display);
  font-weight: 700;
  font-size: 1.4rem;
  letter-spacing: -0.01em;
  line-height: 1.1;
  color: var(--text);
  margin: 0 0 0.5rem;
}

.cat-website .svc-headline,
.cat-email .svc-headline {
  color: var(--cobalt);
}

.cat-hosting .svc-headline,
.cat-crm .svc-headline {
  color: var(--signal);
}

.cat-ads .svc-headline {
  color: var(--amber);
}

.svc-copy {
  font-family: var(--body);
  font-size: 0.88rem;
  color: var(--text-2);
  margin: 0;
}

/* Orange, not teal, even though this is a text link rather than a button:
   every other route to /contact on the site is --cta, and these five were the
   only conversion links wearing a different colour.

   --cta-text rather than --cta: as a fill behind white text the base orange is
   fine, but as small text on the panel's near-white background it measures
   4.43:1, just under the 4.5:1 AA floor. The darker tone clears it at 4.96:1
   and still reads as the same colour. */
.svc-cta {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  margin-top: 0.75rem;
  font-family: var(--display);
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--cta-text);
  text-decoration: none;
}

.svc-cta:hover {
  text-decoration: underline;
}

/* layout: split — text left, scene on a washed panel, right. Used by all
   five service categories, so every panel reads at the same size and the
   scene lands in the same place regardless of which one is showing. */
.svc-split {
  flex: 1;
  display: flex;
}

.svc-split-text {
  flex: 1.3;
  padding: 1.5rem 1.25rem;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

/* The scene area: a diagonal white-to-category-color wash under a faint
   dot grid in the same color. The grid drifts very slowly on the diagonal
   (one cell every ~6s) — quiet enough to read past, alive up close. */
.svc-split-icon {
  flex: 1;
  display: grid;
  place-items: center;
  padding: 1rem;
  background-size: 14px 14px, 100% 100%;
  animation: svc-dot-drift 12s linear infinite;
}

@keyframes svc-dot-drift {
  from { background-position: 0 0, 0 0; }
  to   { background-position: 28px 28px, 0 0; }
}

.cat-website .svc-split-icon,
.cat-email .svc-split-icon {
  background-image: radial-gradient(rgba(31, 95, 214, 0.13) 1px, transparent 1px),
    linear-gradient(135deg, #ffffff 0%, var(--cobalt-soft) 100%);
}

.cat-hosting .svc-split-icon,
.cat-crm .svc-split-icon {
  background-image: radial-gradient(rgba(10, 125, 100, 0.13) 1px, transparent 1px),
    linear-gradient(135deg, #ffffff 0%, var(--signal-soft) 100%);
}

.cat-ads .svc-split-icon {
  background-image: radial-gradient(rgba(180, 83, 9, 0.13) 1px, transparent 1px),
    linear-gradient(135deg, #ffffff 0%, var(--amber-soft) 100%);
}

.svc-scene {
  width: 88%;
  height: auto;
}

/* Ad Campaigns is the one category with a bullet list instead of a single
   sentence (svc-copy) - kept because the three lines are genuinely distinct
   value props, not filler. Still lives in .svc-split-text like every other
   category's copy. */
.svc-dual-list {
  margin: 0;
  padding-left: 1.1rem;
  font-family: var(--body);
  font-size: 0.84rem;
  color: var(--text-2);
}

.svc-dual-list li {
  margin-bottom: 0.3rem;
}

.svc-dual-list li:last-child {
  margin-bottom: 0;
}

@media (max-width: 620px) {
  .cycler--services .cycler-rail {
    flex-direction: row;
    overflow-x: auto;
  }
}

.cycler-panel.is-entering {
  animation: cycler-panel-in var(--t-med) var(--ease-out);
}

@keyframes cycler-panel-in {
  from {
    opacity: 0;
    transform: translateY(8px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

.globe-spin {
  transform-origin: center;
  animation: globe-spin 9s linear infinite;
}

@keyframes globe-spin {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}

.lock-shackle {
  transform-origin: 12px 9px;
  animation: lock-close 2.6s var(--ease-out) infinite;
}

@keyframes lock-close {
  0%,
  15% {
    transform: translateY(-2px) rotate(-8deg);
  }
  35%,
  100% {
    transform: translateY(0) rotate(0deg);
  }
}

/* --------------------------------------------------------------------------
   Service cards — information, not stock photography.
   -------------------------------------------------------------------------- */

.grid-3 {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1.1rem;
}

.grid-2 {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 1.1rem;
}

.card {
  background: var(--slate-2);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1.6rem 1.5rem 1.7rem;
  display: flex;
  flex-direction: column;
  transition: border-color var(--t-fast) var(--ease-out),
    background var(--t-fast) var(--ease-out),
    transform var(--t-fast) var(--ease-out),
    box-shadow var(--t-fast) var(--ease-out);
}

.card:hover {
  border-color: var(--signal-edge);
  background: var(--slate-3);
  transform: translateY(-4px);
  box-shadow: 0 14px 34px rgba(15, 28, 43, 0.12), 0 0 0 1px var(--signal-edge);
}

.card:hover .card-list li {
  color: var(--text-2);
}

.card-icon {
  width: 34px;
  height: 34px;
  border-radius: 9px;
  display: grid;
  place-items: center;
  background: var(--cobalt-soft);
  border: 1px solid rgba(31, 95, 214, 0.22);
  margin-bottom: 1.15rem;
}

.card-icon svg {
  width: 17px;
  height: 17px;
  stroke: var(--cobalt);
  fill: none;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Illustrated tiles on /automation: a small flat-shape scene in the same
   style as the hero illustrations, standing in for the 34px .card-icon
   where a picture says the thing faster than a paragraph would. */
.card-art {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 10px;
  border: 1px solid var(--line);
  margin-bottom: 1.15rem;
}

/* A whole card as one link (the packages on /automation). Inherits the
   card's own hover lift; the text stays the card's colors, and the trailing
   .arrow-link span is the only thing that reads as a link. */
.card-link,
.card-link:hover {
  color: inherit;
  text-decoration: none;
}

.card-link .arrow-link {
  margin-top: auto;
  padding-top: 0.9rem;
}

.card h3 {
  margin-bottom: 0.55rem;
}

.card > p {
  color: var(--text-2);
  font-size: 0.94rem;
  line-height: 1.6;
}

.card-list {
  list-style: none;
  margin: 1.15rem 0 0;
  padding: 1.15rem 0 0;
  border-top: 1px solid var(--line);
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.card-list li {
  font-size: 0.86rem;
  color: var(--muted);
  padding-left: 1.1rem;
  position: relative;
}

.card-list li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.55em;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--line-lit);
}

/* --------------------------------------------------------------------------
   Demo carousel — clickable previews of live example sites.

   One card exists today; the component is built to take more without any
   markup or JS changes later. The nav arrows (JS-only, like the rest of
   this file's enhancements) disable individually at each scroll extreme
   and hide the whole cluster whenever the track doesn't overflow at all —
   which is exactly the state a single card is in right now.
   -------------------------------------------------------------------------- */

.demo-section-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: 2rem;
}

.demo-carousel-nav {
  display: none;
  gap: 0.6rem;
  flex-shrink: 0;
  padding-bottom: 0.3rem;
}

.js .demo-carousel-nav {
  display: flex;
}

.demo-carousel-nav.is-hidden {
  display: none;
}

.demo-carousel-btn {
  width: 38px;
  height: 38px;
  border-radius: 9px;
  border: 1px solid var(--line-lit);
  background: transparent;
  color: var(--text-2);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: border-color var(--t-fast) var(--ease-out),
    color var(--t-fast) var(--ease-out),
    background var(--t-fast) var(--ease-out);
}

.demo-carousel-btn:hover:not(:disabled) {
  border-color: var(--signal-edge);
  color: var(--signal);
  background: var(--signal-soft);
}

.demo-carousel-btn:disabled {
  opacity: 0.35;
  cursor: default;
}

.demo-carousel-btn svg {
  width: 16px;
  height: 16px;
  stroke: currentColor;
  fill: none;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.demo-carousel-track {
  display: flex;
  gap: 1.5rem;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-padding-left: var(--gutter);
  margin-top: 2.5rem;
  padding-bottom: 0.25rem;
  scrollbar-width: thin;
  scrollbar-color: var(--line-lit) transparent;
}

.demo-card {
  flex: 0 0 auto;
  width: min(380px, 82vw);
  scroll-snap-align: start;
  display: block;
  background: var(--slate-2);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  text-decoration: none;
  transition: border-color var(--t-fast) var(--ease-out),
    background var(--t-fast) var(--ease-out),
    transform var(--t-fast) var(--ease-out),
    box-shadow var(--t-fast) var(--ease-out);
}

.demo-card:hover {
  border-color: var(--signal-edge);
  background: var(--slate-3);
  transform: translateY(-4px);
  box-shadow: 0 14px 34px rgba(15, 28, 43, 0.12), 0 0 0 1px var(--signal-edge);
  text-decoration: none;
}

.demo-card-shot {
  display: block;
  aspect-ratio: 1512 / 794;
  background: var(--slate-3);
  border-bottom: 1px solid var(--line);
  overflow: hidden;
}

.demo-card-shot img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: top;
  transition: transform var(--t-med) var(--ease-out);
}

.demo-card:hover .demo-card-shot img {
  transform: scale(1.035);
}

.demo-card-body {
  display: block;
  padding: 1.4rem 1.5rem 1.6rem;
}

.demo-card-body h3 {
  color: var(--text);
  margin-bottom: 0.5rem;
}

.demo-card-body p {
  color: var(--text-2);
  font-size: 0.92rem;
  line-height: 1.6;
  margin-bottom: 1.1rem;
}

.demo-card-visit {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  font-weight: 500;
  font-size: 0.88rem;
  color: var(--signal);
}

.demo-card-visit::after {
  content: "→";
  transition: transform 0.2s ease;
}

.demo-card:hover .demo-card-visit::after {
  transform: translateX(4px);
}

/* --------------------------------------------------------------------------
   Process — the one place numbering is honest, because the steps really
   do happen in order.
   -------------------------------------------------------------------------- */

.steps {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 0;
  border-top: 1px solid var(--line);
}

.step {
  padding: 1.9rem 1.6rem 1.9rem 0;
  position: relative;
}

.step + .step {
  padding-left: 1.6rem;
  border-left: 1px solid var(--line);
}

.step-n {
  font-family: var(--mono);
  font-size: 0.7rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  color: var(--signal);
  display: block;
  margin-bottom: 0.85rem;
}

.step h3 {
  margin-bottom: 0.5rem;
}

.step p {
  color: var(--muted);
  font-size: 0.9rem;
  line-height: 1.58;
}

/* --------------------------------------------------------------------------
   Checklist — "what every build includes"
   -------------------------------------------------------------------------- */

.checklist {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 0.1rem 2.5rem;
}

.checklist li {
  display: flex;
  align-items: flex-start;
  gap: 0.7rem;
  padding: 0.78rem 0;
  border-bottom: 1px solid var(--line);
  font-size: 0.93rem;
  color: var(--text-2);
}

.checklist svg {
  width: 15px;
  height: 15px;
  flex-shrink: 0;
  margin-top: 0.24rem;
  stroke: var(--signal);
  fill: none;
  stroke-width: 2.2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* --------------------------------------------------------------------------
   Spec table — plain facts, mono-set
   -------------------------------------------------------------------------- */

.spec {
  border-top: 1px solid var(--line);
}

.spec-row {
  display: grid;
  grid-template-columns: 13rem 1fr;
  gap: 1.5rem;
  padding: 1.1rem 0;
  border-bottom: 1px solid var(--line);
  align-items: baseline;
}

.spec-key {
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--muted);
}

.spec-val {
  color: var(--text-2);
  font-size: 0.95rem;
  line-height: 1.6;
}

.spec-val strong {
  color: var(--text);
}

/* --------------------------------------------------------------------------
   Pricing
   -------------------------------------------------------------------------- */

.price-group-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  padding-bottom: 0.9rem;
  border-bottom: 1px solid var(--line-lit);
  margin-bottom: 0.25rem;
}

.price-group-head h3 {
  font-size: 1.3rem;
  font-weight: 700;
}

.price-group-note {
  font-family: var(--mono);
  font-size: 0.7rem;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--muted);
}

.price-row {
  display: grid;
  grid-template-columns: 1.05fr 1.5fr auto 7rem;
  align-items: center;
  gap: 1.5rem;
  padding: 1.4rem 0.75rem;
  margin: 0 -0.75rem;
  border-bottom: 1px solid var(--line);
  border-radius: 8px;
  transition: background 0.2s ease;
}

.price-row:hover {
  background: rgba(15, 28, 43, 0.025);
}

.price-row h4 {
  margin: 0;
}

.price-row .desc {
  color: var(--muted);
  font-size: 0.91rem;
  line-height: 1.55;
}

.price-row .btn {
  white-space: nowrap;
  justify-self: stretch;
}

/* Both controls occupy the same grid cell — CSS (gated on the .js class set
   synchronously in <head>, same mechanism the nav toggle uses) shows exactly
   one of them, so a no-JS visitor always gets a single working Subscribe
   link rather than a dead button next to a live one. */
.subscribe-fallback,
[data-checkout] {
  grid-column: 4;
}

[data-checkout] {
  display: none;
}

.js .subscribe-fallback {
  display: none;
}

.js [data-checkout] {
  display: inline-flex;
}

.addon-picker {
  grid-column: 1 / -1;
  display: none;
  border-top: 1px solid var(--line);
  margin-top: 0.5rem;
  padding-top: 0.25rem;
}

.js .addon-picker {
  display: block;
}

.addon-picker summary {
  list-style: none;
  cursor: pointer;
  padding: 0.6rem 0;
  font-family: var(--mono);
  font-size: 0.72rem;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
  transition: color 0.15s ease;
}

.addon-picker summary::-webkit-details-marker {
  display: none;
}

.addon-picker summary:hover {
  color: var(--signal);
}

.addon-list {
  display: grid;
  gap: 0.6rem;
  padding: 0.5rem 0 0.75rem;
}

.addon-option {
  display: flex;
  align-items: baseline;
  gap: 0.65rem;
  font-size: 0.9rem;
  color: var(--text-2);
  cursor: pointer;
}

.addon-option input {
  accent-color: var(--signal);
  flex-shrink: 0;
}

.addon-option .addon-name {
  flex: 1;
}

.addon-option .addon-price {
  font-family: var(--mono);
  color: var(--muted);
  white-space: nowrap;
}

.addon-option .addon-tag {
  display: inline-block;
  margin-left: 0.5rem;
  padding: 0.05rem 0.45rem;
  border-radius: 999px;
  background: var(--signal-soft);
  border: 1px solid var(--signal-edge);
  color: var(--signal);
  font-family: var(--mono);
  font-size: 0.62rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  vertical-align: middle;
}

/* Addon progress bar — tracks recommended extras only, never a price total,
   so it reads as momentum toward a curated bundle rather than a bill. */
.addon-progress {
  display: grid;
  gap: 0.4rem;
  padding: 0.15rem 0 0.85rem;
  position: relative;
}

.addon-progress-label {
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.04em;
  color: var(--muted);
  transition: color var(--t-fast) var(--ease-out);
}

.addon-progress-track {
  height: 6px;
  border-radius: 999px;
  background: var(--slate-3);
  overflow: hidden;
}

.addon-progress-fill {
  height: 100%;
  width: 0%;
  border-radius: 999px;
  background: var(--signal);
  transition: width var(--t-med) var(--ease-out), background-color var(--t-fast) var(--ease-out);
}

.addon-progress.is-complete .addon-progress-label {
  color: var(--signal);
}

.addon-progress.is-complete .addon-progress-fill {
  background: linear-gradient(90deg, var(--signal), var(--cobalt));
}

.addon-progress.is-celebrating .addon-progress-track {
  animation: addon-progress-pulse var(--t-slow) var(--ease-out);
}

@keyframes addon-progress-pulse {
  0% {
    box-shadow: 0 0 0 0 var(--signal-glow);
  }
  40% {
    box-shadow: 0 0 0 7px var(--signal-glow);
  }
  100% {
    box-shadow: 0 0 0 0 rgba(10, 125, 100, 0);
  }
}

.addon-spark {
  position: absolute;
  top: 0;
  left: var(--spark-x, 50%);
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--spark-color, var(--signal));
  pointer-events: none;
  animation: addon-spark-burst 0.8s var(--ease-out) forwards;
}

@keyframes addon-spark-burst {
  0% {
    transform: translate(-50%, -50%) scale(1);
    opacity: 1;
  }
  100% {
    transform: translate(calc(-50% + var(--spark-dx, 0px)), calc(-50% + var(--spark-dy, -30px)))
      scale(0);
    opacity: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .addon-progress.is-celebrating .addon-progress-track {
    animation: none;
  }

  .addon-spark {
    display: none;
  }
}

.addon-checkout-error {
  grid-column: 1 / -1;
  color: #b45309;
  font-size: 0.85rem;
  margin-top: 0.5rem;
}

.price-foot {
  margin-top: 1.5rem;
  font-size: 0.89rem;
  color: var(--muted);
  line-height: 1.6;
}

/* --------------------------------------------------------------------------
   FAQ — objection handling, in the visitor's own words
   -------------------------------------------------------------------------- */

.faq {
  border-top: 1px solid var(--line);
}

.faq details {
  border-bottom: 1px solid var(--line);
}

.faq summary {
  list-style: none;
  cursor: pointer;
  padding: 1.35rem 2.5rem 1.35rem 0;
  position: relative;
  font-family: var(--display);
  font-weight: 500;
  font-size: 1.05rem;
  letter-spacing: -0.015em;
  color: var(--text);
  transition: color 0.15s ease;
}

.faq summary::-webkit-details-marker {
  display: none;
}

.faq summary:hover {
  color: var(--signal);
}

.faq summary::after {
  content: "";
  position: absolute;
  right: 0.4rem;
  top: 50%;
  width: 9px;
  height: 9px;
  border-right: 1.6px solid var(--muted);
  border-bottom: 1.6px solid var(--muted);
  transform: translateY(-70%) rotate(45deg);
  transition: transform 0.22s ease, border-color 0.22s ease;
}

.faq details[open] summary::after {
  transform: translateY(-30%) rotate(225deg);
  border-color: var(--signal);
}

.faq-body {
  padding: 0 3rem 1.5rem 0;
  color: var(--text-2);
  font-size: 0.96rem;
  line-height: 1.68;
  max-width: 46rem;
}

/* --------------------------------------------------------------------------
   About
   -------------------------------------------------------------------------- */

.about-layout {
  display: grid;
  grid-template-columns: 0.8fr 1.2fr;
  gap: 3.5rem;
  align-items: start;
}

.about-photo img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 14px;
  border: 1px solid var(--line);
  box-shadow: 0 24px 60px -30px rgba(15, 28, 43, 0.18);
}

.about-lede {
  font-family: var(--display);
  font-weight: 500;
  font-size: 1.45rem;
  line-height: 1.38;
  letter-spacing: -0.022em;
  margin-bottom: 2rem;
}

.about-block + .about-block {
  margin-top: 1.6rem;
}

.about-block h3 {
  margin-bottom: 0.45rem;
}

.about-block p {
  color: var(--text-2);
  line-height: 1.68;
}

/* The about page's second section: Credentials and the personal blocks
   after it on the left, the badge on the right, mirroring the
   photo-beside-text proportions of the band above. The gray-to-white
   band edge above it is the divider, so no rule of its own. */
.about-cred-band {
  display: grid;
  grid-template-columns: minmax(0, 1.2fr) minmax(0, 0.8fr);
  gap: 3.5rem;
  align-items: center;
}

.credential-badge {
  display: block;
  width: 72%;
  max-width: 250px;
  height: auto;
  margin: 0 auto;
}

.credential-caption {
  text-align: center;
  font-size: 0.85rem;
  color: var(--muted);
  margin-top: 0.35rem;
  line-height: 1.45;
}

@media (max-width: 860px) {
  .about-cred-band {
    grid-template-columns: 1fr;
    gap: 1.5rem;
  }
}

/* Same dot-bullet convention as .card-list, without its top border - this
   list sits inline in a block's own prose rather than closing off a card. */
.about-location-list {
  list-style: none;
  margin: 0.75rem 0 1rem;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 0.4rem 1.5rem;
}

.about-location-list li {
  font-size: 0.95rem;
  color: var(--text-2);
  padding-left: 1.1rem;
  position: relative;
}

.about-location-list li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.6em;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--line-lit);
}

/* --------------------------------------------------------------------------
   Forms
   -------------------------------------------------------------------------- */

.form-field {
  margin-bottom: 1.1rem;
  display: flex;
  flex-direction: column;
}

/* The .form-field display:flex above is author-origin, so it beats the UA
   stylesheet's [hidden] { display: none } outright regardless of
   specificity — same conflict already handled for .cycler-playtoggle. */
.form-field[hidden] {
  display: none;
}

.form-field label {
  font-size: 0.86rem;
  font-weight: 500;
  color: var(--text-2);
  margin-bottom: 0.45rem;
}

.form-field .req {
  color: var(--muted);
  font-weight: 400;
}

.form-field input,
.form-field select,
.form-field textarea {
  background: var(--ink);
  border: 1px solid var(--line-lit);
  border-radius: 9px;
  padding: 0.78rem 0.9rem;
  color: var(--text);
  font-family: var(--body);
  font-size: 0.95rem;
  width: 100%;
  transition: border-color var(--t-fast) var(--ease-out),
    box-shadow var(--t-fast) var(--ease-out);
}

.form-field textarea {
  resize: vertical;
  min-height: 9rem;
}

.form-field select {
  appearance: none;
  background-image: linear-gradient(45deg, transparent 50%, var(--muted) 50%),
    linear-gradient(135deg, var(--muted) 50%, transparent 50%);
  background-position: calc(100% - 18px) calc(50% + 1px),
    calc(100% - 13px) calc(50% + 1px);
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  padding-right: 2.4rem;
}

.form-field input::placeholder,
.form-field textarea::placeholder {
  color: var(--muted);
}

.form-field input:focus,
.form-field select:focus,
.form-field textarea:focus {
  outline: none;
  border-color: var(--signal);
  box-shadow: 0 0 0 3px var(--signal-soft);
}

.form-field input:focus-visible,
.form-field select:focus-visible,
.form-field textarea:focus-visible {
  outline: 2px solid var(--signal);
  outline-offset: 2px;
}

.form-hint {
  font-size: 0.82rem;
  color: var(--muted);
  margin-top: 0.4rem;
}

.form-char-count {
  color: var(--danger);
}

.form-char-count--ok {
  color: var(--signal);
}

.form-foot {
  display: flex;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
  margin-top: 1.4rem;
}

.form-foot p {
  font-size: 0.84rem;
  color: var(--muted);
}

/* Honeypot — off-screen but not display:none, so bots still fill it. */
.hp-field {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.contact-layout {
  display: grid;
  grid-template-columns: 1.3fr 1fr;
  gap: 3.5rem;
  align-items: start;
}

.side-card {
  background: var(--slate-2);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1.6rem;
}

.side-card + .side-card {
  margin-top: 1rem;
}

.side-card h3 {
  margin-bottom: 0.55rem;
}

.side-card p {
  color: var(--text-2);
  font-size: 0.93rem;
  line-height: 1.62;
}

.side-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.7rem;
}

.side-list li {
  display: flex;
  gap: 0.65rem;
  align-items: flex-start;
  font-size: 0.92rem;
  color: var(--text-2);
}

.side-list svg {
  width: 15px;
  height: 15px;
  flex-shrink: 0;
  margin-top: 0.22rem;
  stroke: var(--signal);
  fill: none;
  stroke-width: 2.2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.contact-email {
  font-family: var(--mono);
  font-size: 0.95rem;
  display: inline-block;
  margin-top: 1rem;
  padding-top: 1rem;
  border-top: 1px solid var(--line);
  width: 100%;
  word-break: break-all;
}

/* --------------------------------------------------------------------------
   CTA band
   -------------------------------------------------------------------------- */

.cta-band {
  text-align: center;
  border-top: 1px solid var(--line);
  background:
    radial-gradient(70% 130% at 50% 0%, rgba(10, 125, 100, 0.07) 0%, transparent 62%),
    linear-gradient(180deg, var(--slate) 0%, var(--ink) 100%);
}

.cta-band h2 {
  max-width: 17ch;
  margin: 0 auto 1rem;
}

.cta-band .lede {
  color: var(--text-2);
  max-width: 40ch;
  margin: 0 auto 2rem;
  font-size: 1.04rem;
}

.cta-actions {
  display: flex;
  gap: 0.75rem;
  justify-content: center;
  flex-wrap: wrap;
}

/* --------------------------------------------------------------------------
   Footer
   -------------------------------------------------------------------------- */

.site-footer {
  border-top: 1px solid var(--line);
  padding: 3rem 0 2.5rem;
  color: var(--muted);
  font-size: 0.87rem;
  background: var(--slate);
}

.footer-top {
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 0 var(--gutter) 2rem;
  display: grid;
  grid-template-columns: 1.4fr 1fr 1fr;
  gap: 2.5rem;
}

.footer-brand .brand {
  margin-bottom: 0.8rem;
}

.footer-brand p {
  max-width: 26rem;
  line-height: 1.6;
}

.footer-col h4 {
  font-family: var(--mono);
  font-size: 0.68rem;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--text-2);
  margin-bottom: 0.9rem;
}

.footer-col ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
}

.footer-col a {
  color: var(--muted);
  font-size: 0.88rem;
}

.footer-col a:hover {
  color: var(--text);
  text-decoration: none;
}

.footer-inner {
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 1.6rem var(--gutter) 0;
  border-top: 1px solid var(--line);
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
}

.footer-inner .mono {
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

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

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  .proof-pulse,
  .proof-pulse-ring {
    animation: none;
  }

  .steam {
    animation: none;
    opacity: 0.5;
  }

  .pricing-bar--highlight {
    animation: none;
  }

  .standard-graphic .seal,
  .standard-graphic .layer,
  .standard-graphic .flag,
  .credential-badge .link,
  .credential-badge .shield,
  .hero-illustration-art .auto-flow,
  .hero-illustration-art .auto-badge,
  .hero-illustration-art .art-drift,
  .card-art .art-pulse,
  .card-art .art-bob,
  .card-art .auto-flow,
  .card--tap.is-open .card-art .sc-cursor,
  .card--tap.is-open .card-art .sc-wrong,
  .card--tap.is-open .card-art .sc-move,
  .card--tap.is-open .card-art .sc-after,
  .card--tap.is-open .card-art .sc-pop-item,
  .cycler-bezel::after,
  .svc-split-icon {
    animation: none;
  }

  /* Stays invisible without its animation, matching the graphic at rest. */
  .standard-graphic .hl {
    animation: none;
    opacity: 0;
  }

  .globe-spin,
  .lock-shackle {
    animation: none;
  }

  .cycler-panel.is-entering {
    animation: none;
  }

  /* The spinner is the one loop this site adds beyond the Live pulse dot, and
     it earns the exception by being functional and transient rather than
     decorative. Under reduced motion it stops spinning — but a ring with a
     quarter missing and no rotation just looks like a rendering bug, so the
     gap closes and the label carries the meaning instead. */
  .btn[aria-busy="true"]::before {
    animation: none;
    border-top-color: currentColor;
    opacity: 0.55;
  }

  .btn,
  .card,
  .demo-card,
  .demo-card-shot img,
  .demo-card-visit::after,
  .arrow-link::after,
  .faq summary::after,
  .nav-links a::after,
  .nav-links {
    transition: none;
  }

  .btn-primary:hover,
  .btn-outline:hover,
  .card:hover,
  .demo-card:hover,
  .demo-card:hover .demo-card-shot img {
    transform: none;
  }

  /* Motion system: kill every entrance and reveal in one place. */
  .js .hero-claim > *,
  .js .proof,
  .js .band-head,
  .hero-claim h1 .w,
  .js main > .band:first-of-type .kicker,
  .js main > .band:first-of-type :is(h1, h2),
  .js main > .band:first-of-type .lede,
  .js main > .band:first-of-type .cta-actions {
    animation: none !important;
    opacity: 1 !important;
    transform: none !important;
    filter: none !important;
  }

  * {
    animation-timeline: initial !important;
  }
}

/* --------------------------------------------------------------------------
   Responsive
   -------------------------------------------------------------------------- */

@media (max-width: 1000px) {
  .hero-grid {
    grid-template-columns: 1fr;
    gap: 3rem;
  }

  .hero-sub {
    max-width: 34rem;
  }

  .proof {
    max-width: 30rem;
  }

  .hero-scroll-cue {
    display: none;
  }

  .steps {
    grid-template-columns: repeat(2, 1fr);
  }

  .step:nth-child(odd) {
    padding-left: 0;
    border-left: 0;
  }

  .step:nth-child(n + 3) {
    border-top: 1px solid var(--line);
  }

  .checklist {
    grid-template-columns: repeat(2, 1fr);
    gap: 0.1rem 2rem;
  }

  .band-head--split {
    grid-template-columns: 1fr;
    gap: 1rem;
    align-items: start;
  }

  .band-head--split h2,
  .band-head--split .lede {
    grid-column: 1;
    max-width: 34rem;
  }

  .contact-layout,
  .about-layout {
    grid-template-columns: 1fr;
    gap: 2.5rem;
  }

  .footer-top {
    grid-template-columns: 1fr 1fr;
  }

  .footer-brand {
    grid-column: 1 / -1;
  }
}

/* The nav collapses to the sheet at 1040px, not 860px: with seven items
   ("Automation" joined in September 2026) the inline row needs about
   1030px, and below that "How I work" and "Manage your account" wrapped
   onto two lines. Everything else in the 860px block below is unrelated
   layout and stays where it was. */
@media (max-width: 1040px) {
  .nav-toggle {
    display: block;
  }

  .nav-links {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    background: var(--ink);
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    max-height: 0;
    overflow: hidden;
    transition: max-height var(--t-fast) var(--ease-out);
    border-bottom: 1px solid var(--line);
  }

  .nav-links.open {
    max-height: 40rem;
  }

  .nav-links li {
    border-top: 1px solid var(--line);
  }

  .nav-links a {
    display: block;
    padding: 0.9rem var(--gutter);
  }

  .nav-cta {
    margin: 0;
  }

  /* The sheet stacks, so the hairline that separates "Manage your account"
     from the pages above it on desktop has to become the row's own top
     border like every other item, not a rule down its left edge. */
  .nav-login {
    margin: 0;
    padding-left: 0;
    border-left: 0;
  }

  .nav-links .btn {
    border-radius: 0;
    border: 0;
    justify-content: flex-start;
  }
}

@media (max-width: 860px) {
  :root {
    --band-y: 4.75rem;
  }

  .grid-3,
  .grid-2 {
    grid-template-columns: 1fr;
  }

  .demo-section-head {
    flex-direction: column;
    align-items: flex-start;
    gap: 1.25rem;
  }

  .creds-inner {
    grid-template-columns: 1fr;
  }

  .cred {
    padding: 1.15rem 0;
  }

  .cred + .cred {
    padding-left: 0;
    border-left: 0;
    border-top: 1px solid var(--line);
  }

  .price-row {
    grid-template-columns: 1fr auto;
    gap: 0.4rem 1.25rem;
    align-items: start;
  }

  .price-row h4 {
    grid-column: 1;
  }

  .price-row .desc {
    grid-column: 1 / -1;
    grid-row: 2;
  }

  .price-row .btn {
    grid-column: 1 / -1;
    grid-row: 3;
    justify-self: start;
    margin-top: 0.7rem;
  }

  .subscribe-fallback,
  [data-checkout] {
    grid-column: 1 / -1;
    grid-row: 3;
  }

  .addon-picker {
    grid-row: 4;
  }

  .spec-row {
    grid-template-columns: 1fr;
    gap: 0.4rem;
  }

  .footer-inner {
    justify-content: center;
    text-align: center;
  }
}

@media (max-width: 720px) {
  .cycler {
    grid-template-columns: 1fr;
  }

  .cycler-rail {
    flex-direction: row;
    border-right: none;
    border-bottom: 1px solid var(--line);
    overflow-x: auto;
  }

  .cycler-tab-label {
    display: none;
  }

  .cycler-playtoggle {
    margin: 0.6rem 0.75rem;
  }
}

@media (max-width: 620px) {
  .hero {
    padding: 3.5rem 0 3.5rem;
  }

  .steps {
    grid-template-columns: 1fr;
  }

  .step {
    padding: 1.5rem 0;
    border-left: 0;
  }

  .step + .step {
    padding-left: 0;
    border-left: 0;
    border-top: 1px solid var(--line);
  }

  .checklist {
    grid-template-columns: 1fr;
  }

  .about-lede {
    font-size: 1.22rem;
  }

  .footer-top {
    grid-template-columns: 1fr;
    gap: 2rem;
  }

  .faq-body {
    padding-right: 1rem;
  }
}

/* --------------------------------------------------------------------------
   Motion

   Entrances hide content only under html.js (set by one inline line in each
   page's head), so a no-JS visit shows everything immediately. Scroll
   reveals live inside @supports (animation-timeline: view()) — they are
   scrubbed by scroll position, not observer-fired, so scrolling fast just
   plays them faster; there is no blank-section failure mode. Browsers
   without support simply show a static page.
   -------------------------------------------------------------------------- */

/* Hero choreography — words first, then sub, actions, note. All gated on
   html.js; without JavaScript nothing here applies and the hero is simply
   visible. */

.hero-claim h1 .w {
  display: inline-block;
  opacity: 0;
  animation: word-in var(--t-med) var(--ease-out) forwards;
  animation-delay: calc(var(--w-i, 0) * 70ms);
}

.js .hero-claim .hero-sub,
.js .hero-claim .hero-actions,
.js .hero-claim .hero-note {
  opacity: 0;
  animation: rise-in var(--t-med) var(--ease-out) forwards;
}

.js .hero-claim .hero-sub     { animation-delay: 0.55s; }
.js .hero-claim .hero-actions { animation-delay: 0.75s; }
.js .hero-claim .hero-note    { animation-delay: 0.8s; }

@keyframes rise-in {
  from { opacity: 0; transform: translateY(18px); }
  to   { opacity: 1; transform: none; }
}

@keyframes word-in {
  from { opacity: 0; transform: translateY(0.6em) rotate(2deg); filter: blur(6px); }
  to   { opacity: 1; transform: none; filter: blur(0); }
}

@keyframes card-in {
  from { opacity: 0; transform: translateY(34px) scale(0.96); filter: blur(3px); }
  to   { opacity: 1; transform: none; filter: blur(0); }
}

@keyframes panel-in {
  from { opacity: 0; transform: translateY(24px) scale(0.96); }
  to   { opacity: 1; transform: none; }
}

.js .proof,
.js .landmark--hero {
  opacity: 0;
  animation: panel-in var(--t-slow) var(--ease-out) 0.35s forwards;
}

/* Scroll-driven reveals. Scrubbed by scroll position (view-timeline), so
   fast scrolling plays them faster instead of leaving blanks. Browsers
   without support skip this whole block and show everything statically. */

@supports (animation-timeline: view()) {
  .band-head,
  .card,
  .step,
  .about-block,
  .side-card,
  .faq,
  .urgency-stat,
  .creds-inner > * {
    animation: card-in linear both;
    animation-timeline: view();
    animation-range: entry 0% entry 70%;
  }

  /* Stagger grid children so rows cascade instead of thudding in at once.
     view-timeline has no delay concept, so the stagger comes from shifting
     each child's range start. */
  .grid-2 > :nth-child(2n),
  .grid-3 > :nth-child(3n - 1),
  .steps > :nth-child(2n) {
    animation-range: entry 8% entry 78%;
  }

  .grid-3 > :nth-child(3n) {
    animation-range: entry 16% entry 86%;
  }

  /* First-band headers already have their own load entrance (see the
     page-header rules below) — on short viewports the scroll reveal would
     otherwise first-paint them mid-animation. */
  main > .band:first-of-type .band-head {
    animation: none;
  }

  /* ...except the homepage's urgency-band, which is now the first .band
     but has no load-entrance rule of its own (see the .js rules below,
     which don't target it) — it needs the scroll reveal restored so its
     heading fades in with the stat blocks beneath it instead of not
     animating at all. Selector is deliberately more specific than the
     main > .band:first-of-type .band-head rule above (adds .urgency-band)
     so it wins the cascade regardless of source order. */
  main > .band.urgency-band:first-of-type .band-head {
    animation: card-in linear both;
    animation-timeline: view();
    animation-range: entry 0% entry 70%;
  }
}

/* Page headers on inner pages get the hero's stagger, without the word
   split. Gated on html.js like the hero. Inner pages use h2 (not h1) for
   their title — see markup check in task-4-report.md — so the selector
   targets h2 rather than the brief's literal h1. */
.js main > .band:first-of-type .kicker,
.js main > .band:first-of-type :is(h1, h2),
.js main > .band:first-of-type .lede,
.js main > .band:first-of-type .cta-actions {
  opacity: 0;
  animation: rise-in var(--t-med) var(--ease-out) forwards;
}

.js main > .band:first-of-type :is(h1, h2) { animation-delay: 0.12s; }
.js main > .band:first-of-type .lede { animation-delay: 0.28s; }
.js main > .band:first-of-type .cta-actions { animation-delay: 0.44s; }

/* --------------------------------------------------------------------------
   Price anchor — modelled on the existing .spec/.spec-row key/value pattern
   from /portfolio, widened to three parts (label, figure, caveat) because a
   price without its qualifying sentence is the thing that gets misread.
   The figure uses --signal (teal), which on this site means "a fact you can
   verify yourself" — consistent with the live proof panel.
   -------------------------------------------------------------------------- */

.pricing-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 360px);
  gap: 2.5rem;
  align-items: center;
}

.pricing-layout .pricing {
  margin-top: 0;
}

/* Dark navy/blue card, matching .hero-illustration-frame on the homepage -
   the same deliberate one-off accent, not a site-wide dark mode. Scoped to
   this slot rather than any shared class, since nothing else reuses it. */
.pricing-graphic-slot {
  display: flex;
  align-items: center;
  justify-content: center;
  background: #101c2a;
  border: 1px solid #223349;
  border-radius: 14px;
  padding: 2.25rem;
}

.pricing-graphic {
  width: 100%;
  height: auto;
  max-width: 320px;
}

/* Bars before break-even use --line-lit, not --amber: amber already means
   "Ads" in the services category, and this isn't a warning or a cost callout
   — just "not yet". --signal (teal) after break-even keeps the same "this is
   the payoff" meaning it carries everywhere else on the site. */
.pricing-bar--before { fill: var(--line-lit); }
.pricing-bar--after { fill: var(--signal); }
.pricing-bar-baseline { stroke: var(--line-lit); }

/* The Month 6 bar gets a faint grow-and-settle pulse to draw the eye to
   where the chart ends up. transform-box/transform-origin pin the scale to
   the bar's own bottom edge (its foot on the baseline), same technique as
   .steam in the hero illustration, so it grows upward in place instead of
   from the SVG's own origin corner. */
.pricing-bar--highlight {
  transform-box: fill-box;
  transform-origin: 50% 100%;
  animation: pricing-bar-grow 2.8s ease-in-out infinite;
}

@keyframes pricing-bar-grow {
  0%, 100% { transform: scaleY(1); }
  50% { transform: scaleY(1.06); }
}

/* var(--muted) is tuned for the light page background and reads too low
   contrast on the dark card, so this one label color is overridden to a
   near-white slate — brighter than the hero illustration's #aab8c6, which
   was legible at that panel's larger scale but too faint at this size. */
.pricing-bar-axis-label {
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 600;
  fill: #e4e9ed;
}
.pricing-bar-axis-label--after { fill: var(--signal); }

.pricing-bar-breakeven-line { stroke: var(--cobalt); }
.pricing-bar-breakeven-dot { fill: var(--cobalt); }
.pricing-bar-breakeven-label {
  font-family: var(--display);
  font-size: 9.5px;
  font-weight: 700;
  fill: var(--cobalt);
}

.pricing {
  margin-top: 2.5rem;
  border-top: 1px solid var(--line);
}

.pricing-row {
  display: grid;
  grid-template-columns: 13rem 1fr;
  gap: 0.35rem 1.5rem;
  padding: 1.6rem 0;
  border-bottom: 1px solid var(--line);
  align-items: baseline;
}

.pricing-key {
  font-family: var(--display);
  font-size: 1.05rem;
  font-weight: 700;
  color: var(--text);
}

.pricing-figure {
  font-family: var(--display);
  font-size: 1.9rem;
  font-weight: 700;
  letter-spacing: -0.03em;
  color: var(--signal);
  line-height: 1.1;
}

.pricing-figure small {
  font-size: 0.95rem;
  font-weight: 400;
  letter-spacing: 0;
  color: var(--muted);
}

.pricing-note {
  grid-column: 2;
  font-size: 0.92rem;
  color: var(--text-2);
  line-height: 1.6;
  max-width: 46ch;
}

/* Both-packages strip under the package grid: the deal sits with the CTA
   so price and next step are one glance apart. */
.bundle-strip {
  margin-top: 1.5rem;
  background: var(--slate-2);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1.6rem 1.5rem 1.7rem;
  display: grid;
  gap: 1.25rem;
}

.bundle-strip h3 {
  margin: 0 0 0.6rem;
}

.bundle-strip p {
  margin: 0;
}

.bundle-cta .urgency-promise {
  margin-top: 0.7rem;
}

@media (min-width: 900px) {
  .bundle-strip {
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
    gap: 2.5rem;
  }
}

.pricing-foot {
  margin-top: 1.75rem;
  font-size: 0.95rem;
  color: var(--text-2);
  line-height: 1.65;
  max-width: 52ch;
}

/* "Klarna" lifted out of the pricing footnote. Teal (--signal), not Klarna's
   own pink: the site's colour rule is that --signal marks a fact the visitor
   can verify — Klarna really does appear at checkout — and borrowing a
   third-party brand colour would be the only off-palette hue on the page. */
.klarna-mark {
  color: var(--signal);
  font-weight: 600;
  background: var(--signal-soft);
  padding: 0.1em 0.4em;
  border-radius: 5px;
}

@media (max-width: 860px) {
  .pricing-layout {
    grid-template-columns: 1fr;
    gap: 1rem;
  }

  .pricing-graphic-slot {
    order: -1;
  }

  .pricing-graphic {
    max-width: 300px;
  }

  .pricing-row {
    grid-template-columns: 1fr;
    gap: 0.3rem;
  }

  .pricing-note {
    grid-column: 1;
  }

  .pricing-figure {
    font-size: 1.6rem;
  }
}

/* Reference-builds band. Its heading is left-aligned (.band-head--wide),
   so this cannot reuse .cta-actions — that one centres, which is correct
   inside .cta-band where the heading is centred too. */

.refbuilds-actions {
  display: flex;
  gap: 0.75rem;
  flex-wrap: wrap;
  margin-top: 2rem;
}

/* Homepage variant of the /portfolio demo carousel. Same .demo-card, laid
   out as a static grid instead of a scrolling flex track: three cards fit a
   desktop row here, and a carousel that never needs scrolling is a control
   that does nothing. The width override is required because .demo-card
   carries the carousel's fixed `width: min(380px, 82vw)`, which would
   otherwise stop the grid tracks from sizing themselves. */
.demo-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 1.5rem;
  margin-top: 2.5rem;
}

.demo-grid .demo-card {
  width: auto;
}

@media (max-width: 900px) {
  .demo-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

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

/* On /durham the proof panel sits in a band rather than a hero column, so
   nothing else is constraining its width. Presentation only — the panel's
   markup, metrics, and main.js hooks are untouched. */
.proof--standalone {
  max-width: 30rem;
  margin-top: 2.75rem;
}

/* --------------------------------------------------------------------------
   Landmark plate — a local reference point on the /durham page.

   Holds either the hand-drawn bull (today) or a licensed photograph of the
   real sculpture (once one exists). The :has() rule is what makes that a
   one-element swap: drawn art wants breathing room inside the frame, a
   photograph wants to fill it edge to edge, and neither needs the markup
   around it to change.
   -------------------------------------------------------------------------- */

.landmark {
  margin: 0 0 3.5rem;
  /* The source photo is 426px wide and that is all there is — it is the only
     freely-licensed image of this sculpture that exists. Capping the frame
     below that width keeps it above 1:1 on a normal display instead of
     upscaling into mush. */
  max-width: 21rem;
}

/* In the hero the landmark takes the column the proof panel holds on other
   pages. 26rem is 416px — still under the source's 426px, so it never
   upscales, and it fills the column convincingly at the same time. */
.landmark--hero {
  margin: 0;
  max-width: 26rem;
}

.landmark-frame {
  background: var(--ink);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
}

.landmark-frame:has(svg) {
  padding: 1.5rem 1.75rem;
}

.landmark-frame svg,
.landmark-frame img {
  display: block;
  width: 100%;
  height: auto;
}

.landmark figcaption {
  margin-top: 0.85rem;
  font-size: 0.8rem;
  line-height: 1.55;
  color: var(--muted);
}

/* Attribution is a licence condition, so it has to be legible — quieter than
   the caption, but not hidden. */
.landmark-credit {
  display: block;
  margin-top: 0.45rem;
  font-size: 0.72rem;
  line-height: 1.5;
  opacity: 0.85;
}

/* --------------------------------------------------------------------------
   Testimonial — and the honest placeholder standing in for one.

   /durham has no Durham client behind it yet, so the slot is left visibly
   empty and labelled as such rather than filled with something invented.
   The --empty modifier is what carries that: a dashed rule and muted type,
   so it reads as reserved space at a glance instead of as a real quote.
   Drop the modifier when a real one lands and the block becomes a normal
   testimonial with no other markup changes.

   The accent follows the site's colour rule — teal only once there is
   something true to point at, so an empty slot doesn't get it.
   -------------------------------------------------------------------------- */

.testimonial {
  margin: 0;
  max-width: 44rem;
  padding: 1.9rem 2.1rem;
  background: var(--slate-2);
  border: 1px solid var(--line);
  border-left: 3px solid var(--signal);
  border-radius: var(--radius);
}

.testimonial-mark {
  display: block;
  font-family: var(--mono);
  font-size: 0.68rem;
  font-weight: 500;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 1rem;
}

.testimonial p {
  font-family: var(--display);
  font-size: 1.25rem;
  line-height: 1.38;
  letter-spacing: -0.02em;
  color: var(--text-2);
}

.testimonial-foot {
  margin-top: 1.15rem;
  font-size: 0.85rem;
  color: var(--muted);
}

@media (max-width: 620px) {
  .testimonial {
    padding: 1.4rem 1.5rem;
  }

  .testimonial p {
    font-size: 1.1rem;
  }
}

.reviews {
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
}

.reviews[hidden] {
  display: none;
}

.reviews-teaser {
  text-align: center;
  padding: 0.9rem var(--gutter);
  background: var(--slate);
  border-bottom: 1px solid var(--line);
  font-size: 0.9rem;
}

.reviews-teaser a {
  color: var(--text-2);
}
