/* Ospry Suite — site styles.
 *
 * The product's argument is that it does not dress estimates up as facts, and
 * the design has to carry that. So: no gradient heroes, no floating glass
 * cards, no invented dashboards. Plain structure, real screenshots, generous
 * space, and numbers only where a number is real.
 *
 * Written as one stylesheet rather than a framework because the whole site is
 * a handful of static pages. A build step would add a step between writing a
 * blog post and it being live, which is the exact friction this replaces.
 */

/* ---------- reset ---------- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
body { margin: 0; }
img { max-width: 100%; height: auto; display: block; }
a { color: inherit; }

/* ---------- tokens ----------
 * Neutrals pulled slightly toward the teal so the greys read as chosen
 * rather than inherited from a reset. */
:root {
  --paper:      #F1F4F3;
  --panel:      #FFFFFF;
  --sunk:       #E7ECEA;
  --ink:        #111817;
  --soft:       #414D4B;
  /* Was #6E7C79, which measured 4.4:1 on --paper. AA wants 4.5, and
     the gap is exactly what reads as faded on a phone outdoors. */
  --muted:      #5E6D6A;
  --rule:       #DCE3E1;
  --rule-firm:  #C6D0CD;
  --teal:       #0A7F77;
  --teal-deep:  #075E58;
  --teal-wash:  #E2F0EE;
  /* What goes on top of --teal. It has to be defined per theme:
     the accent is dark in light mode and pale in dark mode, so a
     fixed white sits at 1.7:1 on half the site. */
  --on-teal:    #FFFFFF;
  /* A second accent used only to mark what is NOT built. Honest labelling
     earns its own colour; sharing the brand colour would make the
     distinction decorative instead of informative. */
  --clay:       #9E4F2C;
  --clay-wash:  #F6EAE4;

  --measure: 68ch;
  --gap: clamp(1rem, 2.5vw, 1.75rem);
  --pad-y: clamp(3rem, 8vw, 6rem);

  --sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --serif: "Fraunces", Georgia, "Times New Roman", serif;
}

@media (prefers-color-scheme: dark) {
  :root {
    --paper: #0E1416; --panel: #161D1F; --sunk: #1A2325;
    --ink: #E7EEEC; --soft: #AFBEBB; --muted: #7B8A87;
    --rule: #232D2F; --rule-firm: #33403F;
    --teal: #43DDC9; --teal-deep: #7FE9DA; --teal-wash: #10302D;
  --on-teal: #07201D;
    --clay: #E29068; --clay-wash: #33201A;
  }
}

/* ---------- base ---------- */
body {
  background: var(--paper);
  color: var(--ink);
  font: 400 17px/1.65 var(--sans);
  -webkit-font-smoothing: antialiased;
}

.wrap { width: min(1120px, 100% - 2.5rem); margin-inline: auto; }
.narrow { width: min(720px, 100% - 2.5rem); margin-inline: auto; }

section { padding-block: var(--pad-y); }

h1, h2, h3 { font-family: var(--serif); font-weight: 400; line-height: 1.15;
             text-wrap: balance; margin: 0 0 0.6em; letter-spacing: -0.01em; }
h1 { font-size: clamp(2.2rem, 5.5vw, 3.6rem); }
h2 { font-size: clamp(1.6rem, 3.6vw, 2.4rem); }
h3 { font-size: clamp(1.15rem, 2vw, 1.4rem); }

p { margin: 0 0 1.15em; max-width: var(--measure); }
.lede { font-size: clamp(1.05rem, 1.8vw, 1.25rem); color: var(--soft); }

.eyebrow {
  font: 600 0.72rem/1 var(--sans);
  letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--teal); margin: 0 0 1rem;
}

/* ---------- header ---------- */
.masthead {
  position: sticky; top: 0; z-index: 50;
  background: color-mix(in srgb, var(--paper) 88%, transparent);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--rule);
}
.masthead .wrap {
  display: flex; align-items: center; gap: 1.5rem;
  padding-block: 0.9rem;
}
.brand {
  display: inline-flex; align-items: center; gap: 0.55rem;
  font: 700 1.05rem/1 var(--sans); letter-spacing: -0.02em;
  text-decoration: none; color: var(--ink);
}
.brand .mark {
  width: 26px; height: 26px; border-radius: 7px;
  background: var(--teal); display: grid; place-items: center;
  color: #fff; font-size: 0.8rem; font-weight: 800;
}
.nav { margin-left: auto; display: flex; align-items: center; gap: 1.4rem; }
/* :not(.btn) matters. `.nav a` is a class plus an element, which outranks the
   single class on `.btn-primary`, so without this the nav colour wins and the
   call to action renders dark grey on teal — unreadable, and invisible in a
   quick glance at the page because the button shape still looks right. */
.nav a:not(.btn) {
  font: 500 0.92rem/1 var(--sans); text-decoration: none;
  color: var(--soft); transition: color .15s;
}
.nav a:not(.btn):hover,
.nav a:not(.btn):focus-visible { color: var(--teal); }
@media (max-width: 720px) { .nav .hide-sm { display: none; } }

/* ---------- buttons ---------- */
.btn {
  display: inline-flex; align-items: center; gap: 0.5rem;
  padding: 0.72rem 1.25rem; border-radius: 9px;
  font: 600 0.95rem/1 var(--sans); text-decoration: none;
  border: 1px solid transparent; cursor: pointer;
  transition: background .15s, border-color .15s, color .15s;
}
.btn-primary { background: var(--teal); color: var(--on-teal); }
.btn-primary:hover { background: var(--teal-deep); }
.btn-ghost { border-color: var(--rule-firm); color: var(--ink); }
.btn-ghost:hover { border-color: var(--teal); color: var(--teal); }
:where(a, button):focus-visible { outline: 2px solid var(--teal); outline-offset: 3px; }

/* ---------- hero ---------- */
.hero { padding-block: clamp(3.5rem, 9vw, 7rem) clamp(2.5rem, 6vw, 4rem); }
.hero h1 { max-width: 16ch; }
.hero .lede { max-width: 52ch; }
.hero-actions { display: flex; flex-wrap: wrap; gap: 0.75rem; margin-top: 1.75rem; }
.hero-note { margin-top: 1.1rem; font-size: 0.87rem; color: var(--muted); }

/* ---------- the claim strip ---------- */
.claim {
  border-block: 1px solid var(--rule);
  background: var(--panel);
}
.claim-grid {
  display: grid; gap: var(--gap);
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
}
.claim-grid p { margin: 0; font-size: 0.95rem; color: var(--soft); }
.claim-grid strong { display: block; color: var(--ink); font-size: 1.02rem;
                     margin-bottom: 0.3rem; }

/* ---------- cards ---------- */
.grid { display: grid; gap: var(--gap); }
.grid-2 { grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }
.grid-3 { grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }

.card {
  background: var(--panel); border: 1px solid var(--rule);
  border-radius: 14px; padding: 1.5rem;
}
.card h3 { margin-bottom: 0.45rem; }
.card p { font-size: 0.95rem; color: var(--soft); margin-bottom: 0; }

/* ---------- shots ---------- */
.shot {
  border: 1px solid var(--rule-firm); border-radius: 12px;
  overflow: hidden; background: var(--panel);
  box-shadow: 0 18px 40px -28px rgba(17,24,23,.45);
}
.shot img { width: 100%; }
.shot figcaption {
  padding: 0.85rem 1.1rem; font-size: 0.87rem; color: var(--muted);
  border-top: 1px solid var(--rule);
}
figure { margin: 0; }

/* ---------- proof ---------- */
.proof {
  background: var(--ink); color: #EAF2F0;
  border-radius: 16px; padding: clamp(1.75rem, 4vw, 2.75rem);
}
.proof h2 { color: #fff; }
.proof p { color: #C3D2CF; }
.proof .figure {
  font-family: var(--serif); font-size: clamp(2.4rem, 6vw, 3.6rem);
  color: var(--teal); line-height: 1; margin: 0 0 0.3rem;
}
@media (prefers-color-scheme: dark) {
  .proof { background: var(--sunk); border: 1px solid var(--rule); }
}

/* ---------- honest table ---------- */
.table-scroll { overflow-x: auto; }
table { border-collapse: collapse; width: 100%; font-size: 0.94rem; }
th, td { text-align: left; padding: 0.8rem 1rem; border-bottom: 1px solid var(--rule); }
th { font: 600 0.78rem/1.3 var(--sans); letter-spacing: 0.06em;
     text-transform: uppercase; color: var(--muted); }
.tag {
  display: inline-block; padding: 0.2rem 0.55rem; border-radius: 999px;
  font: 600 0.72rem/1.4 var(--sans);
}
.tag-live { background: var(--teal-wash); color: var(--teal-deep); }
.tag-soon { background: var(--sunk); color: var(--muted); }
.tag-no   { background: var(--clay-wash); color: var(--clay); }

/* ---------- comparison table ----------
   Naming a competitor means the numbers have to be readable and the
   caveat has to be visible, so the row label carries weight and the
   source note is styled rather than tucked away. */
.cmp th[scope="row"] {
  text-transform: none; letter-spacing: 0; font-size: 0.94rem;
  color: var(--ink); font-weight: 600; white-space: nowrap;
}
.cmp tbody td { font-variant-numeric: tabular-nums; color: var(--soft); }
.cmp tbody tr:last-child td, .cmp tbody tr:last-child th { border-bottom: 0; }
.cmp thead th:last-child { color: var(--teal-deep); }
.cmp tbody td:last-child { color: var(--ink); font-weight: 500; }
.fineprint {
  margin-top: 1rem; font-size: 0.84rem; line-height: 1.6;
  color: var(--muted); max-width: 62ch;
}

/* ---------- blog ---------- */
.post-list { list-style: none; padding: 0; margin: 0; display: grid; gap: 0; }
.post-list li { border-bottom: 1px solid var(--rule); }
.post-list a {
  display: block; padding: 1.4rem 0; text-decoration: none;
}
.post-list a:hover h3 { color: var(--teal); }
.post-list h3 { margin-bottom: 0.3rem; transition: color .15s; }
.post-list p { margin: 0; font-size: 0.95rem; color: var(--soft); }
.post-meta { font-size: 0.82rem; color: var(--muted); margin-bottom: 0.4rem; }

article.post { padding-block: var(--pad-y); }
article.post h2 { margin-top: 2.2rem; }
article.post ul, article.post ol { max-width: var(--measure); padding-left: 1.2rem; }
article.post li { margin-bottom: 0.5rem; }
article.post blockquote {
  margin: 1.8rem 0; padding: 0.2rem 0 0.2rem 1.2rem;
  border-left: 3px solid var(--teal); color: var(--soft);
  font-size: 1.05rem;
}

/* ---------- cta ---------- */
.cta {
  background: var(--teal-wash); border: 1px solid color-mix(in srgb, var(--teal) 25%, transparent);
  border-radius: 16px; padding: clamp(1.75rem, 4vw, 2.75rem); text-align: center;
}
.cta p { margin-inline: auto; }
.cta .hero-actions { justify-content: center; }

/* ---------- footer ---------- */
.foot {
  border-top: 1px solid var(--rule); padding-block: 2.5rem;
  font-size: 0.9rem; color: var(--muted);
}
.foot .wrap { display: flex; flex-wrap: wrap; gap: 1.5rem; align-items: baseline; }
.foot a { color: var(--soft); text-decoration: none; }
.foot a:hover { color: var(--teal); }
.foot nav { margin-left: auto; display: flex; flex-wrap: wrap; gap: 1.2rem; }
/* Comparison pages do not belong in the main nav — putting "Hike SEO
   alternative" up there reads as defensive. The footer is the conventional
   home for them, and it gives the pages a site-wide internal link, which is
   most of what they need to rank. Its own row rather than a wide flex item
   in the nav row: simpler, and it cannot be squeezed onto the line above. */
.foot-compare {
  margin-top: 1.1rem; padding-top: 1rem;
  border-top: 1px solid var(--rule);
  font-size: 0.86rem; color: var(--muted);
  display: flex; flex-wrap: wrap; gap: 1rem; align-items: baseline;
}
.foot-compare a { color: var(--soft); }
.foot-compare a:hover { color: var(--teal); }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important;
                           scroll-behavior: auto !important; }
}

/* ===================================================================
   Motion and two-column layouts.

   Everything here is deliberately cheap. This site's whole argument is
   that it builds fast websites for trades, so a heavy hero effect would
   contradict the pitch on the page it decorates. The drifting mist is
   one canvas of soft blobs, it stops when scrolled out of view, and it
   disappears entirely for anyone who asks for reduced motion.
   =================================================================== */

/* ---------- atmospheric header ---------- */
.hero-atmos { position: relative; overflow: hidden; isolation: isolate; }
.hero-atmos > .wrap { position: relative; z-index: 1; }
#mist {
  position: absolute; inset: 0; width: 100%; height: 100%;
  z-index: 0; opacity: .55; pointer-events: none;
}
/* A wash under the canvas so the mist reads as weather over the page
   rather than as shapes floating on nothing. */
.hero-atmos::after {
  content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background: linear-gradient(180deg, transparent 55%, var(--paper) 100%);
}

/* ---------- scroll reveal ----------
   Starts visible. If the script never runs — blocked, failed, ancient
   browser — the page still reads normally, which matters more than the
   animation does. */
.reveal { opacity: 1; }
@media (prefers-reduced-motion: no-preference) {
  .js .reveal {
    opacity: 0; transform: translateY(14px);
    transition: opacity .55s ease, transform .55s ease;
  }
  .js .reveal.seen { opacity: 1; transform: none; }
}

/* ---------- two-column ----------
   The old layout ran text to about half the width and left the rest
   empty, which read as a document rather than a site. */
.split {
  display: grid; gap: clamp(1.5rem, 4vw, 3rem);
  grid-template-columns: 1fr; align-items: center;
}
@media (min-width: 860px) {
  .split { grid-template-columns: 1.05fr 1fr; }
  .split.flip > :first-child { order: 2; }
}
.split p { max-width: 54ch; }

/* ---------- work gallery ---------- */
.work-grid {
  display: grid; gap: var(--gap);
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
}
.work-item {
  border: 1px solid var(--rule); border-radius: 12px; overflow: hidden;
  background: var(--panel); text-decoration: none; color: inherit;
  transition: transform .25s ease, box-shadow .25s ease, border-color .25s;
}
.work-item:hover, .work-item:focus-visible {
  transform: translateY(-3px);
  border-color: color-mix(in srgb, var(--teal) 40%, transparent);
  box-shadow: 0 16px 34px -22px rgba(22,25,31,.5);
}
.work-item img { aspect-ratio: 4 / 3; object-fit: cover; width: 100%; }
.work-item .meta { padding: .95rem 1.1rem; }
.work-item h3 { font-size: 1.05rem; margin-bottom: .2rem; }
.work-item p { font-size: .88rem; color: var(--muted); margin: 0; }

/* ---------- counters ---------- */
.stat-row {
  display: grid; gap: var(--gap);
  grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
}
.stat .n {
  font-family: var(--serif); font-size: clamp(2rem, 4.5vw, 2.8rem);
  color: var(--teal); line-height: 1; display: block;
  font-variant-numeric: tabular-nums;
}
.stat p { font-size: .9rem; color: var(--muted); margin: .35rem 0 0; }

/* ---------- brand mark ----------
 * The real Ospry wave, inline as SVG. Inherits the brand colour from the
 * teal tile so there is one definition of the colour, not two. */
.brand svg.mark {
  width: 26px; height: 26px; padding: 4px; border-radius: 7px;
  background: var(--teal); color: #fff; flex: 0 0 auto;
}

/* ---------- waitlist form ----------
 * Lives on the marketing site rather than sending people to a login screen,
 * which is what the old button did — asking somebody to sign in before they
 * have decided to join is the wrong order. */
.wl-form { display: flex; flex-wrap: wrap; gap: .6rem; justify-content: center;
           margin-top: 1.5rem; }
.wl-form input {
  flex: 1 1 220px; max-width: 300px;
  padding: .72rem .95rem; border-radius: 9px;
  border: 1px solid var(--rule-firm); background: var(--panel);
  color: var(--ink); font: 400 .95rem/1.2 var(--sans);
}
.wl-form input:focus-visible { outline: 2px solid var(--teal); outline-offset: 1px; }
.wl-note { margin-top: .9rem; font-size: .85rem; color: var(--muted); }
.wl-msg { margin-top: 1rem; font-size: .95rem; font-weight: 600; min-height: 1.4em; }
.wl-msg.ok { color: var(--teal); }
.wl-msg.bad { color: var(--clay); }

/* ===================================================================
   Craft pass.

   The page was clean but flat: screenshots sat as bare rectangles, the
   display type used the same tracking as body copy, and the accent
   appeared at exactly one weight throughout. Clean reads as unfinished
   when nothing on the page shows evidence of a decision.

   Nothing here is decoration for its own sake — each change is a detail
   a person would notice without being able to name it.
   =================================================================== */

/* ---------- display typography ----------
 * Fraunces carries an optical size axis. Large type wants tighter
 * tracking and a lower optical size than body copy; using one setting
 * for both is what makes a serif look like a default rather than a
 * choice. */
h1 {
  font-variation-settings: "opsz" 120, "SOFT" 0;
  letter-spacing: -0.022em;
  line-height: 1.08;
}
h2 {
  font-variation-settings: "opsz" 72;
  letter-spacing: -0.016em;
}
.lede { text-wrap: pretty; }

/* Digits that sit in columns should line up. Cheap, and the absence is
   the sort of thing that reads as sloppy without being identified. */
.figure, .stat .n, table td { font-variant-numeric: tabular-nums; }

/* ---------- screenshots as objects ----------
 * A product shot presented as a bare image reads as a placeholder. Given
 * a browser bar and a real shadow it reads as a thing that exists. */
.shot {
  position: relative;
  border-radius: 14px;
  border: 1px solid var(--rule-firm);
  box-shadow:
    0 1px 2px rgba(17,24,23,.05),
    0 12px 24px -12px rgba(17,24,23,.18),
    0 32px 64px -32px rgba(17,24,23,.30);
}
/* A restrained browser bar: three dots and a rule. Enough to frame the
   image as a screen, not so much that it becomes a graphic in itself. */
.shot::before {
  content: "";
  display: block; height: 34px;
  background: var(--sunk);
  border-bottom: 1px solid var(--rule);
  background-image:
    radial-gradient(circle 4px at 18px 17px, var(--rule-firm) 98%, transparent),
    radial-gradient(circle 4px at 36px 17px, var(--rule-firm) 98%, transparent),
    radial-gradient(circle 4px at 54px 17px, var(--rule-firm) 98%, transparent);
  background-repeat: no-repeat;
}
.shot img { display: block; }

/* ---------- depth on the accent ----------
 * One flat teal everywhere gives the page no range. A quiet wash behind
 * the hero gives the top of the page somewhere to sit. */
.hero { position: relative; isolation: isolate; }
.hero::before {
  content: ""; position: absolute; inset: -20% -10% 30% -10%; z-index: -1;
  background:
    radial-gradient(60% 55% at 78% 22%,
      color-mix(in srgb, var(--teal) 11%, transparent) 0%, transparent 70%),
    radial-gradient(45% 40% at 8% 8%,
      color-mix(in srgb, var(--teal) 7%, transparent) 0%, transparent 70%);
  pointer-events: none;
}

/* ---------- hairline detail ---------- */
.eyebrow { display: inline-flex; align-items: center; gap: .6rem; }
.eyebrow::after {
  content: ""; width: 2.2rem; height: 1px;
  background: color-mix(in srgb, var(--teal) 45%, transparent);
}

/* Cards lift very slightly on hover — enough to feel responsive, not
   enough to bounce. */
.card { transition: border-color .2s ease, transform .2s ease, box-shadow .2s ease; }
a.card:hover {
  transform: translateY(-2px);
  border-color: color-mix(in srgb, var(--teal) 35%, transparent);
  box-shadow: 0 14px 30px -20px rgba(17,24,23,.35);
}

/* ===================================================================
   Interaction and depth.

   Buttons that do not respond feel like pictures of buttons. The moves
   here are small on purpose: a couple of pixels and a shadow. Anything
   larger reads as a toy, and this product is asking people to trust its
   numbers.

   Press states matter more than hover — hover does not exist on a phone,
   and a button that visibly depresses is the only feedback a thumb gets.
   =================================================================== */

.btn {
  position: relative;
  will-change: transform;
  transition: background .18s ease, border-color .18s ease, color .18s ease,
              transform .12s ease, box-shadow .18s ease;
}
.btn-primary {
  box-shadow: 0 1px 2px rgba(17,24,23,.14), 0 6px 14px -8px rgba(10,127,119,.55);
}
.btn-primary:hover {
  transform: translateY(-1px);
  box-shadow: 0 2px 4px rgba(17,24,23,.16), 0 12px 22px -10px rgba(10,127,119,.62);
}
.btn-ghost:hover { transform: translateY(-1px); background: var(--panel); }

/* Pressed. Returns to the resting position and drops the shadow, so the
   button reads as physically going down rather than just changing colour. */
.btn:active { transform: translateY(0); box-shadow: 0 1px 2px rgba(17,24,23,.18); }

/* Links in body copy: the underline grows from the left rather than
   simply appearing. Costs nothing and makes the page feel built. */
article.post p a:not(.btn), .split p a:not(.btn), .narrow p a:not(.btn) {
  color: var(--teal); text-decoration: none;
  background-image: linear-gradient(var(--teal), var(--teal));
  background-size: 0% 1.5px; background-position: 0 100%;
  background-repeat: no-repeat;
  transition: background-size .25s ease;
  padding-bottom: 1px;
}
article.post p a:not(.btn):hover, .split p a:not(.btn):hover,
.narrow p a:not(.btn):hover {
  background-size: 100% 1.5px;
}

/* Screenshots respond too, so they read as things rather than pictures. */
.shot { transition: transform .3s ease, box-shadow .3s ease; }
.shot:hover {
  transform: translateY(-3px);
  box-shadow:
    0 2px 4px rgba(17,24,23,.06),
    0 18px 34px -14px rgba(17,24,23,.22),
    0 44px 80px -40px rgba(17,24,23,.34);
}

/* The waitlist field is the most important control on the site, so it
   gets a visible focus state rather than the browser default. */
.wl-form input {
  transition: border-color .18s ease, box-shadow .18s ease;
}
.wl-form input:focus {
  outline: none;
  border-color: var(--teal);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--teal) 18%, transparent);
}

@media (prefers-reduced-motion: reduce) {
  .btn, .shot, article.post p a:not(.btn) { transition: none; }
  .btn:hover, .btn-ghost:hover, .shot:hover { transform: none; }
}

/* ===================================================================
   The one loud moment.

   Everything else on this page is deliberately quiet, which is right for
   a product selling restraint — but a page with no emphasis anywhere
   reads as timid rather than confident. This is the single section
   allowed to shout, and it shouts with a real measurement rather than an
   adjective.
   =================================================================== */

.bleed {
  background: var(--ink);
  color: #EAF2F0;
  margin-block: clamp(3rem, 8vw, 6rem);
  padding-block: clamp(3.5rem, 9vw, 6.5rem);
  position: relative; overflow: hidden;
}
.bleed::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: radial-gradient(65% 60% at 82% 20%,
    color-mix(in srgb, var(--teal) 22%, transparent) 0%, transparent 70%);
}
.bleed > * { position: relative; z-index: 1; }
.bleed h2 { color: #fff; }
.bleed p { color: #C3D2CF; }
.bleed .eyebrow { color: #7FE9DA; }
.bleed .eyebrow::after { background: color-mix(in srgb, #7FE9DA 40%, transparent); }

/* The number, at a size that makes the point before the words do. */
.bigfig {
  font-family: var(--serif);
  font-variation-settings: "opsz" 144;
  font-size: clamp(3.4rem, 12vw, 8rem);
  line-height: .9; letter-spacing: -0.04em;
  color: #fff; margin: 0 0 .2em;
  font-variant-numeric: tabular-nums;
}
.bigfig small {
  display: block; font-family: var(--sans); font-size: .16em;
  letter-spacing: .16em; text-transform: uppercase; color: #7FE9DA;
  margin-top: 1.1em; font-weight: 600;
}
.bleed-split {
  display: grid; gap: clamp(1.5rem, 5vw, 3.5rem);
  grid-template-columns: 1fr; align-items: center;
}
@media (min-width: 860px) { .bleed-split { grid-template-columns: .9fr 1.1fr; } }

/* ===================================================================
   Laptop.

   A screenshot centred in a rounded box is the single most templated
   thing on the modern web. So this one is built as an actual device and
   then deliberately misbehaves: it sits at an angle, overflows its
   column, and runs off the right edge of the page.

   Breaking the container is the whole point. A grid that never breaks
   reads as a theme; one interruption reads as a decision.
   =================================================================== */

.rig {
  position: relative;
  padding-block: clamp(3rem, 8vw, 5.5rem);
  overflow: hidden;              /* contains the overflowing device */
}

.rig-grid {
  display: grid; gap: clamp(1.5rem, 4vw, 3rem);
  grid-template-columns: 1fr; align-items: center;
}
@media (min-width: 900px) {
  .rig-grid { grid-template-columns: .82fr 1.18fr; }
}

/* The device itself. Proportions taken from a real 16:10 laptop rather
   than guessed, because the eye knows this shape. */
.mac { perspective: 1800px; }
.mac-inner {
  transform: rotateY(-14deg) rotateX(4deg) rotate(-1.2deg);
  transform-origin: left center;
  transition: transform .5s cubic-bezier(.2,.7,.3,1);
  will-change: transform;
}
@media (min-width: 900px) {
  /* Push it out of the column and off the page edge. This is the bit
     that stops it looking like a stock hero image. */
  .mac { margin-right: clamp(-14rem, -9vw, -4rem); }
}
.mac-inner:hover { transform: rotateY(-9deg) rotateX(2deg) rotate(-.6deg); }

.mac-lid {
  background: linear-gradient(160deg, #2A3134 0%, #171C1E 55%, #0E1214 100%);
  border-radius: 16px 16px 10px 10px;
  padding: 12px 12px 14px;
  box-shadow:
    0 2px 3px rgba(17,24,23,.20),
    0 26px 44px -20px rgba(17,24,23,.45),
    0 70px 120px -60px rgba(17,24,23,.55);
}
.mac-screen {
  position: relative;
  aspect-ratio: 16 / 10;
  border-radius: 6px; overflow: hidden;
  background: var(--panel);
  box-shadow: inset 0 0 0 1px rgba(255,255,255,.06);
}
.mac-screen img {
  width: 100%; height: 100%; object-fit: cover; object-position: top center;
  display: block;
}
/* The glass. A single soft diagonal, not a stripe — a hard highlight is
   what makes CSS devices look like clip art. */
.mac-screen::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(115deg,
    rgba(255,255,255,.16) 0%, rgba(255,255,255,.04) 26%, transparent 52%);
}
.mac-notch {
  position: absolute; top: 0; left: 50%; transform: translateX(-50%);
  width: 84px; height: 6px; border-radius: 0 0 5px 5px;
  background: #0E1214; z-index: 2;
}
/* Base: a shallow wedge, wider than the lid, with the lip cut out. */
.mac-base {
  height: 12px; margin: 0 -20px;
  background: linear-gradient(180deg, #303739 0%, #1B2123 60%, #12171A 100%);
  border-radius: 0 0 12px 12px;
  position: relative;
  box-shadow: 0 18px 26px -18px rgba(17,24,23,.55);
}
.mac-base::after {
  content: ""; position: absolute; top: 0; left: 50%;
  transform: translateX(-50%);
  width: 88px; height: 5px; border-radius: 0 0 6px 6px;
  background: rgba(0,0,0,.35);
}

@media (prefers-reduced-motion: reduce) {
  .mac-inner { transition: none; }
  .mac-inner:hover { transform: rotateY(-14deg) rotateX(4deg) rotate(-1.2deg); }
}

/* Data tables in posts. The 259-site study is the first page to need one.

   Numbers are tabular-nums and right-aligned so the eye can compare down the
   column, which is the whole reason to read a table rather than a paragraph.
   It scrolls inside its own box on a narrow screen rather than pushing the
   page sideways. */
.post table.data {
  width: 100%;
  border-collapse: collapse;
  margin: 1.8rem 0;
  font-size: .95rem;
}
.post table.data th,
.post table.data td {
  padding: .62rem .8rem;
  border-bottom: 1px solid var(--rule);
  text-align: left;
}
.post table.data th {
  font-size: .78rem;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--muted);
  font-weight: 600;
  border-bottom-color: var(--rule-firm);
}
.post table.data td:nth-child(2),
.post table.data td:nth-child(3),
.post table.data th:nth-child(2),
.post table.data th:nth-child(3) {
  text-align: right;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
/* The share column carries the finding; the raw count is supporting detail. */
.post table.data td:nth-child(3) { font-weight: 600; color: var(--ink); }
.post table.data tr:last-child td { border-bottom: none; }
.post table.data tbody tr:hover { background: var(--sunk); }

@media (max-width: 560px) {
  .post table.data { display: block; overflow-x: auto; }
}

/* The two routes on the waitlist section.
   .routes .card overrides rather than fights .card: same specificity class
   pair, declared later, so the cascade resolves without !important. */
.seats {
  display: inline-block; margin: 0 auto 1.6rem; padding: .4rem .9rem;
  border: 1px solid var(--rule-firm); border-radius: 999px;
  background: var(--panel); color: var(--soft);
  font-size: .85rem; font-weight: 600; letter-spacing: .01em;
}
.seats.gone { border-color: var(--clay); color: var(--clay); background: var(--clay-wash); }

.routes { margin-bottom: var(--gap); text-align: left; }
.routes .route { display: flex; flex-direction: column; gap: .55rem; }
.routes .price { margin: .2rem 0 0; font-family: var(--serif); font-size: 1.05rem; color: var(--muted); }
.routes .price strong { font-size: 2.1rem; color: var(--ink); letter-spacing: -.02em; }
.routes .price span { font-size: .95rem; }
.routes .price-sub { margin: 0; font-size: .9rem; color: var(--muted); }
.routes ul, .included ul { margin: .4rem 0 0; padding-left: 1.1rem; }
.routes li, .included li { margin-bottom: .45rem; color: var(--soft); font-size: .95rem; }

.included { text-align: left; margin-bottom: var(--gap); }
.included h3 { margin: 0 0 .6rem; font-size: 1rem; letter-spacing: .02em; text-transform: uppercase; color: var(--muted); }
.included .fine { margin: .8rem 0 0; font-size: .85rem; color: var(--muted); }

/* The radio pair. A fieldset is the correct element for a single choice, and
   it needs its default border and padding cleared before it will sit in a
   flex row with the inputs. */
.wl-form .choice {
  flex-basis: 100%; border: 0; padding: 0; margin: 0 0 .3rem;
  display: flex; flex-wrap: wrap; gap: .5rem; justify-content: center;
}
.wl-form .choice legend { float: left; width: 100%; margin-bottom: .5rem;
  font-size: .85rem; color: var(--muted); text-align: center; }
.wl-form .choice label {
  display: inline-flex; align-items: center; gap: .45rem; cursor: pointer;
  padding: .45rem .85rem; border: 1px solid var(--rule-firm); border-radius: 999px;
  background: var(--panel); font-size: .9rem; color: var(--soft);
}
.wl-form .choice label:has(input:checked) {
  border-color: var(--teal); background: var(--teal-wash); color: var(--teal-deep); font-weight: 600;
}
.wl-form .choice input:focus-visible { outline: 2px solid var(--teal); outline-offset: 2px; }
.wl-form .choice label:has(input:disabled) { opacity: .5; cursor: not-allowed; }

/* Two flex defaults that needed overriding above.

   A flex column stretches its children to full width, so .tag — an inline
   pill everywhere else on the site — was rendering as a full-width bar
   across the top of each route card. align-self restores its own width.

   And a flex item may shrink below its content width, which was squeezing
   "Standard, £79 a month" onto three lines inside a pill sized for one.
   flex: none stops the shrink; the row already wraps, so narrow screens
   still lay out correctly. */
.routes .route .tag { align-self: flex-start; }
.wl-form .choice label { flex: none; }
.wl-form .choice label span { white-space: nowrap; }

/* The hero laptop is deliberately pushed past the right edge (see .mac above),
   but nothing was clipping it, so the whole page gained about 120px of
   sideways scroll on desktop. Pre-existing, and only on the homepage.

   overflow-x: clip rather than hidden: `hidden` would make .hero a scroll
   container, which silently breaks position: sticky for anything inside it.
   `clip` cuts the overflow without that side effect. */
.hero { overflow-x: clip; }

/* The founding landing page.

   Section padding is tuned for pages you read top to bottom, where the air
   between sections is doing real work. On a single-purpose landing page the
   same rhythm reads as three separate pages stapled together: adjacent
   padding-bottom and padding-top stack into roughly 250px of nothing, and
   every one of those gaps is a place to stop scrolling and leave.

   Scoped to .lp so nothing else on the site inherits it. */
.lp section { padding-block: clamp(1.75rem, 4vw, 3.25rem); }
.lp .hero { padding-bottom: clamp(1.25rem, 3vw, 2.25rem); }
.lp .seats { margin-bottom: 1rem; }
.lp h1 { margin-top: 0; }

/* A recessed band, used on the landing page to set the "why only 75"
   explanation apart from the offer above and the form below.

   --sunk is a colour token, not a class. An earlier draft wrote class="sunk"
   on the strength of the variable name and styled nothing at all, which is
   invisible in review precisely because a class that does not exist looks
   identical to one that does. */
.band { background: var(--sunk); border-block: 1px solid var(--rule); }
.band h2 { margin-top: 0; }

/* The price treatment was scoped to `.routes .price`, which only matched cards
   sitting inside the two-up comparison grid. The thank-you page shows a single
   route card with no grid around it, so the £174 rendered at body size on the
   one page where it is the entire point.

   Same specificity as the .routes rules and declared later, so it wins where
   both match and also applies where only .route does. */
.route .price { margin: .2rem 0 0; font-family: var(--serif); font-size: 1.05rem; color: var(--muted); }
.route .price strong { font-size: 2.1rem; color: var(--ink); letter-spacing: -.02em; }
.route .price span { font-size: .95rem; }
.route .price-sub { margin: 0; font-size: .9rem; color: var(--muted); }
.route ul { margin: .4rem 0 0; padding-left: 1.1rem; }
.route li { margin-bottom: .45rem; color: var(--soft); font-size: .95rem; }

/* The product screenshot on the landing page.

   Given an explicit width and height in the markup so the browser reserves the
   right space before the image arrives. Without that the text below jumps down
   when it loads, which on a page asking for £174 reads as a page that is still
   loading rather than one that is finished. */
.shot {
  margin: var(--gap) 0 0;
  border: 1px solid var(--rule-firm);
  border-radius: 12px;
  overflow: hidden;
  background: var(--panel);
  line-height: 0;
}
.shot img { width: 100%; height: auto; display: block; }

/* ===================================================================
   The loud band, fixed.

   It was written as `background: var(--ink)` with hardcoded light text.
   --ink is the TEXT colour: near-black in light mode, near-white in dark.
   So in dark mode the panel inverted to pale while its text stayed light,
   and the number, the headline and the eyebrow all but disappeared. A
   tester found it on a phone and described it exactly: faded writing.

   This panel is deliberately dark in both themes - that is the whole
   point of it, the page's one loud moment - so it now owns a background
   that does not invert underneath it, rather than borrowing a token whose
   job is to invert.

   Declared after the original rule and at the same specificity, so the
   cascade resolves without !important.
   =================================================================== */
.bleed {
  /* Fixed, not a token. A deliberately dark panel should not follow the
     theme, and every colour inside it is chosen against this one value. */
  background: #111817;
  color: #EAF2F0;
}
.bleed .eyebrow { color: #7FE9DA; }
.bleed h2, .bleed h3 { color: #FFFFFF; }
.bleed p { color: #C9D8D5; }
.bleed a { color: #7FE9DA; }

/* The tag on a card, which was 3.6:1 against the panel behind it. */
.tag-soon { color: var(--soft); }


/* Accent text, as opposed to accent fills.

   --teal is chosen to be filled with, and at 4.4:1 on --paper it sits just
   under AA for small text. That gap is what a tester described as faded on
   a phone: legible indoors, washed out in daylight.

   --teal-deep is already defined for both themes and is the right colour for
   type: darker in light mode, lighter in dark. Fills keep --teal. */
.eyebrow { color: var(--teal-deep); }
/* :not(.btn) is load-bearing. .prose a is (0,1,1) and beats
   .btn-primary at (0,1,0), so without it every call to action inside a
   paragraph takes the link colour and becomes teal text on a teal fill. */
.prose a:not(.btn):not(.osp-cta),
main p a:not(.btn):not(.osp-cta),
main li a:not(.btn):not(.osp-cta),
.post a:not(.btn):not(.osp-cta) { color: var(--teal-deep); }
