/*
 * styles.v2.css — what the FINAL_LANDING frame has that `index.html` did not.
 *
 * Loaded after `styles.css`, which stays exactly as `index.html` left it: the
 * page-scale system (--dp, --dp-full, --dp-soft, --vw), the buttons, the nav,
 * the hero, the cards and the progressive blur all still come from there.
 * This file adds the three sections the frame changed — the step
 * illustrations, the three-slide carousel and the stat strip — and corrects
 * the handful of measurements that moved with them.
 *
 * Numbers are design pixels read off Figma file HqR8U8EtLVhMcvxPl6iC71,
 * frames 43:144 (FINAL_LANDING), 127:1569 (Slide1) and 127:1579 (Slide3).
 * Horizontal sizes inside the carousel are percentages because the carousel
 * is as wide as the container, which is not exactly 1388 design px at every
 * viewport width; vertical sizes are percentages of the carousel's own
 * height, which is.
 */

:root {
  /* The step media box is a lighter grey than the hero chart's plot. */
  --color-step-media: #e5e5e5;
  /* The carousel's white, and the copy under it: not quite #fff. */
  --color-showcase: #f4f4f4;
  --color-slide-plot: #f3f3f3;
}

/* ---------- Steps: the illustration inside the grey box ---------- */

.step-media {
  overflow: hidden;
  background: var(--color-step-media);
}

/*
 * The frame has one shape for this box — `Rectangle 53`, 403 × 406 — and every
 * illustration is placed against that shape. `styles.css` drops the ratio past
 * 1512 design px and pins the height at 406 instead, so the box goes landscape
 * as the window widens while the illustration, which keeps its own ratio, does
 * not: the drawing grows against a box that is no longer growing, and how much
 * of it the box shows changes with the window. Keeping the frame's ratio at
 * every width is what makes the three cards scale rather than recompose.
 */
@media (min-width: 1095px) { /* page at 1512 design px */
  .step-media {
    aspect-ratio: 403 / 406;
    height: auto;
  }
}

/*
 * The stacked band has the same problem from the other end: `styles.css` lets
 * the box run to the full width of a page-wide card and caps its height at
 * 406, which at 700 makes it 592 × 284 — a 2:1 letterbox through a drawing
 * that is nearly square, so half of step 1's window is gone. The ratio goes
 * back here too, and what gets bounded instead is the card: at the frame's own
 * 463 it holds the illustration at the size the frame drew it, centred, rather
 * than stretching one across the page.
 */
@media (max-width: 826px) { /* page at 1180 design px, where the steps stack */
  .step {
    max-width: calc(463 * var(--dp));
    margin-inline: auto;
  }

  .step-media {
    min-width: 0;
    max-height: none;
    aspect-ratio: 403 / 406;
  }
}

.step-num {
  z-index: 1;
}

/*
 * Each illustration is placed the way the frame places it — two of the three
 * are wider than the box and are cut by it, which is why the box clips.
 */
.step-art {
  position: absolute;
  max-width: none;
  height: auto;
}

/*
 * Raised 2.7% from the frame's placement: the drawing sits low inside its own
 * canvas, which left 10% of the box above it and 4.6% below. Now it is even.
 */
.step-art--install {
  left: -4.218%;   /* 94 - 111 */
  top: -5.7%;
  width: 108.685%; /* 438 / 403 */
}

.step-art--pull-request {
  left: -12.438%;  /* 554 - 604 */
  top: 10.099%;    /* 1445 - 1404 */
  width: 124.876%; /* 502 / 402 */
}

/*
 * Full width, vertically centred. The illustration is clipped left and right
 * in Figma itself — the frame it lives in is narrower than the drawing — so
 * the only placement where the cut lands on the card's own border rather than
 * floating inside the grey box is edge to edge. Nothing is cut vertically.
 */
.step-art--review {
  left: 0;
  top: 2.955%; /* (406 - 382) / 2 */
  width: 100%;
}

/* ---------- Showcase: a three-slide carousel ---------- */

.showcase {
  color: var(--color-showcase);
}

.carousel {
  background: var(--color-showcase);
  /* Vertical drags scroll the page; horizontal ones are the swipe gesture. */
  touch-action: pan-y;
}

/*
 * A white rectangle a little larger than the viewport, blurred: the frame
 * uses it to soften the viewport's edges rather than to hold anything.
 */
.carousel-panel {
  position: absolute;
  top: calc(-14 * var(--dp));
  left: 0;
  width: calc(100% + 44 * var(--dp));
  height: calc(100% + 26 * var(--dp));
  border-radius: calc(14.04 * var(--dp));
  background: var(--color-white);
  filter: blur(calc(18.85 * var(--dp)));
}

.carousel-track {
  position: absolute;
  inset: 0;
  display: flex;
  transform: translateX(calc(var(--slide, 0) * -100%));
  transition: transform 0.45s cubic-bezier(0.4, 0, 0.2, 1);
}

.carousel-slide {
  position: relative;
  flex: 0 0 100%;
  height: 100%;
}

/*
 * The screenshot keeps its own aspect ratio and is cut by the viewport. The
 * corner and the shadow are the frame's own — `Screenshot …1` carries a 5
 * radius and `0 4 24.1 rgba(84,84,84,.3)`, which is what lifts it off the
 * white panel behind it. The viewport clips the bottom of the shadow exactly
 * as `Frame 1` clips it in the frame.
 */
.carousel-shot {
  position: absolute;
  left: 6.628%;   /* 92 / 1388 */
  top: 12.449%;   /* 61 / 490 */
  width: 86.671%; /* 1203 / 1388 */
  height: auto;
  max-width: none;
  border-radius: calc(5 * var(--dp));
  box-shadow: 0 calc(4 * var(--dp)) calc(24.1 * var(--dp)) rgba(84, 84, 84, 0.3);
  /* A drag across a slide is a swipe, not the start of an image drag. */
  -webkit-user-drag: none;
  user-select: none;
}

/*
 * The grey strip sits *under* the slide in the frame, so a screenshot hides
 * it and it only shows in the margins either side — which is why the frame's
 * screenshots stop at a hard edge rather than fading out. No mask: the
 * strip's own alpha ramp is the fade.
 */
.carousel-fade {
  top: calc(100% - 121 * var(--dp));
  left: calc(-45 * var(--dp));
  width: calc(100% + 54 * var(--dp));
  height: calc(208 * var(--dp));
  -webkit-mask-image: none;
  mask-image: none;
}

.carousel-arrow {
  z-index: 2;
}

/* ---------- Slide 3: the benchmark chart, drawn from the numbers ---------- */

/*
 * The chart panel is the same kind of object as a screenshot, and the frame
 * gives it a shadow of its own — a heavier one than the screenshots carry
 * (`Group 23`: 0 11 35.2 rgba(0,0,0,.25)), because it is white on white and
 * has no dark edge to separate it from the panel behind it.
 */
.slide-chart {
  position: absolute;
  left: 6.628%;    /* 92 / 1388 */
  top: 9.796%;     /* 48 / 490 */
  width: 86.671%;  /* 1203 / 1388 */
  height: 94.898%; /* 465 / 490 */
  border-radius: calc(5 * var(--dp));
  background: var(--color-white);
  box-shadow: 0 calc(11 * var(--dp)) calc(35.2 * var(--dp)) rgba(0, 0, 0, 0.25);
}

.slide-plot {
  position: absolute;
  left: 1.547%;    /* 18.6 / 1203 */
  top: 4.301%;     /* 20 / 465 */
  width: 96.809%;  /* 1164.6 / 1203 */
  height: 77.204%; /* 359 / 465 */
  border-radius: calc(3 * var(--dp));
  background: var(--color-slide-plot);
}

/*
 * The frame's height, the opposite corner.
 *
 * It sat at the top left, which worked while the leftmost bar was the
 * shortest. This chart is a descending staircase and the tallest bar now
 * stands exactly where the words were. Below the labels is no good either:
 * the carousel clips the panel a little under them. The top right is the
 * corner a descending chart leaves empty, and it is the same line of the
 * frame, only read from the other end.
 */
.slide-chart figcaption {
  position: absolute;
  right: 2.411%; /* mirrors the frame's 29 / 1203 inset */
  top: 9.032%;   /* (90 - 48) / 465 */
  padding: 0;
  color: #3f3f3f;
  font: 500 calc(20 * var(--dp))/1 var(--font-mono);
  text-align: right;
  transform: translateY(-50%);
}

/* The columns sit on the plot, so a bar and its label cannot drift apart. */
.slide-cols {
  position: absolute;
  left: 2.418%;    /* (121.1 - 92) / 1203 */
  top: 4.301%;
  width: 94.681%;  /* 1139 / 1203 — first bar's left to last bar's right */
  height: 77.204%;
  display: flex;
  align-items: flex-end;
  gap: 1.941%;     /* 22.1 / 1139 */
}

.slide-col {
  position: relative;
  flex: 1 1 0;
  min-width: 0;
  height: var(--v);
}

.slide-bar {
  position: absolute;
  inset: 0;
  border-radius: calc(4 * var(--dp)) calc(4 * var(--dp)) 0 0;
  background: var(--color-ink);
}

.slide-bar--accent {
  background: var(--color-accent);
}

.slide-label {
  position: absolute;
  top: 100%;
  left: 50%;
  width: max-content;
  margin-top: calc(10 * var(--dp));
  color: var(--color-ink);
  font: 500 calc(15.4 * var(--dp))/1.21 var(--font-sans);
  text-align: center;
  transform: translateX(-50%);
}

.slide-label span {
  display: block;
  white-space: nowrap;
}

.slide-label--accent {
  color: var(--color-accent);
  font-weight: 700;
}

/* ---------- Nav: the actions at the end of the bar ---------- */

/*
 * The waitlist button and the menu share the bar's end track, so the grid
 * places the pair rather than the button itself.
 */
.nav-actions {
  display: flex;
  justify-self: end;
  align-items: center;
  gap: 6px;
}

/*
 * The hamburger exists only while the bar cannot carry its own links. Above
 * that width `.nav-links` is back and a button that opens a copy of it would
 * be a second way to the same three places.
 */
.nav-menu {
  display: none;
}

@media (max-width: 680px) {
  /* A `details` that is a control, not a paragraph that folds away. */
  .nav-menu {
    display: block;
  }

  .nav-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: calc(52 * var(--dp-soft));
    height: calc(52 * var(--dp-soft));
    border-radius: var(--radius-button);
    background: rgba(255, 255, 255, 0.1);
    cursor: pointer;
    transition: background-color 0.15s ease;
    /* The disclosure triangle, twice: a `::marker` in one engine and a
       `::-webkit-details-marker` in the other. */
    list-style: none;
  }

  .nav-toggle::-webkit-details-marker {
    display: none;
  }

  .nav-toggle:hover {
    background: rgba(255, 255, 255, 0.18);
  }

  .nav-toggle:focus-visible {
    outline: 3px solid var(--color-accent);
    outline-offset: 3px;
  }

  /*
   * Three bars from one span: the span is the middle one and its two ends are
   * the outer pair, so the icon is one element to turn into a cross.
   */
  .nav-toggle-bars,
  .nav-toggle-bars::before,
  .nav-toggle-bars::after {
    width: calc(22 * var(--dp-soft));
    height: 2px;
    border-radius: 2px;
    background: var(--color-white);
    transition: transform 0.2s ease, background-color 0.2s ease;
  }

  .nav-toggle-bars {
    position: relative;
    display: block;
  }

  .nav-toggle-bars::before,
  .nav-toggle-bars::after {
    content: "";
    position: absolute;
    left: 0;
  }

  .nav-toggle-bars::before {
    top: calc(-7 * var(--dp-soft));
  }

  .nav-toggle-bars::after {
    top: calc(7 * var(--dp-soft));
  }

  /*
   * Open, the middle bar goes and the outer pair crosses where it was. The
   * middle one loses its colour rather than its opacity: the other two are
   * its own pseudo-elements, and fading the span fades them with it.
   */
  .nav-menu[open] .nav-toggle-bars {
    background: transparent;
  }

  .nav-menu[open] .nav-toggle-bars::before {
    top: 0;
    transform: rotate(45deg);
  }

  .nav-menu[open] .nav-toggle-bars::after {
    top: 0;
    transform: rotate(-45deg);
  }

  /*
   * The panel hangs under the bar and is as wide as it, in the bar's own
   * material: same colour, same blur, same radius, so it reads as the bar
   * having opened rather than as a card that has landed on the hero.
   */
  .nav-menu-panel {
    position: absolute;
    top: calc(100% + 8px);
    right: 0;
    left: 0;
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: 10px;
    border-radius: 16px;
    background: rgba(36, 31, 31, 0.93);
    -webkit-backdrop-filter: blur(31px);
    backdrop-filter: blur(31px);
  }

  /* Full-width rows: the panel is a stack of targets, not a line of chips. */
  .btn--menu {
    width: 100%;
    min-width: 0;
    font-weight: 600;
  }
}

/* ---------- Dots: buttons now that they pick a slide ---------- */

.dots .dot {
  width: 13px;
  height: 13px;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.22);
  cursor: pointer;
  transition: width 0.3s ease, background-color 0.3s ease;
}

.dots .dot:hover {
  background: rgba(255, 255, 255, 0.45);
}

.dots .dot.is-active {
  width: 55px;
  background: var(--color-white);
}

.dots .dot:focus-visible {
  outline: 3px solid var(--color-white);
  outline-offset: 3px;
}

/*
 * The copy under the viewport belongs to the slide, so it swaps with it. All
 * three sit in the same grid cell: the block is then as tall as the longest
 * of them and the section does not resize as the carousel moves.
 */
.showcase-copies {
  display: grid;
  width: 100%;
}

.showcase-copy {
  grid-area: 1 / 1;
  display: flex;
  flex-direction: column;
  align-items: center;
}

/* Overrides the UA's display:none: the copy keeps its space, loses its ink. */
.showcase-copy[hidden] {
  display: flex;
  visibility: hidden;
  pointer-events: none;
}

/* ---------- Stat strip (full-bleed, overflows both edges) ---------- */

/*
 * The tiles drift. The track holds the four figures twice and slides left by
 * exactly half its width, which puts the copy where the original was, so the
 * loop has no seam. Each tile carries its own right margin rather than the
 * row using `gap`, because -50% is only exact when the gaps are part of the
 * repeated unit.
 *
 * At 1512 design px a tile is 465 wide with 30 between, which is the frame;
 * the width keeps the row wider than the window on every screen, so the ends
 * never pull inside it.
 */
.stat-strip {
  --stat-gap: clamp(16px, 2 * var(--vw), 30 * var(--dp));
  --stat-width: clamp(200px, 30.75 * var(--vw), 560 * var(--dp));
  /* One tile every ~30s: slow enough to read as drift, not as a ticker. */
  --stat-cycle: 120s;
  overflow: hidden;
}

.stat-track {
  display: flex;
  width: max-content;
  /* Starts part-way into the first tile, the way the frame crops it. */
  margin-left: calc(var(--stat-width) * -0.37);
  animation: stat-drift var(--stat-cycle) linear infinite;
}

@keyframes stat-drift {
  to {
    transform: translateX(-50%);
  }
}

.stat {
  display: flex;
  flex: none;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  width: var(--stat-width);
  margin-right: var(--stat-gap);
  aspect-ratio: 465 / 176;
  padding: 0 calc(20 * var(--dp));
  border-radius: calc(23 * var(--dp));
  background: var(--color-white);
  color: var(--color-black);
  text-align: center;
}

.stat-value {
  font: 800 calc(64 * var(--dp))/1.21 var(--font-sans);
  white-space: nowrap;
}

.stat-label {
  font: 800 calc(20 * var(--dp))/1.2 var(--font-sans);
}

/* ---------- Features wall ---------- */

/*
 * The frame crops the wall 1712 design px below the top of the first card.
 * Every column runs past that, so the crop reads as "more below" rather than
 * as the wall running out. `.features-more` replaces this with a measured
 * height when it opens the wall up.
 */
/*
 * No height transition. Animating it re-lays-out every card in the wall on
 * every frame, which made opening and closing the wall crawl. The switch is
 * instant instead. (The fade over the bottom is a single static gradient, so
 * it adds no per-frame cost either.)
 */
.features-grid {
  height: calc(1712px + 2 * var(--grow));
}

/*
 * Taller than the frame's fade rectangle. At the frame's height the topmost
 * cards in the fade were still legible enough to read as the end of a column
 * rather than as cards carrying on underneath.
 */
.features-fade {
  height: calc(820 * var(--dp));
}

/* The CTA card sits higher than it did, to leave room for the link below it. */
.cta {
  bottom: calc(114 * var(--dp));
}

.features-block {
  position: relative;
}

.features-more {
  position: absolute;
  left: 50%;
  bottom: calc(44 * var(--dp));
  z-index: 2;
  border: 0;
  font-size: calc(20 * var(--dp));
  cursor: pointer;
  transform: translateX(-50%);
}

/* ---------- The figure behind a bar ---------- */

/*
 * A bar encodes a ratio in a height, and a height is not readable: the frame
 * gives both charts a name under each bar and no numbers anywhere. Pointing
 * at a bar, or tabbing to it, shows what it is worth and the two measurements
 * it came from. The same words are on the bar as an `aria-label`, so a screen
 * reader never has to hover anything to get them.
 *
 * Both charts share the pill; only where it is anchored differs.
 */
.bar-tip,
.slide-tip {
  position: absolute;
  left: 50%;
  z-index: 4;
  display: flex;
  flex-direction: column;
  gap: calc(2 * var(--dp));
  width: max-content;
  max-width: calc(210 * var(--dp));
  padding: calc(8 * var(--dp)) calc(11 * var(--dp)) calc(9 * var(--dp));
  border-radius: calc(7 * var(--dp));
  background: var(--color-ink);
  color: var(--color-white);
  text-align: center;
  /* It is a read-out, never a target: it must not eat the hover it answers. */
  pointer-events: none;
  opacity: 0;
  transform: translate(-50%, calc(3 * var(--dp)));
  transition: opacity 0.12s ease, transform 0.12s ease;
}

.bar-tip strong,
.slide-tip strong {
  color: var(--color-white);
  font: 700 calc(19 * var(--dp))/1.1 var(--font-sans);
  white-space: nowrap;
}

.bar-tip small,
.slide-tip small {
  color: var(--color-text-on-dark);
  font: 400 calc(13 * var(--dp))/1.25 var(--font-sans);
  white-space: nowrap;
}

.bar:hover .bar-tip,
.bar:focus-visible .bar-tip,
.slide-col:hover .slide-tip,
.slide-col:focus-visible .slide-tip {
  opacity: 1;
  transform: translate(-50%, 0);
}

.bar:focus-visible,
.slide-col:focus-visible {
  outline: 3px solid var(--color-white);
  outline-offset: 2px;
}

.slide-col:focus-visible {
  outline-color: var(--color-ink);
}

/*
 * Inside the bar rather than over it. The hero's accent bar is 94% of the
 * plot, so there is nothing above it to put a pill in, and every bar on this
 * chart is tall enough to hold one. `styles.css` makes the bar a flex row for
 * its label, so the pill is taken out of flow and the row is unaffected.
 */
.bar {
  position: relative;
}

.bar-tip {
  top: calc(10 * var(--dp));
}

/*
 * Over the bar. This chart's bars go down to 5% of the plot, which is too
 * short to hold anything, and the panel above them is white and empty.
 */
.slide-tip {
  bottom: calc(100% + 6 * var(--dp));
}

/* ---------- Figures in the boxes the frame left empty ---------- */

/*
 * The mockup has a dozen grey rectangles standing in for pictures we do not
 * have. Rather than ship the grey, each one holds a measured figure: same
 * box, same ratio, same button in the corner, but it says something. The box
 * goes to the page's own ink so the number reads as a plate rather than as a
 * picture that failed to load.
 */
.card-media--figure {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: calc(24 * var(--dp));
  background: var(--color-ink);
}

/* Room for the button in the corner, so the figure stays centred above it. */
.card-media--figure:has(.btn) {
  padding-bottom: calc(76 * var(--dp));
}

.card-figure {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: calc(9 * var(--dp));
  text-align: center;
}

.card-figure-value {
  color: var(--color-white);
  font: 800 calc(46 * var(--dp))/1.05 var(--font-sans);
  letter-spacing: -0.02em;
}

.card-figure-label {
  max-width: calc(300 * var(--dp));
  color: var(--color-text-on-dark);
  font: 500 calc(18 * var(--dp))/1.25 var(--font-sans);
}

/* ---------- Screenshots in a card-sized box ---------- */

/*
 * A card's box is a quarter of the width a product screenshot was captured
 * at, and `object-fit: cover` on a shot with nearly the box's own ratio just
 * shrinks the whole screen into a grey smudge. These are placed the way the
 * step illustrations are instead — scaled up and pinned to the top-left —
 * so the box holds one legible corner of the interface. The percentages are
 * the shot's full width over the width of the crop it should show.
 */
.card-media .card-shot {
  position: absolute;
  top: 0;
  left: 0;
  height: auto;
  max-width: none;
  object-fit: fill;
}

/* 1960 wide, cropped to the 1100 that holds the nav and the expanded run. */
.card-shot--run-log {
  width: 178.2%;
}

/* 2084 wide, cropped to the 1100 that holds the nav and the token chart. */
.card-shot--console {
  width: 189.5%;
}

/* ---------- The wall, opened up ---------- */

/*
 * Open, the wall is not a crop of something longer — it is the whole thing —
 * so the blur that stood for "more below" goes and the black rectangle holds
 * nothing but cards. The CTA and the control that closes the wall again drop
 * out of their floating positions and stack underneath it.
 */
.features-block[data-expanded] .features-fade {
  display: none;
}

/*
 * The crop goes with it: the grid is as tall as its tallest column, so no card
 * is cut, and the rectangle closes 30 below the last one — the same inset it
 * already has at the top and down both sides. Nothing is measured in script;
 * `height: auto` is the whole of it.
 */
.features-block[data-expanded] .features {
  padding-bottom: calc(30 * var(--dp));
}

.features-block[data-expanded] .features-grid {
  height: auto;
  overflow: visible;
}

/*
 * Every other column is shorter than the tallest, and that slack has to go
 * somewhere or the column stops short of the bottom edge and the rectangle is
 * no longer full. It goes into the media boxes: they carry no copy, so a card
 * gaining 40px reads as a slightly taller picture rather than as a card with
 * odd padding, and the gaps between cards stay the 30 they are everywhere
 * else. The boxes share it equally, so no single one stretches far.
 */
.features-block[data-expanded] .card:has(.card-media) {
  flex-grow: 1;
}

.features-block[data-expanded] .card-media {
  flex-grow: 1;
}

/*
 * A column with no media box at all — every card text to the bottom — has
 * nothing invisible to stretch, so the slack goes under its last card and the
 * button in it sits against the bottom edge instead of floating mid-card.
 */
.features-block[data-expanded] .features-col:not(:has(.card-media)) > .card:last-child {
  flex-grow: 1;
}

.features-block[data-expanded] .features-col:not(:has(.card-media)) > .card:last-child .card-action {
  margin-top: auto;
}

.features-block[data-expanded] .cta {
  position: static;
  width: min(694 * var(--dp), 100%);
  margin: calc(30 * var(--dp)) auto 0;
  transform: none;
}

.features-block[data-expanded] .features-more {
  position: static;
  display: block;
  margin: calc(24 * var(--dp)) auto 0;
  transform: none;
}

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

/*
 * The frame's footer is a wordmark alone in a 338-tall black band. The band
 * is the frame's; what fills it is not, because the page now quotes measured
 * figures and a figure is only quotable if the page says where it came from.
 * Links sit across the top, the sources-and-caveats line along the bottom.
 */
.footer {
  display: flex;
  flex-direction: column;
  gap: calc(36 * var(--dp));
  padding: calc(40 * var(--dp));
}

.footer-top {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: calc(40 * var(--dp));
}

.footer-brand {
  display: flex;
  flex-direction: column;
  gap: calc(16 * var(--dp));
  max-width: calc(430 * var(--dp));
}

.footer-logo {
  display: block;
}

.footer-tagline {
  color: var(--color-text-on-dark);
  font: 400 calc(18 * var(--dp))/1.35 var(--font-sans);
  text-wrap: pretty;
}

.footer-cols {
  display: flex;
  flex-wrap: wrap;
  gap: calc(30 * var(--dp)) calc(72 * var(--dp));
}

.footer-col {
  display: flex;
  flex-direction: column;
  gap: calc(11 * var(--dp));
}

.footer-col h2 {
  color: var(--color-white);
  font: 700 calc(19 * var(--dp))/1.2 var(--font-sans);
}

.footer-col a {
  color: var(--color-text-on-dark);
  font: 400 calc(18 * var(--dp))/1.2 var(--font-sans);
  text-decoration: none;
}

.footer-col a:hover {
  color: var(--color-white);
  text-decoration: underline;
  text-underline-offset: 4px;
}

/*
 * Pushed to the bottom of the band, under a hairline, and set small: it is
 * the apparatus behind the numbers, not a paragraph anyone has to read. The
 * rule is on the wrapper so it runs the width of the band while the line
 * length stays somewhere a reader could follow if they wanted to.
 */
.footer-foot {
  margin-top: auto;
  padding-top: calc(22 * var(--dp));
  border-top: 1px solid rgba(255, 255, 255, 0.12);
}

.footer-note {
  max-width: calc(1180 * var(--dp));
  color: #7f7f7f;
  font: 400 calc(13 * var(--dp))/1.5 var(--font-sans);
  text-wrap: pretty;
}

/* ---------- Padding corrections ---------- */

/*
 * Measurements transcribed from the frame that read as mistakes rather than
 * as intent once the page is built: sides that differ by a pixel or two,
 * insets given in raw pixels inside a layout that scales everything else
 * with --dp, and one text block indented past the picture above it. Each
 * rule below squares one of those off. The frame's *proportions* are
 * untouched — nothing here changes a height, a ratio or a column.
 */

/*
 * The bar carried 15px of padding and then gave the wordmark 11px of margin
 * on top of it, so the logo sat 26 from the left edge and the button 15 from
 * the right.
 *
 * One inset at both ends instead, and the inset is the air the waitlist
 * button already has above and below it: the bar is a fixed 82 and the button
 * scales with --dp-soft, so half of what is left over is the only value that
 * puts the same gap on all three edges the button touches. A flat 20 made it
 * sit further from the end of the bar than from the top of it.
 */
.nav {
  --nav-inset: calc((82px - 52 * var(--dp-soft)) / 2);
  padding: 0 var(--nav-inset);
}

.nav-logo {
  margin-left: 0;
}

/* 28 right against 30 left, 30 bottom. */
.hero {
  padding-right: calc(30 * var(--dp));
}

/* 27 left against 21 on the other three sides. */
.hero-panel {
  padding-left: calc(21 * var(--dp));
}

/*
 * The button row sat 3px right of the heading above it, and its 32px gap was
 * the one measurement in the hero that did not grow with the page. Below it
 * the same story: 21 of panel padding plus 3 under the row, so both ends
 * scale and the 24 holds past 1512 instead of drifting on a fixed 3. (The
 * frame holds 30 here; review found that ~20% too much air under the row.)
 */
.hero-copy .actions {
  margin: auto 0 calc(3 * var(--dp)) 0;
  padding-top: calc(32 * var(--dp));
}

/* 12px between bars while everything either side of them scales. */
.chart-plot {
  gap: calc(12 * var(--dp-full));
}

/* The caption's foot was 10 against the chart's own 16 on the other sides. */
.chart figcaption {
  padding: calc(10 * var(--dp)) 0 calc(16 * var(--dp));
}

/* 26 at the foot against 30 on the other three sides. */
.step {
  padding-bottom: calc(30 * var(--dp));
}

/*
 * The heading and the copy were indented 7px past the illustration above
 * them and stopped 15 short of its right edge, so the text block sat inside
 * the picture rather than under it. Flush with the box at both ends now.
 */
.step h3 {
  margin: calc(15 * var(--dp)) 0 0;
  max-width: none;
}

.step p {
  margin: calc(5 * var(--dp)) 0 0;
  max-width: none;
}

/*
 * One rhythm for every card: 20/21 inside the border, then the same gap
 * between a picture and the text on either side of it. The frame gives the
 * three card shapes three different sets (13/21/20, 20/21/13, 21/21/87),
 * which reads as three different cards rather than one wall.
 */
.card,
.card--media-first,
.card--tail {
  padding: calc(20 * var(--dp)) calc(21 * var(--dp));
}

.card > p,
.card-text {
  margin-top: calc(6 * var(--dp));
}

.card-media,
.card--media-first h3 {
  margin-top: calc(14 * var(--dp));
}

.card--media-first .card-media {
  margin-top: 0;
}

/* 15px from the corner of a box that is itself inset by a scaling 21. */
.card-media .btn {
  right: calc(15 * var(--dp));
  bottom: calc(15 * var(--dp));
}

/*
 * The button under a card's text hung 6px past the card's right padding and
 * 3px below its foot. Inside both now, like everything else in the card.
 */
.card-action {
  margin: calc(10 * var(--dp)) 0 0;
}

/* 15 top against 19 bottom, and a button row with the gap taken out of it. */
.cta {
  padding-top: calc(19 * var(--dp));
}

.cta .actions {
  gap: 4px;
}

@media (prefers-reduced-motion: reduce) {
  .stat-track {
    animation: none;
  }
}

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

/*
 * Stacked hero keeps the frame's 30 at the foot. Stacking changes neither
 * side of it — the panel still carries 21 down here, and the row's auto top
 * margin simply goes to zero with the copy's min-height — so the row still
 * brings 3. Phones carry their own rule below: the panel's foot drops to 16
 * there, so the row brings the other 8 instead.
 */
@media (max-width: 826px) {
  .hero-copy .actions {
    margin-bottom: calc(3 * var(--dp));
  }
}

/*
 * Phones. Below 490px the page drops its 70% zoom and every section is one
 * column wide, which changes what each of these pieces has to do: a media box
 * that was a third of a row is now the full width of the screen, a chart drawn
 * for 1203px has 293 to draw in, and the bar has to hold a second button.
 */
@media (max-width: 490px) { /* page at 700 design px */
  html {
    /* Clears the 64px bar at its 12px offset rather than the desktop one. */
    scroll-padding-top: 88px;
  }

  /* ---- Nav: logo, pricing, waitlist, all in 375px ---- */

  /*
   * Full container width. The bar lines up with the sections under it instead
   * of sitting inset from them, and the 16px it gains is most of what the
   * second button needs.
   *
   * Flex, not the three-track grid: with nothing in the middle track the two
   * ends are each given half the bar, and two buttons need more than half of
   * 300px — they ran under the wordmark. Here the buttons keep their size and
   * the wordmark takes what is left, down to whatever a 300px phone has.
   */
  .nav {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    width: calc(100% - var(--gutter) * 2);
    /* Same rule at a 64 bar holding 42 controls: half the leftover. */
    --nav-inset: 11px;
    padding: 0 var(--nav-inset);
  }

  .nav-logo {
    flex: 0 1 auto;
    min-width: 0;
    margin-left: 0;
  }

  .nav-logo img {
    width: 96px;
    max-width: 100%;
  }

  .nav-actions {
    flex: none;
  }

  /* One size for the button and the hamburger: the bar is 64, so 42 in it. */
  .nav-actions .btn {
    height: 42px;
    min-width: 0;
    padding: 0 12px;
    font-size: 16px;
  }

  .nav-toggle {
    width: 42px;
    height: 42px;
  }

  .nav-toggle-bars,
  .nav-toggle-bars::before,
  .nav-toggle-bars::after {
    width: 18px;
  }

  .nav-toggle-bars::before { top: -6px; }
  .nav-toggle-bars::after { top: 6px; }

  .nav-menu-panel {
    top: calc(100% + 6px);
    border-radius: 12px;
  }

  /*
   * The menu rows are the one place a phone has room for a full-size target,
   * so they are not held to the 42 the bar's own controls are. Selected past
   * `.nav-actions .btn`, which the menu sits inside.
   */
  .nav-menu-panel .btn {
    height: 48px;
    font-size: 17px;
  }

  /* ---- Hero ---- */

  /*
   * The pair does not fit on one line at this width and wrapping left the link
   * dangling under a button half the row wide. Stacked full-width instead: the
   * primary reads as the page's call and the secondary as its alternative.
   */
  .hero-copy .actions,
  .cta .actions {
    flex-direction: column;
    align-items: stretch;
    gap: 8px;
  }

  /*
   * The section inset drops to 12 here and the panel's foot to 16, so the
   * row brings the other 8 of the 24 itself.
   */
  .hero-copy .actions {
    margin-bottom: 8px;
  }

  /*
   * Edge to edge. Inset by the gutter, the painting read as a picture in a
   * dark frame rather than as the page's backdrop. It starts at the top of
   * the screen (under the floating bar, past the body's 16) and stops square
   * where the step cards begin.
   */
  .hero.container {
    width: 100%;
    margin-top: -16px;
    padding-top: 16px;
    border-radius: 0;
  }

  /* Otherwise the hero's -16 collapses into the bar's -76 and only the
     larger of the two applies, leaving a strip of page above the painting. */
  main:has(> .hero:first-child) {
    display: flow-root;
  }

  /*
   * Equal columns. Each bar used to shrink from the same basis only down to
   * its own label's min-content, so "DeepSeek" held its bar wider than the
   * rest. A zero basis and no minimum split the plot five equal ways.
   */
  .bar {
    flex: 1 1 0;
    min-width: 0;
  }

  .bar span {
    max-width: 100%;
    letter-spacing: -0.02em;
  }

  /* ---- Carousel ---- */

  .carousel-panel {
    top: -10px;
    width: calc(100% + 30px);
    height: calc(100% + 18px);
    filter: blur(12px);
  }

  /*
   * The frame's screenshot inset is 92px of 1388 — on a phone that spends a
   * fifth of the viewport on white margin around a product shot already too
   * small to read. Nearly edge to edge instead, and centred in what is left.
   */
  .carousel-shot {
    left: 4%;
    top: 9%;
    width: 92%;
  }

  .carousel-fade {
    top: calc(100% - 25%);
    left: -12px;
    width: calc(100% + 24px);
    height: 42%;
  }

  /*
   * No arrows. Two 38px targets in the corners of the slide cover the corners
   * of the slide — on the benchmark chart they sit on the first and last
   * label — and the gesture that moves a carousel on a phone is a swipe.
   * `carousel.ts` wires that, and the dots below still move it by tap or key.
   */
  .carousel-arrow {
    display: none;
  }

  /* ---- Slide 3: twelve labels in 293px ---- */

  /*
   * Horizontal, the labels are four times the width of their own column and
   * every one of them overlaps its neighbour. Turned on their side they read
   * down from the bar they belong to, which is what an axis this crowded does
   * on a phone. The plot gives up the height they need.
   */
  .slide-plot,
  .slide-cols {
    top: 13%;
    height: 50%;
  }

  .slide-label {
    margin-top: 5px;
    /* Small, because "Gemini 3.5 Flash" turned on its side has to clear the
       bottom of the panel: at 7px it runs 56 of the 82px it has. */
    font-size: 7px;
    text-align: left;
    white-space: nowrap;
    transform-origin: 0 0;
    transform: rotate(90deg) translateY(-50%);
  }

  /* The frame's hand-broken lines run together into one turned label. */
  .slide-label span {
    display: inline;
  }

  .slide-label span + span::before {
    content: "\00a0";
  }

  /*
   * A band of its own above the plot. The line is 55 characters and the panel
   * is 268px wide, so there is no corner of this plot it can sit in without
   * crossing a bar. The plot gives up the top of the panel instead, which is
   * cheaper than the caption giving up the words that name its scale.
   */
  .slide-chart figcaption {
    right: auto;
    left: 2.411%;
    top: 6.5%;
    font-size: 8px;
    text-align: left;
    transform: none;
  }

  /*
   * A 9px dot is a 9px tap target. The pill keeps its size and the target
   * grows around it as an empty box, rather than by padding the dot: every
   * `background` shorthand on a dot's state would reset a `background-clip`.
   */
  .dots {
    /* Wide enough apart that neighbouring targets meet rather than overlap. */
    gap: 8px;
  }

  .dots .dot {
    position: relative;
    width: 9px;
    height: 9px;
  }

  .dots .dot::before {
    content: "";
    position: absolute;
    inset: -13px -4px;
  }

  .dots .dot.is-active {
    width: 38px;
  }

  /* ---- Stat strip ---- */

  /*
   * At the shared clamp a tile was 200px wide with 34px figures in it, and
   * "15× cheaper" ran 13px past its own tile. Wider tiles, smaller figures.
   */
  .stat-strip {
    --stat-gap: 12px;
    --stat-width: clamp(240px, 66 * var(--vw), 300px);
  }

  .stat {
    padding: 0 14px;
    border-radius: 18px;
  }

  .stat-value {
    font-size: clamp(24px, 7.2 * var(--vw), 34px);
  }

  .stat-label {
    font-size: clamp(12px, 3.4 * var(--vw), 15px);
  }

  /* ---- Cards ---- */

  /*
   * A card is the full width of the page here, so a box at the frame's ratio
   * comes out 340px tall: a screenful of picture before the next heading, and
   * for the boxes the mockup leaves empty, a screenful of grey. Capping the
   * height keeps a card to about heading, copy and picture — the box still
   * spans the card, it just stops getting taller.
   */
  .card-media {
    max-height: 240px;
  }

  /* Nothing in it: it gets the least room of all. */
  .card-media:not(:has(img)):not(.card-media--figure) {
    max-height: 168px;
  }

  /*
   * A figure box is sized by what is written in it rather than by the
   * frame's ratio: a number and a line of label need about a third of a
   * screenful, and the button underneath needs its own room.
   */
  .card-media--figure {
    max-height: 210px;
    padding: 16px;
  }

  .card-media--figure:has(.btn) {
    padding-bottom: 66px;
  }

  .card-figure-value {
    font-size: clamp(30px, 8.6 * var(--vw), 38px);
  }

  .card-figure-label {
    font-size: 15px;
  }

  /*
   * One padding for every card. The frame's are cut for a 398px column and
   * read as uneven once the card is the page — the tail card's 87px foot in
   * particular left a hand's width of white under an empty box.
   */
  .card,
  .card--media-first,
  .card--tail {
    padding: 16px;
  }

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

  .footer {
    gap: 24px;
    padding: 20px;
  }

  .footer-top {
    gap: 24px;
  }

  .footer-cols {
    gap: 20px 40px;
  }

  .footer-foot {
    padding-top: 16px;
  }

  .footer-tagline {
    font-size: 16px;
  }

  .footer-note {
    font-size: 12px;
  }

  .card-text p + p {
    margin-top: 16px;
  }

  /* ---- Features wall ---- */

  /*
   * The wall's crop and the fade over it are measured for three columns. In
   * one column they have to be re-cut around the CTA, which floats over the
   * bottom 300px of the section: a fade shorter than that is entirely behind
   * the card, so the wall ends on a crisp card sliced off by it instead of
   * fading out. 1480 puts two cards clear of the CTA and a third going into
   * the fade, which starts above the card and carries under it.
   */
  .features-grid {
    height: 1480px;
  }

  .features-fade {
    height: 530px;
  }

  /* The section's inset drops to 12 here, so the bottom one has to follow. */
  .features-block[data-expanded] .features {
    padding-bottom: 12px;
  }

  .cta {
    bottom: calc(76 * var(--dp));
  }

  .features-more {
    bottom: calc(22 * var(--dp));
    font-size: 17px;
  }
}

/*
 * Small phones. The bar is the tightest thing on the page — a wordmark and two
 * buttons — so it is the only thing that needs another size below 360px.
 */
@media (max-width: 360px) {
  :root {
    --gutter: 12px;
  }

  /* The controls drop to 38 here, so the air around them grows to match. */
  .nav {
    --nav-inset: 13px;
    padding: 0 var(--nav-inset);
  }

  .nav-logo {
    margin-left: 0;
  }

  .nav-logo img {
    width: 84px;
  }

  .nav-actions {
    gap: 4px;
  }

  .nav-actions .btn {
    height: 38px;
    padding: 0 9px;
    font-size: 15px;
  }

  .nav-toggle {
    width: 38px;
    height: 38px;
  }

  /* The bar's controls shrink; the menu's rows stay a comfortable target. */
  .nav-menu-panel .btn {
    height: 44px;
    font-size: 16px;
  }
}

/*
 * `Log in` beside `Sign up`: text only, in the bar's own white, Inter at
 * medium weight, the same size as the bar's links. No box of its own — the
 * button beside it is the call; this is the way back in.
 */
.nav-login {
  padding: 8px calc(14 * var(--dp-soft));
  color: var(--color-white);
  font: 500 calc(24 * var(--dp-soft))/1.2 var(--font-sans);
  text-decoration: none;
  white-space: nowrap;
}

.nav-login:hover {
  text-decoration: underline;
  text-underline-offset: 4px;
}

.nav-login:focus-visible {
  outline: 3px solid var(--color-accent);
  outline-offset: 3px;
  border-radius: 4px;
}

/* With the hamburger, `Log in` is the menu's last row instead (render.ts). */
@media (max-width: 680px) {
  .nav-login {
    display: none;
  }
}
