/* ChaseLog — marketing site
   ===========================================================================
   Plain CSS, no build step. `scp` it up and it is live.

   Structure and motion come from the reference the client gave
   (nota.uprock.pro, watched frame by frame 14/08/2026): hard-edged full-bleed
   bands, content sliding in from the right, masked display lines, one carousel.

   Its type is NOT copied. That site pairs a high-contrast serif display with a
   sans; this one stays on Inter throughout, because Inter is the product's own
   face and a marketing site in a different family reads as a different company.
   The display sizes therefore carry weight and negative tracking instead of
   stroke contrast — the substitution that keeps a grotesque from going limp at
   8rem.

   Nor could its photography be copied. It sells an object, so every set piece
   is that object shot full bleed. ChaseLog has no object, so the artefact it
   replaces — the ledger — is drawn instead, in CSS.

   Colours are the Prussian ramp decided 14/08/2026, the same values that sit in
   the app's globals.css. They are duplicated here rather than imported, because
   this site does not build and must not depend on the app to render.
   =========================================================================== */

@font-face {
  font-family: "Inter";
  src: url("vendor/inter-variable.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

:root {
  --p-50: #edf8fe;
  --p-100: #d9eefa;
  --p-200: #b8ddf2;
  --p-400: #63acd3;
  --p-500: #3891bc;
  --p-600: #1b759d;
  --p-700: #095a7c;
  --p-800: #064159;
  --p-900: #032635;
  --p-950: #010f16;

  --ink: #0d1418;
  --ink-3: #66737b;
  --paper: #ffffff;
  --paper-2: #f2f5f7;
  --rule: #e2e8ec;

  --sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
  /* Display and body are one family. Inter is the product's face and the
     site keeps it, so the two never look like different companies. At
     display size it is carried by WEIGHT and negative tracking rather than
     by contrast — set loose and light, a grotesque goes limp where a serif
     would have held. */
  --display-face: var(--sans);

  /* Named fluid pairs rather than ad-hoc clamp() calls, so the scale is
     auditable in one place — the convention Vercel ships. */
  --text-fluid-40-80: clamp(2.5rem, -0.55rem + 7.6vw, 5rem);
  --text-fluid-32-56: clamp(2rem, 0.47rem + 3.8vw, 3.5rem);
  --text-fluid-24-36: clamp(1.5rem, 0.89rem + 1.52vw, 2.25rem);

  /* Weight, measured off shipped CSS: 450–550 carries headings now. Vercel
     sets a 72px hero at 450; Linear's "bold" token is 680, not 700. A 700
     headline is the clearest 2021 tell there is. */
  --w-display: 500;
  --w-strong: 560;
  --w-body: 450;

  /* Tracking is size-indexed and crosses zero around 16px. Below that it goes
     POSITIVE — Retool tracks body at +0.0126em, Cursor at +0.01em. One global
     negative value is what makes small text look smeared. */
  --track-display: -0.032em;
  --track-mid: -0.018em;
  --track-body: -0.005em;
  --track-small: 0.01em;

  /* One device pixel on every display. 0.5px on retina is the point. */
  --hairline: 1px;

  --gut: clamp(1.25rem, 4vw, 4.5rem);
  --section-y: clamp(5rem, 10vw, 11rem);
}

@media (min-resolution: 192dpi), (-webkit-min-device-pixel-ratio: 2) {
  :root {
    --hairline: 0.5px;
  }
}

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

html {
  /* Shallow on purpose: a big monitor should buy room, not larger prose.
     16px floor — never below the browser default. */
  font-size: clamp(1rem, 0.97rem + 0.1vw, 1.0625rem);
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 0.9375rem; /* 15px — where product-dense sites have settled */
  font-weight: var(--w-body);
  letter-spacing: var(--track-body);
  line-height: 1.6;
  text-wrap: pretty;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

html.lenis,
html.lenis body {
  height: auto;
}
.lenis.lenis-smooth {
  scroll-behavior: auto !important;
}

img,
svg {
  display: block;
  max-width: 100%;
}
a {
  color: inherit;
}

:focus-visible {
  outline: 2px solid var(--p-600);
  outline-offset: 3px;
}

/* ================================================================== type */

.display {
  font-family: var(--display-face);
  font-weight: var(--w-display);
  letter-spacing: var(--track-display);
  line-height: 1;
  margin: 0;
  font-size: var(--text-fluid-40-80);
  text-wrap: balance;
}
.display--md {
  font-size: var(--text-fluid-32-56);
  line-height: 1.05;
}
.display--sm {
  font-size: var(--text-fluid-24-36);
  letter-spacing: var(--track-mid);
}

h3 {
  margin: 0;
  font-family: var(--sans);
  font-size: 1.02rem;
  font-weight: 600;
  letter-spacing: -0.012em;
}

p {
  margin: 0;
}

.lede {
  font-size: 1rem;
  line-height: 1.65;
  letter-spacing: var(--track-body);
  color: var(--ink-3);
  /* min() rather than a bare ch value: 46ch is wider than a 390px phone, and
     a grid item with the default min-width:auto will happily widen its track
     to fit rather than wrap — which clipped this paragraph at the edge. */
  max-width: min(46ch, 100%);
}

/* The section kicker. Clerk and Raycast set this in a mono at 10–11px, +0.1em,
   weight 500; we ship no mono, so this is Cursor's sans version of the same
   shape — the substitution costs texture, not structure. */
.eyebrow {
  font-size: 0.6875rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  font-weight: 500;
  color: var(--p-600);
  margin: 0 0 1.4rem;
}

/* ================================================================ blocks

   Hard-edged full-bleed bands, alternating ink and paper. The reference never
   fades between sections: each one arrives as its own surface. */

/* Two widths on one page: wide for product visuals, narrow for prose. One
   container for everything is the tell of a 2021 layout. */
.wrap {
  max-width: 84rem; /* 1344px, the top of the current band */
  margin: 0 auto;
  padding-inline: var(--gut);
}

.block {
  padding-block: var(--section-y);
  position: relative;
  overflow: hidden;
}

.block--paper {
  background: var(--paper);
}
.block--ink {
  background: var(--p-950);
  color: #eef5f8;
}
.block--ink .lede {
  color: #93a9b4;
}
.block--ink .eyebrow {
  color: var(--p-400);
}

/* =================================================================== nav

   The nav is a ledger header row, not a generic bar: ruled underneath, with a
   tick above the section you are in, and a running count on the right. The
   product replaces a register, so its own chrome is drawn like one — which is
   also the only idea available that no other B2B site can borrow, because it
   comes from the subject rather than from a gallery.
   ========================================================================== */

.nav {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 60;
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 1.5rem;
  padding: 0.9rem var(--gut);
  color: #eef5f8;
  transition:
    transform 0.4s cubic-bezier(0.16, 1, 0.3, 1),
    background-color 0.3s ease-out,
    backdrop-filter 0.3s ease-out;
}
.nav[data-hidden] {
  transform: translateY(-105%);
}
.nav[data-scrolled] {
  background: color-mix(in oklab, var(--p-950) 82%, transparent);
  backdrop-filter: blur(14px);
}

/* The rule under the whole row, and the scroll progress drawn ON it — one
   line doing two jobs, the way a ruled page does. */
.nav__progress {
  position: absolute;
  left: 0;
  bottom: 0;
  height: var(--hairline);
  width: 100%;
  transform: scaleX(0);
  transform-origin: left;
  background: var(--p-400);
}
.nav::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: var(--hairline);
  background: rgba(255, 255, 255, 0.14);
  opacity: 0;
  transition: opacity 0.3s ease-out;
}
.nav[data-scrolled]::after {
  opacity: 1;
}

.nav__mark {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-family: var(--display-face);
  font-weight: 640;
  letter-spacing: -0.03em;
  font-size: 1.05rem;
  text-decoration: none;
}
/* The ChaseLog mark, Connected Ledger — the same file the browser tab wears.
   Generated from dashboard/src/lib/brand-mark.ts by tools/brand-icons.ts. */
.nav__mark::before {
  content: "";
  width: 1.75rem;
  height: 1.75rem;
  flex: none;
  background: url("favicon.svg") center / contain no-repeat;
  margin-right: 0.15rem;
}

/* Each link is a ruled column with its stage number above it — the register's
   own numbering, not decoration. The tick under the active one slides. */
.nav__links {
  display: none;
  justify-self: center;
  gap: 0;
  font-size: 0.78rem;
}
.nav__links a {
  position: relative;
  display: grid;
  gap: 0.15rem;
  padding: 0.1rem 1.15rem;
  text-decoration: none;
  color: rgba(238, 245, 248, 0.6);
  transition: color 0.2s ease-out;
  border-left: var(--hairline) solid rgba(255, 255, 255, 0.12);
}
.nav__links a:last-child {
  border-right: var(--hairline) solid rgba(255, 255, 255, 0.12);
}
.nav__links a::before {
  content: attr(data-n);
  font-size: 0.6rem;
  letter-spacing: 0.14em;
  color: rgba(238, 245, 248, 0.3);
  font-variant-numeric: tabular-nums;
  transition: color 0.2s ease-out;
}
.nav__links a:hover,
.nav__links a[data-current] {
  color: #fff;
}
.nav__links a:hover::before,
.nav__links a[data-current]::before {
  color: var(--p-400);
}
/* The tick: a short rule that slides to the section you are reading. */
.nav__links a::after {
  content: "";
  position: absolute;
  left: 1.15rem;
  right: 1.15rem;
  bottom: -0.55rem;
  height: 2px;
  background: var(--p-400);
  transform: scaleX(0);
  transform-origin: center;
  transition: transform 0.35s cubic-bezier(0.16, 1, 0.3, 1);
}
.nav__links a[data-current]::after {
  transform: scaleX(1);
}

@media (min-width: 62rem) {
  .nav__links {
    display: flex;
  }
}

.nav__cta {
  display: flex;
  align-items: center;
  gap: 1rem;
}
.nav__price {
  display: none;
  font-size: 0.66rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: rgba(238, 245, 248, 0.4);
  font-variant-numeric: tabular-nums;
}
@media (min-width: 48rem) {
  .nav__price {
    display: block;
  }
}

/* =============================================================== buttons

   The fill wipes in from the left rather than the colour swapping — the same
   gesture as the meter beside the pipeline stages, and as money arriving
   against an invoice. A tint change says "hovered"; a fill says "filling".
   ========================================================================== */

.btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.55rem;
  border: 0;
  border-radius: 999px;
  padding: 0.72rem 1.4rem;
  font: inherit;
  font-family: var(--sans);
  font-size: 0.82rem;
  font-weight: 560;
  cursor: pointer;
  text-decoration: none;
  white-space: nowrap;
  overflow: hidden;
  isolation: isolate;
  transition: color 0.25s ease-out;
}

/* The wipe itself. scaleX from the left, so it reads as filling rather than
   fading — and it is a pseudo-element, so no extra markup per button. */
.btn::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.42s cubic-bezier(0.16, 1, 0.3, 1);
}
.btn:hover::before,
.btn:focus-visible::before {
  transform: scaleX(1);
}
.btn:active {
  transform: translateY(1px);
}

/* A tick that slides out as the fill lands. */
.btn::after {
  content: "";
  width: 0;
  height: 2px;
  background: currentColor;
  opacity: 0;
  transition:
    width 0.42s cubic-bezier(0.16, 1, 0.3, 1),
    opacity 0.2s ease-out;
}
.btn:hover::after,
.btn:focus-visible::after {
  width: 0.85rem;
  opacity: 1;
}

.btn--brand {
  background: var(--p-700);
  color: #fff;
}
.btn--brand::before {
  background: var(--p-500);
}

.btn--dark {
  background: #fff;
  color: var(--ink);
}
.btn--dark::before {
  background: var(--p-400);
}

.btn--line {
  background: transparent;
  color: inherit;
  box-shadow: inset 0 0 0 var(--hairline) currentColor;
}
.btn--line::before {
  background: rgba(255, 255, 255, 0.12);
}
.block--paper .btn--line::before {
  background: rgba(13, 20, 24, 0.07);
}

/* ================================================================== hero */

.hero {
  position: relative;
  /* 88svh, not 100: a full viewport with the content centred leaves a dead
     band top and bottom, and the section below should be visibly beginning. */
  min-height: 88svh;
  display: grid;
  align-content: center;
  padding-block: 7rem clamp(2rem, 5vw, 4rem);
  overflow: hidden;
  background: var(--p-950);
  color: #eef5f8;
}

/* Four layers, because two gradients on a flat colour is what "lame" looks
   like: a ground, two slowly drifting blooms that are never both at their
   brightest, a perspective floor, and grain over the lot. The grain is the
   part that matters — a large gradient without it bands visibly on an 8-bit
   panel, and the banding is most of why a flat hero reads as cheap. */
.hero__bg {
  position: absolute;
  inset: 0;
  background: linear-gradient(172deg, #06364a 0%, var(--p-900) 42%, var(--p-950) 100%);
}

.hero__bg::before,
.hero__bg::after {
  content: "";
  position: absolute;
  inset: -20%;
  background: radial-gradient(circle at 50% 50%, rgba(56, 145, 188, 0.5), transparent 62%);
  will-change: transform;
}
.hero__bg::before {
  animation: bloom-a 19s ease-in-out infinite alternate;
}
.hero__bg::after {
  background: radial-gradient(circle at 50% 50%, rgba(9, 90, 124, 0.55), transparent 60%);
  animation: bloom-b 23s ease-in-out infinite alternate;
}

@keyframes bloom-a {
  from {
    transform: translate3d(18%, -14%, 0) scale(1);
  }
  to {
    transform: translate3d(34%, 6%, 0) scale(1.18);
  }
}
@keyframes bloom-b {
  from {
    transform: translate3d(-24%, 26%, 0) scale(1.1);
  }
  to {
    transform: translate3d(-8%, 8%, 0) scale(0.92);
  }
}

/* The floor: a grid laid flat in perspective, fading out before the horizon.
   It reads as depth without being a starfield or a blueprint cliché. */
.hero__floor {
  position: absolute;
  inset: 42% -30% -12% -30%;
  background-image:
    repeating-linear-gradient(to right, rgba(99, 172, 211, 0.16) 0 1px, transparent 1px 6.5rem),
    repeating-linear-gradient(to bottom, rgba(99, 172, 211, 0.16) 0 1px, transparent 1px 6.5rem);
  transform: perspective(46rem) rotateX(66deg);
  transform-origin: 50% 0;
  mask-image: linear-gradient(to bottom, transparent, #000 38%, transparent 88%);
  pointer-events: none;
}

/* Grain. An inline SVG turbulence, so it costs one data URI and no request. */
.hero__grain {
  position: absolute;
  inset: 0;
  opacity: 0.16;
  mix-blend-mode: overlay;
  pointer-events: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* The workbook this product replaced, as texture: 27px baselines. */
.hero__rule {
  position: absolute;
  inset: 0;
  background-image: repeating-linear-gradient(
    to bottom,
    rgba(255, 255, 255, 0.05) 0 1px,
    transparent 1px 27px
  );
  mask-image: linear-gradient(to bottom, transparent, #000 25%, #000 70%, transparent);
  pointer-events: none;
}

/* --- the drawn ledger: this site's equivalent of the reference's product shot */
/* The ledger is a COLUMN of the hero now, not decoration floating behind it.
   Two earlier attempts pushed it off the right edge and faded it to near
   nothing, which is what left the hero looking like an empty gradient: the
   thing meant to fill that space was never really on screen. */
.hero__art {
  display: none;
  position: relative;
  transform: perspective(1800px) rotateX(6deg) rotateZ(-2deg);
  transform-origin: 50% 50%;
  mask-image: linear-gradient(to bottom, #000 78%, transparent);
}
@media (min-width: 62rem) {
  .hero__art {
    display: block;
  }
}

.ledger {
  font-size: 0.78rem;
  font-variant-numeric: tabular-nums;
  color: #cfe2ea;
}
.ledger__head,
.ledger__row {
  display: grid;
  grid-template-columns: 1.1fr 0.6fr 1fr 0.9fr;
  gap: 1rem;
  padding: 0.62rem 1.1rem;
  border-bottom: 1px solid rgba(255, 255, 255, 0.09);
}
.ledger__head {
  font-size: 0.6rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.35);
  border-bottom-color: rgba(255, 255, 255, 0.22);
}
.ledger .num {
  text-align: right;
}
.ledger__row {
  opacity: 0; /* raised by the hero timeline; set to 1 when motion is off */
}
.ledger__row b {
  font-weight: 500;
  color: #fff;
}
.ledger__row .st {
  color: var(--p-400);
}

.hero__inner {
  position: relative;
  z-index: 2;
  display: grid;
  gap: clamp(1.8rem, 4vw, 3rem);
  min-width: 0;
  align-items: center;
}
@media (min-width: 62rem) {
  .hero__inner {
    grid-template-columns: 1fr 0.95fr;
    gap: clamp(2.5rem, 5vw, 5rem);
  }
  /* headline + deck + CTAs occupy the left column and stack within it */
  .hero__lead {
    display: grid;
    gap: 1.6rem;
  }
}

.hero__foot {
  display: grid;
  gap: 1.6rem;
  /* A grid item defaults to min-width:auto, so a long line pushes its track
     wider than the container instead of wrapping — which clipped the hero
     paragraph at the right edge on a phone. */
  min-width: 0;
}
.hero__foot > * {
  min-width: 0;
}

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.7rem;
}

.hero__scroll {
  display: none; /* shown only where there is room for it — see below */
  position: absolute;
  left: var(--gut);
  bottom: 1.4rem;
  z-index: 2;
  display: flex;
  align-items: center;
  gap: 0.6rem;
  font-size: 0.62rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.4);
}
.hero__scrollline {
  display: block;
  width: 2.4rem;
  height: 1px;
  background: currentColor;
  transform-origin: left;
  animation: scroll-hint 2.4s ease-in-out infinite;
}
@media (min-width: 62rem) {
  .hero__scroll {
    display: flex;
  }
}

@keyframes scroll-hint {
  0%,
  100% {
    transform: scaleX(0.3);
    opacity: 0.4;
  }
  50% {
    transform: scaleX(1);
    opacity: 1;
  }
}

/* ============================================================ line masks */

.line {
  display: block;
  overflow: hidden;
}
.line > span {
  display: block;
  will-change: transform;
}

/* ================================================================= prose */

.prose-cols {
  display: grid;
  gap: 2rem;
  margin-top: 2.5rem;
}
.prose-cols .lede {
  max-width: min(65ch, 100%); /* the prose ceiling every measured site lands on */
}
@media (min-width: 56rem) {
  .prose-cols {
    grid-template-columns: 1fr 1fr;
    gap: 4rem;
  }
}

/* =============================================================== figures */

.figures {
  padding-block: clamp(3rem, 6vw, 5rem);
  border-top: var(--hairline) solid rgba(255, 255, 255, 0.1);
  border-bottom: var(--hairline) solid rgba(255, 255, 255, 0.1);
}
.figures__row {
  display: grid;
  gap: 2.5rem 1.5rem;
  grid-template-columns: repeat(2, 1fr);
}
@media (min-width: 52rem) {
  .figures__row {
    grid-template-columns: repeat(4, 1fr);
  }
}
.figure b {
  display: block;
  font-family: var(--display-face);
  font-size: clamp(2.6rem, 4.6vw, 4rem);
  line-height: 0.9;
  font-weight: 620;
  letter-spacing: -0.045em;
  color: #fff;
  font-variant-numeric: tabular-nums;
}
.figure span {
  display: block;
  margin-top: 0.7rem;
  font-size: 0.75rem;
  letter-spacing: var(--track-small); /* positive below 16px */
  color: #7d93a0;
}

/* ============================================================== carousel */

.carousel {
  margin-top: clamp(2.5rem, 5vw, 4rem);
}

/* All slides occupy ONE grid cell rather than being absolutely positioned.
   The stage then sizes itself to the tallest slide instead of to a guessed
   min-height — which on a narrow screen clipped the art and the body copy,
   because they stack there and no fixed height could be right for both. */
.carousel__stage {
  display: grid;
}

.slide {
  grid-area: 1 / 1;
  display: grid;
  gap: 2rem;
  align-items: center;
  opacity: 0;
  visibility: hidden;
  transform: translateX(3rem);
  transition:
    opacity 0.5s ease-out,
    transform 0.6s cubic-bezier(0.16, 1, 0.3, 1),
    visibility 0.5s;
}
.slide[data-active] {
  opacity: 1;
  visibility: visible;
  transform: none;
}
/* visibility:hidden already removes an inactive slide from the a11y tree and
   from hit-testing; this makes the intent explicit for pointer devices. */
.slide:not([data-active]) {
  pointer-events: none;
}
@media (min-width: 56rem) {
  .slide {
    grid-template-columns: 1.15fr 1fr;
    gap: 4rem;
  }
}

.slide__body {
  display: grid;
  gap: 0.9rem;
  align-content: center;
}
.slide__body h3 {
  font-family: var(--display-face);
  font-size: clamp(1.5rem, 2.4vw, 2.1rem);
  font-weight: 620;
  letter-spacing: -0.032em;
}

.slide__art {
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 0.4rem;
  background: rgba(255, 255, 255, 0.03);
  min-height: clamp(13rem, 22vw, 18rem);
  padding: clamp(1.5rem, 3vw, 2.5rem);
  display: grid;
  align-content: center;
  gap: 0.9rem;
}

.slide__art--tender {
  grid-auto-flow: column;
  justify-content: start;
  align-items: center;
  gap: 1rem;
}
.rank {
  font-family: var(--display-face);
  font-size: clamp(2.2rem, 4.4vw, 3.4rem);
  font-weight: 620;
  letter-spacing: -0.05em;
  line-height: 1;
  color: #fff;
}
.rank.ghost {
  color: rgba(255, 255, 255, 0.18);
}

.bar {
  position: relative;
  height: 2.1rem;
  border-radius: 0.2rem;
  background: rgba(255, 255, 255, 0.08);
  overflow: hidden;
  display: flex;
  align-items: center;
}
.bar::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: min(var(--w), 100%);
  background: var(--p-600);
}
.bar--total::before {
  background: var(--p-400);
}
.bar span {
  position: relative;
  padding-inline: 0.8rem;
  font-size: 0.7rem;
  color: #fff;
}

.slide__art--milestones ol {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 0.55rem;
}
.slide__art--milestones li {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  padding-bottom: 0.55rem;
  border-bottom: 1px solid rgba(255, 255, 255, 0.09);
  font-size: 0.8rem;
}
.slide__art--milestones span {
  color: var(--p-400);
  font-variant-numeric: tabular-nums;
}

.carousel__dashes {
  display: flex;
  gap: 0.6rem;
  margin-top: 2.2rem;
}
.dash {
  width: 3.4rem;
  height: 2px;
  border: 0;
  padding: 0;
  cursor: pointer;
  background: rgba(255, 255, 255, 0.2);
  position: relative;
  overflow: hidden;
  transition: background-color 0.2s ease-out;
}
.dash:hover {
  background: rgba(255, 255, 255, 0.4);
}
.dash::after {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: 0;
  background: #fff;
}
.dash[aria-selected="true"]::after {
  width: 100%;
  transition: width 6s linear;
}

/* ================================================================= split */

.split {
  display: grid;
  gap: clamp(2.5rem, 5vw, 5rem);
  align-items: center;
}
@media (min-width: 56rem) {
  .split {
    grid-template-columns: 1fr 1fr;
  }
  .split--flip .split__media {
    order: -1;
  }
}
.split__text {
  display: grid;
  gap: 1.2rem;
  align-content: start;
}
.split__text .display {
  margin-bottom: 0.6rem;
}

/* =============================================================== mock UI */

.mock {
  border: var(--hairline) solid var(--rule);
  border-radius: 0.7rem;
  overflow: hidden;
  background: #fff;
  box-shadow: 0 30px 60px -35px rgba(13, 20, 24, 0.4);
  font-size: 0.72rem;
}
.mock__bar {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  padding: 0.65rem 0.9rem;
  border-bottom: var(--hairline) solid var(--rule);
  font-weight: 600;
}
.mock__dot {
  width: 0.75rem;
  height: 0.75rem;
  border-radius: 0.25rem;
  background: var(--p-600);
}
.mock__tabs {
  margin-left: auto;
  display: flex;
  gap: 0.8rem;
  font-weight: 500;
  color: var(--ink-3);
  font-size: 0.68rem;
}
.mock__tabs .on {
  color: var(--p-600);
  box-shadow: inset 0 -2px 0 var(--p-600);
  padding-bottom: 0.15rem;
}
.mock table {
  width: 100%;
  border-collapse: collapse;
  font-variant-numeric: tabular-nums;
}
.mock td {
  padding: 0.55rem 0.9rem;
  border-bottom: var(--hairline) solid var(--rule);
}
.mock tr:last-child td {
  border-bottom: 0;
}
.mock .num {
  text-align: right;
}

.pill {
  display: inline-block;
  font-size: 0.6rem;
  font-weight: 620;
  padding: 0.12rem 0.45rem;
  border-radius: 999px;
  white-space: nowrap;
}
.pill--done {
  background: #e7f5ec;
  color: #1a6b3c;
}
.pill--due {
  background: #fdf3e0;
  color: #8a5a12;
}
.pill--late {
  background: #fdeceb;
  color: #a52822;
}

/* ================================================================= specs */

.specs {
  display: grid;
  gap: 1px;
  background: var(--rule);
  border-block: var(--hairline) solid var(--rule);
  margin-top: clamp(2.5rem, 5vw, 4rem);
}
@media (min-width: 52rem) {
  .specs {
    grid-template-columns: repeat(3, 1fr);
  }
}
.spec {
  background: var(--paper);
  padding: 2rem 1.8rem;
  display: grid;
  gap: 0.7rem;
  align-content: start;
  transition: background-color 0.3s ease-out;
}
.spec:hover {
  background: var(--paper-2);
}
.spec p {
  font-size: 0.85rem;
  color: var(--ink-3);
}

/* ================================================================= phone */

.phone {
  width: min(15rem, 68vw);
  aspect-ratio: 9 / 19;
  border-radius: 2rem;
  border: 8px solid var(--ink);
  background: #fff;
  overflow: hidden;
  box-shadow: 0 40px 80px -40px rgba(13, 20, 24, 0.55);
  margin-inline: auto;
}
.phone__head {
  background: var(--p-700);
  color: #fff;
  padding: 1.5rem 1rem 1rem;
  font-size: 0.72rem;
}
.phone__head div {
  opacity: 0.72;
  margin-top: 0.2rem;
}
.phone__body {
  padding: 0.85rem;
  display: grid;
  gap: 0.6rem;
  font-size: 0.68rem;
}
.phone__q {
  border: var(--hairline) solid var(--rule);
  border-radius: 0.55rem;
  padding: 0.6rem;
  display: grid;
  gap: 0.3rem;
}
.phone__q span {
  color: var(--ink-3);
}

/* ================================================================ footer */

.close .wrap {
  display: grid;
  gap: 1.6rem;
  justify-items: start;
  position: relative;
  z-index: 2;
}

.foot {
  padding-block: 3.5rem;
  font-weight: var(--w-body);
  border-top: 1px solid rgba(255, 255, 255, 0.1);
  font-size: 0.78rem;
  color: #7d93a0;
}
.foot__row {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem 2rem;
  justify-content: space-between;
}

/* ================================================================= modal */

dialog.lead {
  border: 0;
  padding: 0;
  border-radius: 0.9rem;
  width: min(28rem, calc(100vw - 2rem));
  background: var(--paper);
  color: var(--ink);
  box-shadow: 0 40px 90px -40px rgba(0, 0, 0, 0.7);
}
dialog.lead::backdrop {
  background: rgba(1, 15, 22, 0.66);
  backdrop-filter: blur(3px);
}
.lead__inner {
  padding: 2rem 1.9rem;
  display: grid;
  gap: 1rem;
}
.lead__grid {
  display: grid;
  gap: 0.75rem;
}
.field {
  display: grid;
  gap: 0.35rem;
}
.field span {
  font-size: 0.72rem;
  font-weight: 560;
  color: var(--ink-3);
}
.field input {
  font: inherit;
  font-size: 1rem; /* 16px: below this iOS zooms the viewport on focus */
  padding: 0.65rem 0.9rem;
  border: var(--hairline) solid var(--rule);
  border-radius: 0.5rem;
  background: var(--paper-2);
  color: inherit;
  outline: none;
  width: 100%;
  transition:
    border-color 0.15s ease-out,
    box-shadow 0.15s ease-out;
}
.field input:focus {
  border-color: var(--p-600);
  box-shadow: 0 0 0 3px rgba(27, 117, 157, 0.22);
}
.hp {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}
.lead__note {
  font-size: 0.78rem;
  color: var(--ink-3);
}
.lead__actions {
  display: flex;
  gap: 0.6rem;
  justify-content: flex-end;
}
.lead__status {
  font-size: 0.8rem;
  min-height: 1.2em;
}
.lead__status[data-state="error"] {
  color: #a52822;
}
.lead__status[data-state="ok"] {
  color: #1a6b3c;
}

/* =============================================================== reveals

   The from-state is set by JS only once it has decided it will animate. With
   JS off, or reduced motion, nothing is ever hidden. */

@media (prefers-reduced-motion: reduce) {
  * {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
  .slide {
    transition: none;
  }
}


/* ===========================================================================
   Sticky side-by-side — the structural pattern that replaced the bento grid.
   Measured as the load-bearing section on Linear (five of them, numbered) and
   Vercel (three consecutive case studies). The text column pins while its
   visual scrolls past, so a capability gets a whole screen without a whole
   page. Numbering is Linear's device and it earns its place here: these are
   genuinely sequential stages of one pipeline, not a feature list.
   =========================================================================== */

.stages {
  margin-top: clamp(3rem, 6vw, 5rem);
  display: grid;
  gap: clamp(4rem, 10vw, 9rem);
}

.stage {
  display: grid;
  gap: 2rem;
  align-items: start;
}
@media (min-width: 60rem) {
  .stage {
    grid-template-columns: 0.85fr 1fr;
    gap: 5rem;
  }
  .stage__text {
    position: sticky;
    top: 22vh;
  }
}

.stage__n {
  display: block;
  font-size: 0.6875rem;
  letter-spacing: 0.1em;
  font-weight: 500;
  color: var(--p-400);
  font-variant-numeric: tabular-nums;
  margin-bottom: 0.9rem;
}

.stage__text h3 {
  font-size: clamp(1.5rem, 2.4vw, 2.1rem);
  font-weight: var(--w-display);
  letter-spacing: var(--track-mid);
  line-height: 1.1;
  margin-bottom: 0.9rem;
}

.stage__panel {
  border: var(--hairline) solid rgba(255, 255, 255, 0.12);
  border-radius: 0.5rem;
  background: rgba(255, 255, 255, 0.025);
  padding: clamp(1.4rem, 2.6vw, 2.2rem);
  display: grid;
  gap: 0.75rem;
}

.stage__row {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 1rem;
  padding-bottom: 0.75rem;
  border-bottom: var(--hairline) solid rgba(255, 255, 255, 0.09);
  font-size: 0.8rem;
  font-variant-numeric: tabular-nums;
}
.stage__row:last-child {
  border-bottom: 0;
  padding-bottom: 0;
}
.stage__row b {
  font-weight: 500;
  color: #fff;
}
.stage__row span {
  color: var(--p-400);
}

/* ------------------------------------------------------------- fat footer
   Six columns is the measured standard (Linear 6, Vercel 7, Attio 7). */
.foot__cols {
  display: grid;
  gap: 2rem 1.5rem;
  grid-template-columns: repeat(2, 1fr);
  margin-bottom: 3rem;
}
@media (min-width: 52rem) {
  .foot__cols {
    grid-template-columns: 2fr repeat(3, 1fr);
  }
}
.foot__col h4 {
  margin: 0 0 0.9rem;
  font-size: 0.6875rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  font-weight: 500;
  color: #eef5f8;
}
.foot__col ul {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 0.55rem;
}
.foot__col a {
  text-decoration: none;
  transition: color 0.15s ease-out;
}
.foot__col a:hover {
  color: #fff;
}
.foot__name {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}
.foot__brand p {
  max-width: 34ch;
  margin-top: 0.8rem;
}

/* A footer action that is a button, not a link, because it opens the modal. */
.foot__link {
  border: 0;
  background: none;
  padding: 0;
  font: inherit;
  color: inherit;
  cursor: pointer;
  text-align: left;
  transition: color 0.15s ease-out;
}
.foot__link:hover {
  color: #fff;
}

/* ===========================================================================
   Accent marks — the one idea worth taking from baseten.co.
   Small flat rectangles that wipe in beside a heading as it reveals. Motion
   identity that never touches the type itself, at one transform per element.
   A second accent (the pink) exists only here: it is decoration, never state,
   so it can be a colour the status pills will never use.
   =========================================================================== */

.marks {
  display: flex;
  gap: 0.35rem;
  margin-bottom: 1.1rem;
}

.mark {
  display: block;
  height: 0.5rem;
  transform-origin: left center;
}
.mark--a {
  width: 3.5rem;
  background: var(--p-500);
}
.mark--b {
  width: 1.1rem;
  background: var(--p-200);
}
.mark--c {
  width: 0.5rem;
  background: #ffa9fd;
}

.block--ink .mark--b {
  background: var(--p-700);
}

/* Section rules that draw themselves, rather than existing as a border that
   was always there. Staggered, so a boundary arrives as a gesture. */
.rule-draw {
  height: var(--hairline);
  background: var(--rule);
  transform: scaleX(0);
  transform-origin: left;
}
.block--ink .rule-draw {
  background: rgba(255, 255, 255, 0.14);
}


/* A keyboard visitor should not have to tab the whole nav to reach the page.
   Off-screen until focused, then pinned over everything. */
.skip {
  position: fixed;
  top: 0.6rem;
  left: 0.6rem;
  z-index: 100;
  padding: 0.65rem 1rem;
  background: var(--p-700);
  color: #fff;
  border-radius: 0.4rem;
  font-size: 0.82rem;
  text-decoration: none;
  transform: translateY(-160%);
  transition: transform 0.2s ease-out;
}
.skip:focus-visible {
  transform: none;
}

/* ===========================================================================
   The set piece — the effect the reference actually has and this page lacked.
   A visual that starts inset and small, then grows to full-bleed as it is
   scrubbed through a pinned section. Reveals and slides are punctuation; this
   is the one moment where the scroll is doing something you can feel.
   =========================================================================== */

.reveal-stage {
  position: relative;
  min-height: 100svh;
  display: grid;
  place-items: center;
  overflow: hidden;
  padding-block: 0;
}

.reveal-stage__inner {
  width: min(64rem, 88vw);
  will-change: transform;
}

.reveal-stage__cap {
  position: absolute;
  left: 0;
  right: 0;
  bottom: clamp(1.5rem, 4vw, 3rem);
  text-align: center;
  font-size: 0.6875rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.45);
}

/* The mock inside the stage runs dark, since the band it sits on is ink. */
.reveal-stage .mock {
  background: #05202c;
  border-color: rgba(255, 255, 255, 0.14);
  color: #dbe7ec;
  box-shadow: 0 50px 100px -50px rgba(0, 0, 0, 0.9);
}
.reveal-stage .mock__bar,
.reveal-stage .mock td {
  border-color: rgba(255, 255, 255, 0.09);
}
.reveal-stage .mock__tabs {
  color: #7d93a0;
}


/* ===========================================================================
   Section transitions — the two mechanics in the client's reference recording.

   A CURTAIN: the incoming band arrives as vertical slats at staggered depths,
   like a blind falling, rather than as a straight edge sliding up.
   An IRIS: the incoming band opens from a circle at the centre.

   Both are pure geometry, which is the point — they were the only two ideas in
   that recording that did not depend on product photography to work.
   =========================================================================== */

.curtain {
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  height: clamp(4rem, 9vw, 9rem);
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  pointer-events: none;
  z-index: 3;
}

.curtain span {
  display: block;
  background: var(--curtain-fill, var(--paper));
  transform: translateY(-100%);
  will-change: transform;
}

/* With no JS the slats simply sit closed, so the boundary is a straight edge
   and nothing looks half-finished. */
.no-js-closed span {
  transform: none;
}

.iris {
  position: absolute;
  inset: 0;
  background: var(--iris-fill, var(--p-950));
  clip-path: circle(0% at 50% 50%);
  pointer-events: none;
  z-index: 3;
}

@media (prefers-reduced-motion: reduce) {
  .curtain {
    display: none;
  }
  .iris {
    display: none;
  }
  .hero__bg::before,
  .hero__bg::after {
    animation: none;
  }
}
