/* ═══════════════════════════════════════════════════════════════════════
   webpage.contact: the homepage only.

   Loaded after styles.css by index.html, and by the pages that borrow the
   homepage's hero and pulsing form (the "what it should cost" guide), so
   nothing here can move a case study or the price list. Every class is
   prefixed h- for the same reason: the shared sheet already owns .hero,
   .path, .ticks and friends on other pages.

   What this page is trying to be: a plain, well-set page by one person who
   builds websites. It says what it sells in the first line, shows real work
   before it asks for anything, and asks for one thing (the free mock-up) in
   one voice. No pill badges, no stat strips, no grey half-headlines, no
   pictures of software that isn't there.
   ══════════════════════════════════════════════════════════════════════ */

.h-page {
  --h-surface: #12151b;
  --h-rule: #232833;
  --h-soft: #b3b6bd;      /* body on ink: 9:1, a notch warmer than --muted */
}

/* Section rhythm: generous above a heading, tight below it. Grounds
   alternate between ink and a slightly raised surface, so sections separate
   without a border drawn around every one of them. */
.h-sect { padding-block: clamp(2.75rem, 6vw, 4.75rem); }
.h-sect--raised { background: var(--h-surface); border-block: 1px solid var(--h-rule); }

.h-title {
  font-size: clamp(1.9rem, 1.2rem + 2.4vw, 2.9rem);
  letter-spacing: -.035em; line-height: 1.04;
  max-width: 22ch;
}
.h-intro {
  margin: 1rem 0 0; max-width: 58ch;
  font-size: clamp(1.02rem, .96rem + .25vw, 1.14rem); line-height: 1.6;
  color: var(--h-soft);
}
.h-head { margin-bottom: clamp(2rem, 4vw, 3rem); }
.h-head--split { display: flex; flex-wrap: wrap; align-items: end; justify-content: space-between; gap: 1rem 3rem; }

/* ── Hero ─────────────────────────────────────────────────────────────────
   The /free-mockup/ layout (centred headline fading toward the bottom, the
   client build framed underneath with a glow behind it) on a full-screen
   version of 21st.dev's interactive ripple background. The demo's
   indigo-purple-pink wash read as off-brand here, so it is the brand navy
   instead, lit from the top. It fills at least the whole first screen and
   fades into the page at the bottom. */
.h-hero {
  position: relative; overflow: hidden; isolation: isolate;
  min-height: calc(100svh - 4.5rem);
  padding-block: clamp(3rem, 8vw, 5.5rem) 0;
  text-align: center;
  background:
    linear-gradient(to bottom, transparent 70%, var(--bg)),
    radial-gradient(ellipse 80% 60% at 50% 0%, rgba(27, 58, 107, .55), transparent 70%),
    linear-gradient(135deg, #0f1a2e 0%, #13213d 50%, #0f1626 100%);
}
.h-hero > .wrap { position: relative; z-index: 1; }
/* On an inner page the hero is a header, not a full screen. */
.h-hero--page { min-height: 0; padding-block: clamp(2.75rem, 7vw, 5rem) clamp(2.5rem, 6vw, 4rem); }

/* A header drawn by the mesh shader (mesh.js) instead of the ripples, on
   /work/ and the bots page: the canvas under the text, darkened a touch
   for contrast and faded into the page at the bottom. */
.h-hero--mesh { background: #0f1a2e; }
.h-mesh { position: absolute; inset: 0; width: 100%; height: 100%; z-index: 0; display: block; }
.h-hero--mesh::after { content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background: linear-gradient(to bottom, rgba(8, 12, 22, .18), rgba(8, 12, 22, .34) 62%, var(--bg)); }

/* Effects the owner switched off in /admin/ (the middleware adds these
   classes to <html>). The scripts check the same classes and never start. */
.fx-off-ripple .h-ripples, .fx-off-ripple .h-mesh, .fx-off-pulse .h-pulse { display: none; }
/* Ring off: the three builds as a still row (the ring's own no-script look). */

/* ── Samples: pick your trade, see your page ───────────────────────────
   A phone still of the sample page beside a short note on it, under a row
   of trade tabs. The still is a tall capture cropped by the frame, so it
   shows the hero and the first services; the full page is one tap away.
   Tabs are links, so with no script they still go somewhere. */
.h-samp-tabs { display: flex; flex-wrap: wrap; gap: .5rem; margin-bottom: clamp(1.4rem, 3vw, 2.2rem); }
.h-samp-tabs a {
  display: inline-flex; align-items: center; min-height: 40px; padding: .5rem 1rem;
  border: 1.5px solid var(--line-2); border-radius: 99px;
  font-family: var(--display); font-weight: 600; font-size: .92rem; color: var(--h-soft);
  text-decoration: none; transition: border-color .15s ease, color .15s ease, background .15s ease;
}
.h-samp-tabs a:hover { color: var(--text); border-color: var(--text); }
.h-samp-tabs a[aria-selected="true"] { background: var(--text); border-color: var(--text); color: #14161a; }
/* The live builds carry a green dot; the samples don't. */
.h-samp-tabs a.is-live::before { content: ""; width: 7px; height: 7px; margin-right: .5rem; border-radius: 50%; background: #3ddc84; box-shadow: 0 0 0 3px rgba(61, 220, 132, .18); }
.h-samp-kind { margin: 0 0 .35rem; font-family: var(--display); font-size: .74rem; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); }
.h-samp-pane .h-samp-kind { max-width: none; }
.h-samp-body { display: grid; grid-template-columns: minmax(0, 300px) minmax(0, 1fr); gap: clamp(1.5rem, 4vw, 3.5rem); align-items: start; }
.h-samp-phone {
  position: relative; width: 100%; max-width: 300px; aspect-ratio: 390 / 720;
  border: 8px solid #1c2027; border-radius: 34px; overflow: hidden;
  background: #1c2027; box-shadow: 0 30px 60px -30px rgba(0, 0, 0, .8), 0 0 0 1px var(--h-rule);
}
.h-samp-phone img {
  position: absolute; inset: 0; width: 100%; height: auto; display: block; border-radius: 26px;
  opacity: 0; transition: opacity .3s ease;
}
.h-samp-phone img.is-on { opacity: 1; }
.h-samp-copy { padding-top: .4rem; }
.h-samp-pane h3 { margin: 0 0 .5rem; font-size: clamp(1.35rem, 1.1rem + 1vw, 1.8rem); letter-spacing: -.025em; }
.h-samp-pane p { margin: 0 0 1rem; max-width: 46ch; color: var(--h-soft); font-size: 1.05rem; line-height: 1.55; }
.h-samp-open { font-family: var(--display); font-weight: 700; color: var(--blue); text-decoration: none; }
.h-samp-open::after { content: " \2197"; }
.h-samp-open:hover { text-decoration: underline; }
.h-samp-list { margin: 1.8rem 0 0; padding: 0; list-style: none; display: grid; gap: .45rem; max-width: 46ch; color: var(--text); font-size: .98rem; }
.h-samp-list li { position: relative; padding-left: 1.4rem; }
.h-samp-list li::before { content: ""; position: absolute; left: 0; top: .58em; width: 9px; height: 9px; border-radius: 50%; background: var(--blue); }
.h-samp-cta { margin: 1.6rem 0 0; color: var(--h-soft); max-width: 46ch; }
.h-samp-cta a { color: var(--text); }
@media (max-width: 700px) {
  .h-samp-body { grid-template-columns: 1fr; }
  .h-samp-phone { max-width: 250px; margin-inline: auto; }
  .h-samp-copy { text-align: left; }
}

/* ── Glow behind the primary CTA ────────────────────────────────────────
   A soft ring that breathes behind the one button on the page that matters.
   Pure CSS: a blurred pseudo-element pulsing opacity and scale. Sits behind
   the button (z-index), so it never steals the click. */
.h-glow-cta{position:relative;isolation:isolate}
.h-glow-ring{
  position:absolute;inset:-14px;z-index:-1;border-radius:14px;
  background:radial-gradient(ellipse at center, rgba(236,231,221,.55), transparent 72%);
  filter:blur(10px);
  animation:h-glow-pulse 2.6s ease-in-out infinite;
}
@keyframes h-glow-pulse{
  0%,100%{opacity:.45;transform:scale(.94)}
  50%{opacity:.85;transform:scale(1.08)}
}
@media (prefers-reduced-motion: reduce){.h-glow-ring{animation:none;opacity:.6}}

/* ── Ticker: a strip that never stops saying the offer ─────────────────
   The track holds the phrase list twice; translating by exactly -50% of the
   track's own width loops it with no visible seam, so only one @keyframes
   is needed regardless of how many phrases are in the list. */
.h-ticker{
  position:relative; overflow:hidden; background:var(--h-surface);
  border-block:1px solid var(--h-rule); padding-block:14px;
}
.h-ticker-track{
  display:flex; width:max-content; gap:2rem; align-items:center;
  font-family:var(--display); font-weight:700; font-size:.92rem; letter-spacing:-.01em;
  color:var(--h-soft); white-space:nowrap;
  animation:h-ticker-scroll 26s linear infinite;
}
.h-ticker-track span{display:inline-flex}
.h-ticker-dot{width:5px;height:5px;border-radius:50%;background:var(--blue);flex:none;opacity:.7}
@keyframes h-ticker-scroll{from{transform:translateX(0)}to{transform:translateX(-50%)}}
.h-ticker:hover .h-ticker-track{animation-play-state:paused}
@media (prefers-reduced-motion: reduce){.h-ticker-track{animation:none}}

/* ── The check: a small terminal that types out a real verdict ────────
   Monospace, dark, a fake window chrome (three dots) for the "this is a
   tool running" read. JS in main.js drives the typing; CSS only supplies
   the blinking cursor and the frame, so the no-script state (the line as
   written in the HTML) is already a complete, true sentence. */
.h-check-wrap{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,26rem);gap:clamp(2rem,5vw,4rem);align-items:center}
.h-check-eyebrow{margin:0 0 .6rem;font-family:var(--display);font-size:.78rem;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:var(--blue)}
.h-check-term{
  border-radius:12px; overflow:hidden; background:#0a0e17; border:1px solid var(--h-rule);
  box-shadow:0 30px 60px -30px rgba(0,0,0,.7);
}
.h-check-bar{display:flex;gap:6px;padding:11px 14px;background:#10141d;border-bottom:1px solid var(--h-rule)}
.h-check-bar span{width:9px;height:9px;border-radius:50%;background:#333a48}
.h-check-body{padding:24px 22px 28px;min-height:6.5rem}
.h-check-line{margin:0;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:1rem;color:#d8dbe4}
.h-check-prompt{color:var(--blue);font-weight:700}
.h-check-cursor{display:inline-block;width:.55ch;height:1.05em;margin-left:1px;background:var(--blue);vertical-align:text-bottom;animation:h-check-blink 1s step-end infinite}
.h-check-result{margin:.9rem 0 0;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:.95rem;color:#7fd9a6;min-height:1.3em;opacity:0;transition:opacity .35s ease}
.h-check-result.is-on{opacity:1}
.h-check-result[data-neutral="1"]{color:#e8b34a}
@keyframes h-check-blink{50%{opacity:0}}
@media (prefers-reduced-motion: reduce){.h-check-cursor{animation:none}}
@media (max-width:800px){.h-check-wrap{grid-template-columns:1fr}}

/* ── How it works: three numbered cards, before the price ─────────────── */
.h-steps { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: clamp(.9rem, 2vw, 1.4rem); counter-reset: none; }
.h-steps li { position: relative; padding: clamp(1.3rem, 2.4vw, 1.8rem); border: 1px solid var(--h-rule); border-radius: 14px; background: var(--h-surface); }
.h-step-n { display: inline-grid; place-items: center; width: 2.1rem; height: 2.1rem; margin-bottom: 1rem; border-radius: 50%; background: var(--blue); color: #0e1014; font-family: var(--display); font-weight: 800; font-size: 1rem; }
.h-steps h3 { margin: 0 0 .45rem; font-size: 1.2rem; letter-spacing: -.02em; }
.h-steps p { margin: 0; color: var(--h-soft); font-size: 1rem; line-height: 1.55; }
.h-steps a { color: var(--text); }
@media (max-width: 760px) { .h-steps { grid-template-columns: 1fr; } }

/* ── Reviews: filled from the site settings once there are any ───────── */
.h-reviews { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr)); gap: clamp(1rem, 2vw, 1.5rem); }
.h-review { margin: 0; padding: clamp(1.2rem, 2.4vw, 1.6rem); border: 1px solid var(--line-2); border-radius: 6px; background: var(--h-surface); display: flex; flex-direction: column; gap: 1rem; }
.h-review blockquote { margin: 0; color: var(--text); font-size: 1.02rem; line-height: 1.6; }
.h-review figcaption { margin-top: auto; display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: .3rem 1rem; font-family: var(--display); font-size: .9rem; }
.h-review figcaption a { color: var(--blue); text-decoration: none; font-weight: 600; }
.h-review figcaption a:hover { text-decoration: underline; }

/* Ripples: a ring set wherever the hero is pressed, plus an ambient one every
   three seconds (main.js). They sit above the content so they read across
   the whole screen, and take no clicks. Grown with transform rather than
   width/height so they never cause layout; with reduced motion the script
   never starts. */
.h-ripples { position: absolute; inset: 0; z-index: 2; pointer-events: none; }
.h-ripple { position: absolute; width: 0; height: 0; }
.h-ripple i {
  position: absolute; left: -200px; top: -200px; width: 400px; height: 400px;
  border-radius: 50%; border: 2px solid rgba(110, 155, 232, .6);
  transform: scale(0); animation: h-ripple 2s ease-out forwards;
}
.h-ripple i:nth-child(2) { border-color: rgba(236, 231, 221, .32); animation-delay: .2s; }
.h-ripple i:nth-child(3) { border-color: rgba(150, 185, 245, .28); animation-delay: .4s; }
@keyframes h-ripple { from { transform: scale(0); opacity: 1; } to { transform: scale(1); opacity: 0; } }

.h-hero h1 {
  max-width: 34ch; margin-inline: auto;
  font-size: clamp(1.9rem, 4.3vw, 3.1rem);
  font-weight: 700; line-height: 1.12; letter-spacing: -.03em;
  background: linear-gradient(to bottom, var(--text) 35%, color-mix(in srgb, var(--text) 55%, transparent));
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent; color: transparent;
}
.h-hero h1 strong { font-weight: 800; }
.h-lede {
  margin: 1.3rem auto 0; max-width: 58ch;
  font-size: clamp(1.05rem, .98rem + .35vw, 1.2rem); line-height: 1.55;
  color: var(--h-soft);
}
.h-lede strong { color: var(--text); font-weight: 600; }
.h-whoami {
  margin: .7rem auto 0; max-width: 54ch;
  font-size: .92rem; line-height: 1.55;
  color: color-mix(in srgb, var(--h-soft) 78%, transparent);
}
.h-actions { display: flex; flex-wrap: wrap; justify-content: center; align-items: center; gap: .9rem 1.4rem; margin-top: 1.9rem; }
.h-actions .btn { font-size: 1rem; padding: .95rem 1.6rem; }
.btn-glow {
  border-radius: 8px;
  background: linear-gradient(to bottom, #fff, #f6f2ea 45%, color-mix(in srgb, var(--hot) 70%, var(--bg)));
  color: #14161a;
  box-shadow: 0 10px 30px -12px rgba(236, 231, 221, .45);
  transition: transform .2s ease, box-shadow .2s ease;
}
.btn-glow:hover { transform: scale(1.04); box-shadow: 0 14px 36px -12px rgba(236, 231, 221, .6); }
.btn-glow:active { transform: scale(.97); }
/* The one secondary action: an outlined button, clearly quieter than the
   cream primary beside it. Phone is how most local customers reach a trade,
   so it stays in the hero, but it doesn't compete for the eye. */
.h-call {
  display: inline-flex; align-items: center; justify-content: center; min-height: 44px;
  padding: .85rem 1.3rem; border: 1.5px solid var(--line-2); border-radius: 8px;
  font-family: var(--display); font-weight: 700; font-size: 1rem; color: var(--text);
  text-decoration: none;
}
.h-call:hover { border-color: var(--text); }
.h-assure {
  margin: 1.4rem 0 0; display: flex; flex-wrap: wrap; justify-content: center; gap: .4rem 1.4rem;
  font-family: var(--display); font-size: .88rem; color: var(--muted);
  list-style: none; padding: 0;
}
.h-assure li { display: inline-flex; align-items: center; gap: .45rem; }
.h-assure svg { width: 15px; height: 15px; flex: none; color: var(--blue); }

/* The three builds on a turning ring, after 21st.dev's circular gallery.
   main.js moves each card around a circle (translate3d, a slight turn
   toward the centre, dimmer at the back) rather than spinning a cylinder,
   so the back cards never show their screenshots mirrored. The glow behind
   is a radial gradient where the template used a PNG off a CDN. */
.h-show { position: relative; max-width: 1040px; margin: clamp(2.5rem, 6vw, 4rem) auto 0; padding-bottom: clamp(2.5rem, 6vw, 4.5rem); }
.h-show::before {
  content: ''; position: absolute; z-index: 0; pointer-events: none;
  left: 50%; top: -22%; width: 92%; height: 75%; transform: translateX(-50%);
  background: radial-gradient(ellipse at 50% 60%, rgba(110, 155, 232, .4), rgba(110, 155, 232, .1) 45%, transparent 70%);
  filter: blur(24px);
}
.h-ring {
  --cw: clamp(230px, 38vw, 470px);
  position: relative; z-index: 1;
  height: calc(var(--cw) * .574 * 1.4);
  perspective: 1800px;
}
.h-ring-card {
  position: absolute; left: 50%; top: 50%;
  width: var(--cw); aspect-ratio: 1568 / 900;
  margin: calc(var(--cw) * -.287) 0 0 calc(var(--cw) / -2);
  border-radius: 10px; overflow: hidden;
  border: 1px solid var(--line-2); background: var(--panel);
  box-shadow: 0 40px 70px -35px rgba(0, 0, 0, .95);
  color: #fff; text-decoration: none; text-align: left;
  will-change: transform, opacity;
}
.h-ring-card img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; object-position: top left; }
/* A solid band, not a fade: the screenshots have their own text (buttons,
   headlines) at the bottom, and through a fade the two read as one jumble.
   Only the front card shows its caption (main.js sets --front, 1 at the
   front and 0 at the back), so the back cards' captions never peek out
   beside the front one. */
.h-ring-cap {
  position: absolute; left: 0; right: 0; bottom: 0; display: grid; gap: .1rem;
  padding: .7rem 1rem .75rem;
  background: #0b111f;
  border-top: 1px solid rgba(255, 255, 255, .08);
  opacity: clamp(0, (var(--front, 1) - .8) * 5, 1);
}
.h-ring-cap strong { font-family: var(--display); font-size: 1.05rem; letter-spacing: -.02em; }
.h-ring-cap em { font-style: normal; font-size: .85rem; opacity: .8; }
.h-ring-card:focus-visible { outline: 2px solid var(--blue); outline-offset: 3px; }
/* Before main.js runs (or if it never does): a plain row. */
.h-ring:not(.is-ready) { display: flex; justify-content: center; gap: 1rem; height: auto; }
.h-ring:not(.is-ready) .h-ring-card { position: relative; left: auto; top: auto; margin: 0; width: calc(33.3% - 1rem); }
.h-show-cap {
  position: relative; z-index: 1; margin: .9rem 0 0; font-family: var(--display); font-size: .84rem; line-height: 1.4; color: var(--muted);
}
.h-show-cap a { color: var(--text); font-weight: 700; text-decoration: none; border-bottom: 1.5px solid var(--blue); }
/* On a phone the ring squeezed three cards into a 230px jumble, on the
   screen most visitors use. Under 700px main.js leaves the ring alone and
   this makes the plain row a swipeable one: each card most of the screen
   wide, snapping to centre, its caption always showing. */
@media (max-width: 700px) {
  .h-ring, .h-ring:not(.is-ready) {
    display: flex; justify-content: flex-start; gap: .8rem; height: auto;
    margin-inline: -1rem; padding: .3rem 1rem 1rem;
    overflow-x: auto; scroll-snap-type: x mandatory; -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }
  .h-ring::-webkit-scrollbar { display: none; }
  .h-ring .h-ring-card, .h-ring:not(.is-ready) .h-ring-card {
    position: relative; left: auto; top: auto; margin: 0; flex: none;
    width: min(82vw, 360px); scroll-snap-align: center;
    transform: none; opacity: 1;
  }
  .h-ring .h-ring-card:first-child { margin-left: calc((100vw - min(82vw, 360px)) / 2 - 1rem); }
  .h-ring .h-ring-card:last-child { margin-right: calc((100vw - min(82vw, 360px)) / 2 - 1rem); }
  .h-ring-cap { opacity: 1; }
  .h-ring .h-ring-cap em { display: block; }
  .h-show::before { display: none; }
}

/* ── Pricing ──────────────────────────────────────────────────────────── */
.h-plans { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: clamp(1rem, 2vw, 1.5rem); }
.h-plan {
  position: relative; display: flex; flex-direction: column;
  padding: clamp(1.4rem, 2.6vw, 2.1rem);
  border: 1px solid var(--line-2); border-radius: 6px; background: var(--bg);
}
.h-plan--pick { border-color: color-mix(in srgb, var(--blue) 55%, var(--line-2)); box-shadow: 0 0 0 1px color-mix(in srgb, var(--blue) 30%, transparent) inset, 0 24px 50px -30px rgba(0,0,0,.9); }
.h-plan-tag {
  position: absolute; top: 0; right: clamp(1.4rem, 2.6vw, 2.1rem); transform: translateY(-50%);
  font-family: var(--display); font-weight: 700; font-size: .72rem; letter-spacing: .08em; text-transform: uppercase;
  background: var(--blue-deep); color: var(--text); border: 1px solid color-mix(in srgb, var(--blue) 60%, transparent);
  padding: .35rem .6rem; border-radius: 3px;
}
.h-plan h3 { font-size: 1.05rem; color: var(--muted); font-weight: 700; }
.h-price { margin: .5rem 0 0; font-family: var(--display); font-weight: 800; letter-spacing: -.045em; line-height: 1; font-size: clamp(2.6rem, 2rem + 2vw, 3.4rem); }
.h-price small { font-size: .36em; font-weight: 600; letter-spacing: -.01em; color: var(--muted); margin-left: .3rem; }
.h-plan-sum { margin: .8rem 0 0; color: var(--h-soft); }
.h-plan ul { list-style: none; margin: 1.3rem 0 1.6rem; padding: 1.2rem 0 0; border-top: 1px solid var(--h-rule); display: grid; gap: .6rem; }
.h-plan li { position: relative; padding-left: 1.5rem; color: var(--text); font-size: .98rem; }
.h-plan li::before {
  content: ""; position: absolute; left: .1rem; top: .5em; width: .55rem; height: .3rem;
  border-left: 2px solid var(--blue); border-bottom: 2px solid var(--blue); transform: rotate(-45deg);
}
.h-plan-cta { margin-top: auto; display: grid; gap: .7rem; }
.h-plan-cta .btn { width: 100%; font-size: .98rem; padding: .9rem 1.2rem; }
.h-plan-alt { text-align: center; font-family: var(--display); font-size: .88rem; color: var(--muted); }
.h-plan-alt a { color: var(--text); font-weight: 700; }
/* The buy button: outlined, so it is plainly a different action from the
   cream mock-up button, not a second copy of it. main.js points it at a
   checkout link, a text (phone) or an email (computer). */

.h-extras { margin: 1.5rem 0 0; font-size: .94rem; color: var(--muted); line-height: 1.7; }
.h-extras a { color: var(--text); }

/* ── The ask: how it goes in three lines, beside the one form ────────── */
.h-ask { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 26rem); gap: clamp(2rem, 6vw, 5rem); align-items: center; }
.h-ask h2 { font-size: clamp(2rem, 1.3rem + 2.6vw, 3.2rem); letter-spacing: -.04em; line-height: 1; max-width: 15ch; }
.h-how-lede { margin: 1.2rem 0 0; color: var(--h-soft); max-width: 46ch; }
.h-how-lede strong { color: var(--text); font-weight: 600; }
.h-how-lede a { color: var(--blue); }
.h-how { margin: 1.5rem 0 0; padding-left: 1.3rem; display: grid; gap: .5rem; color: var(--h-soft); font-size: 1.02rem; }
.h-how li::marker { font-family: var(--display); font-weight: 800; color: var(--blue); }
.h-how strong { color: var(--text); font-weight: 600; }
.h-ask-alt { margin: 1.6rem 0 0; font-size: 1rem; color: var(--muted); }
.h-ask-alt a { color: var(--text); font-family: var(--display); font-weight: 700; text-decoration: none; border-bottom: 2px solid var(--blue); }

/* The pulsing frame (pulse.js): a canvas laid over the form, overhanging it
   by 14px so the glow has room, taking no clicks. Without WebGL it stays
   blank and the form's own border does the job. */
.h-form-frame { position: relative; }
.h-pulse { position: absolute; inset: -14px; width: calc(100% + 28px); height: calc(100% + 28px); z-index: 1; pointer-events: none; display: block; }
.h-form {
  padding: clamp(1.25rem, 2.6vw, 1.9rem); border-radius: 6px;
  background: var(--h-surface); border: 1px solid var(--line-2);
  box-shadow: 0 30px 60px -36px rgba(0,0,0,.95);
}
.h-form .field label { font-size: .78rem; letter-spacing: .06em; color: var(--muted); }
.h-form .field input { background: var(--bg); padding: .85rem .9rem; font-size: 1rem; }
.h-form .btn { font-size: 1rem; padding: .95rem; margin-top: .35rem; }

/* ── Phones ───────────────────────────────────────────────────────────── */
@media (max-width: 900px) {
  .h-show-cap { font-size: .8rem; }
  .h-ring-cap { padding: .5rem .75rem .55rem; }
  .h-ring-cap strong { font-size: .92rem; }
  .h-ring-cap em { display: none; }
  .h-plans, .h-ask { grid-template-columns: 1fr; }
  .h-plan ul { margin: 1rem 0 1.2rem; padding-top: .9rem; }
  .h-plan--pick { order: -1; }
  .h-plan-tag { right: 1.4rem; }
}
@media (max-width: 560px) {
  .h-actions .btn, .h-actions .h-call { width: 100%; }
  .h-actions { gap: .9rem; }
}
@media (prefers-reduced-motion: reduce) {
  .btn-glow, .btn-glow:hover, .btn-glow:active { transform: none; }
}

/* ── Studio panel (homepage): a vanilla port of 21st.dev's "builder OS"
   bento, every figure real (see the comment in index.html). studio.js adds
   .st-anim (motion allowed), then .is-in once it scrolls into view; without
   script, or with reduced motion, everything simply shows. ─────────────── */
.st { --st-cell: #151920; --st-cell-hi: #191e27; --st-line: rgba(255, 255, 255, .07); --st-line-hi: rgba(255, 255, 255, .15); --st-good: #5fbf8f;
  border: 1px solid var(--st-line); border-radius: 20px; background: var(--bg); padding: clamp(.75rem, 1.6vw, 1.25rem); }
.st-top { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: .4rem 1rem; padding: .2rem .35rem 1rem; }
.st-id { margin: 0; display: flex; align-items: center; gap: .6rem; font-family: var(--display); font-size: .92rem; font-weight: 400; letter-spacing: normal; line-height: 1.3; }
.st-id strong { color: var(--text); font-weight: 700; }
.st-id span:last-child { color: var(--faint); }
.st-note { margin: 0; color: var(--faint); font-size: .84rem; }

.st-dot { position: relative; display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--st-good, #5fbf8f); flex: none; }
.st-dot::after { content: ""; position: absolute; inset: 0; border-radius: 50%; background: inherit; animation: st-ping 2.4s ease-out infinite; }
.st-dot--sm { width: 6px; height: 6px; }
@keyframes st-ping { from { transform: scale(1); opacity: .5; } to { transform: scale(2.6); opacity: 0; } }

.st-grid { display: grid; gap: .65rem; grid-template-columns: 1.7fr 1fr;
  grid-template-areas: "ship load" "ship last"; }
.st-ship { grid-area: ship; } .st-load { grid-area: load; } .st-last { grid-area: last; }

.st-cell { position: relative; display: flex; flex-direction: column; min-width: 0; min-height: 7.25rem; padding: 1.1rem;
  border: 1px solid var(--st-line); border-radius: 14px; background: var(--st-cell);
  transform: perspective(900px) rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg)) translateY(var(--lift, 0));
  transition: transform .35s cubic-bezier(.2, .8, .2, 1), background-color .16s ease, border-color .16s ease; }
.st-cell:hover { --lift: -2px; background: var(--st-cell-hi); border-color: var(--st-line-hi); }
.st-lab { margin: 0; display: flex; align-items: center; gap: .4rem; font-family: var(--display); font-weight: 600; font-size: .8rem; color: var(--faint); }
.st-lab svg { width: 13px; height: 13px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.st-cap { margin: auto 0 0; padding-top: .6rem; display: flex; align-items: center; gap: .45rem; font-size: .84rem; color: var(--faint); }
.st-cap--c { justify-content: center; text-align: center; }
.st-mid { margin: .5rem 0 0; font-style: italic; font-size: clamp(1.25rem, 1.05rem + .7vw, 1.6rem); line-height: 1.15; color: var(--text); }

/* Changes shipped: the big italic figure and a sparkline of changes a day. */
.st-big { margin: .6rem 0 0; display: flex; align-items: baseline; flex-wrap: wrap; gap: .3rem .75rem; }
.st-big [data-st-count] { font-style: italic; font-size: clamp(2.8rem, 2rem + 2.6vw, 3.9rem); line-height: .95; color: var(--text); font-variant-numeric: tabular-nums; }
.st-up { display: inline-flex; align-items: center; gap: .15rem; font-family: var(--display); font-weight: 600; font-size: .84rem; color: var(--blue); }
.st-up svg { width: 13px; height: 13px; fill: none; stroke: currentColor; stroke-width: 2.5; stroke-linecap: round; stroke-linejoin: round; }
/* Grows into whatever height the row gives it, never past it (an SVG
   with height:auto would size itself square from the viewBox). */
.st-spark { display: block; width: 100%; flex: 1 1 0; height: 0; min-height: 6rem; margin-top: .9rem; overflow: visible; }
.st-area { fill: var(--blue); opacity: .08; }
.st-line { fill: none; stroke: var(--blue); stroke-width: 1.6; vector-effect: non-scaling-stroke; stroke-linecap: round; stroke-linejoin: round; }

/* Page load ring: this visitor's own load, filled against a 4-second budget. */
.st-load { align-items: center; justify-content: center; }
.st-ring { position: relative; width: 5.5rem; height: 5.5rem; }
.st-ring svg { width: 100%; height: 100%; transform: rotate(-90deg); }
.st-ring circle { fill: none; stroke-width: 5; }
.st-ring-bg { stroke: var(--st-line); }
.st-ring-fg { stroke: var(--st-good); stroke-linecap: round; stroke-dasharray: 1; stroke-dashoffset: var(--off, .12); transition: stroke-dashoffset 1.25s cubic-bezier(.22, 1, .36, 1); }
.st-ring p { position: absolute; inset: 0; margin: 0; display: grid; place-content: center; text-align: center; line-height: 1.1; }
.st-ring strong { font-family: var(--display); font-size: 1.05rem; color: var(--text); font-variant-numeric: tabular-nums; }
.st-ring span { font-size: .7rem; color: var(--faint); }
.st-load .st-cap { margin-top: .4rem; padding-top: 0; }

/* What I've built / Recently shipped */

/* Motion: cells rise in one after another; the line draws; bars grow. */
.st-anim .st-cell { opacity: 0; transform: perspective(900px) translateY(14px) scale(.985); }
.st-anim .is-in .st-cell { opacity: 1; transform: perspective(900px) rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg)) translateY(var(--lift, 0));
  transition: opacity .55s cubic-bezier(.22, 1, .36, 1) calc(var(--i) * 55ms + 80ms), transform .55s cubic-bezier(.22, 1, .36, 1) calc(var(--i) * 55ms + 80ms), background-color .16s ease, border-color .16s ease; }
.st-anim .is-in.is-settled .st-cell { transition: transform .35s cubic-bezier(.2, .8, .2, 1), background-color .16s ease, border-color .16s ease; }
/* The line wipes in left to right (a dash-offset draw breaks on a
   non-scaling stroke in Chrome and stops short). */
.st-anim .st-spark { clip-path: inset(-10% 100% -10% 0); transition: clip-path 1.3s cubic-bezier(.22, 1, .36, 1) .35s; }
.st-anim .is-in .st-spark { clip-path: inset(-10% -2% -10% 0); }
.st-anim .st-area { opacity: 0; transition: opacity 1s ease .7s; }
.st-anim .is-in .st-area { opacity: .08; }

@media (max-width: 960px) {
  .st-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-areas: "ship ship" "last load"; }
}
@media (max-width: 560px) {
  .st-grid { grid-template-columns: 1fr; grid-template-areas: "ship" "last" "load"; }
  .st-note { width: 100%; }
}
@media (prefers-reduced-motion: reduce) {
  .st-dot::after { animation: none; }
  .st-cell, .st-ring-fg { transition: none; }
}
