/* ──────────────────────────────────────────────────────────────────────
   /app — the Soba App page — on the system in landing.css + night.css.

   NOT app.css, which is a different stylesheet entirely: the old design
   system, still serving /connect/*, /changelog, /docs and /blog. The page
   is at /app and the sheet is called soba-app.css because that name was
   already spoken for, and quietly taking it would have retired four
   sections of the site.

   It is two pages' worth of markup: the argument that used to live at
   /soba-connect, and the download machinery that was already here. Both
   came across from app.css untouched, so this sheet is a TRANSLATION
   rather than a new design: every class name below already
   existed, and what changed is the scale it is drawn in. app.css had its
   own two inks, its own two lines and a white card; the mapping used
   throughout is

     --ink    → --ink      --line    → --line2   (the weaker hairline)
     --ink-2  → --ink2     --line-2  → --line    (the stronger one)
     --muted  → --ink2     #fff card → --night2
     --faint  → --ink3     --ground  → --soft

   and nothing else is reinterpreted. Where app.css leaned on a shadow to
   lift a card off white, the lift here is the ground: a panel one shade
   above the page needs no shadow, and a shadow on night is a smudge.
   ────────────────────────────────────────────────────────────────────── */

/* ── the page's own scaffolding, in the landing vocabulary ───────────── */
/* .inner was app.css's column and .section its rhythm. Rather than rewrite
   every wrapper in the markup, they are given the measurements .in and
   .band already use, so the two pages line up to the pixel. */
.inner {
  width: 100%; max-width: 1200px; margin-inline: auto;
  padding-inline: 24px; position: relative;
}
/* air. A premium page is mostly the room around things, and 112 was the
   front page's rhythm for bands that carry more than these do. */
.section { padding-block: 136px; }
.section + .section { border-top: 1px solid var(--line); }
/* .act-k / .act-h / .act-p are this page's eyebrow, heading and lede */
.act-k {
  display: inline-flex; align-items: center; line-height: 1;
  font-variant-ligatures: none;
  font-family: "JetBrains Mono", monospace; font-size: 12px;
  letter-spacing: .08em; text-transform: uppercase;
  color: var(--ink3); margin-bottom: 20px;
}
/* Display type is set tighter than text type — at 52px the default
   tracking reads as gappy. `balance` keeps a two-line heading from leaving
   one word on the second line. */
.act-h {
  font-size: 52px; line-height: 1.04; letter-spacing: -.045em; font-weight: 600;
  max-width: 18ch; text-wrap: balance;
}
.act-p { margin-top: 20px; font-size: 18px; line-height: 1.62; color: var(--ink2); max-width: 680px; text-wrap: pretty; }
.act-fig { margin-top: 56px; }
/* ── 1 · the opening ─────────────────────────────────────────────────── */
/* THE SAME OPENING /pricing HAS, down to the three layers it is made of:
   the low centred glow, the hairline seam down the middle of the window,
   and the night under both. Arriving here from there should be the same
   room, and that only happens if the gradients are the same gradients.

   The circuit tracery is what this page keeps of its own. It sits UNDER
   the glow rather than beside it — masked to a soft oval and dropped to
   where it is a texture you notice second, not a drawing competing with
   the headline. */
.cn-open {
  position: relative; overflow: hidden; text-align: center;
  background:
    radial-gradient(ellipse 42% 68% at 50% 80%, rgba(85, 109, 180, .18), transparent 72%),
    linear-gradient(90deg, transparent 49.9%, rgba(143, 155, 190, .08) 50%, transparent 50.1%),
    var(--night);
}
.cn-open-bg {
  position: absolute; inset: 0; color: #141824; opacity: .42; pointer-events: none;
  -webkit-mask-image: radial-gradient(ellipse 58% 58% at 50% 34%, #000 6%, transparent 70%);
          mask-image: radial-gradient(ellipse 58% 58% at 50% 34%, #000 6%, transparent 70%);
}
/* THE RHYTHM IS THIS PAGE'S, NOT /pricing's, and the difference is what
   stands above the headline. There, the headline is the first thing in the
   band, so 42px of air above it is generous. Here an icon and a mono label
   come first, so the same 42px puts three things in the space one thing had
   and the whole opening reads squeezed. The gradients are shared; the
   measurements are not, and should not be. */
.cn-in { padding-block: 64px 76px; display: flex; flex-direction: column; align-items: center; }
.cn-icon { width: 64px; height: 64px; border-radius: 16px; }
.cn-k {
  margin-top: 20px;
  font-family: "JetBrains Mono", monospace; font-size: 12px;
  letter-spacing: .08em; text-transform: uppercase; color: var(--ink3);
}
.cn-h1 {
  margin-top: 14px; max-width: 16ch;
  font-size: clamp(46px, 5.1vw, 68px); line-height: 1;
  letter-spacing: -.04em; font-weight: 600;
}
/* the same measure and the same ink as the line under "Plans for every stage" */
.cn-sub { margin-top: 20px; max-width: 640px; font-size: 17.5px; line-height: 1.6; color: var(--dink2); }
.cn-cta { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; margin-top: 32px; }
/* the four facts a person checks before installing anything. One row of
   hairlined columns, which is the shape /pricing uses for its conditions */
.cn-rail {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 28px;
  width: 100%; margin-top: 64px; text-align: left;
}
.cn-rail > div { border-top: 1px solid var(--line); padding-top: 18px; }
.cn-rail-k {
  font-family: "JetBrains Mono", monospace; font-size: 11.5px;
  letter-spacing: .07em; text-transform: uppercase; color: var(--ink3);
}
.cn-rail-v { margin-top: 9px; font-size: 14.5px; line-height: 1.5; color: var(--ink2); }
/* ── 2 · the flow ────────────────────────────────────────────────────── */
/* nvf, appm and dmg are already in landing.css — the same figure runs in the
   front page's "where a run lands" band. Only its frame is set here. */
/* the flow is still inside the opening band, so it supplies its own room
   rather than inheriting the hero's bottom padding as its top */
.cn-flow { padding-block: 40px 104px; }
.cn-flow-h { font-size: 26px; font-weight: 600; letter-spacing: -.03em; text-align: center; }
.cn-flow-p {
  margin: 12px auto 0; max-width: 620px; text-align: center;
  font-size: 16px; line-height: 1.6; color: var(--ink2);
}
.cn-flow .nvf { margin-top: 48px; }
/* ── 3 · what it costs ───────────────────────────────────────────────── */
/* ONE FIGURE: the product in the middle, every place a run can go leaning
   into it. There is no frame around any of it — a header strip, a row of
   refusals and a running total have each been cut since, and what is left
   lines up on the section's own gutter rather than on a panel's. */
/* one label per side of the hub: the right column is the half that is not
   here yet, so a pill on each of its chips said the same word twice */
.acost-heads {
  display: flex; align-items: baseline; justify-content: space-between; gap: 16px;
}
/* the eyebrow that names each band, in the same mono the page labels with */
.acost-k {
  margin: 0;
  font-family: "JetBrains Mono", monospace; font-size: 10.5px; font-weight: 500;
  letter-spacing: .09em; text-transform: uppercase; color: var(--ink3);
}
.afan-tx { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.afan-tx b { font-size: 14.5px; font-weight: 600; letter-spacing: -.02em; }
.afan-tx em { font-style: normal; font-size: 12.5px; color: var(--ink3); line-height: 1.45; }
.afan-m { width: 18px; height: 18px; fill: var(--ink); flex: none; }
.afan-m.is-stroke {
  fill: none; stroke: currentColor; color: var(--ink);
  stroke-width: 1.3; stroke-linecap: round; stroke-linejoin: round;
}
/* THE HUB. The figure's one claim is that everything converges on a single
   connection, so the figure is drawn that way: the product in the middle,
   every place a run can go leaning into it, and nothing else on the band.
   The two sides are not categories — they are what works today on the left
   and what does not yet on the right, which is what the wires say. */
.ahub {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(56px, 92px) auto minmax(56px, 92px) minmax(0, 1fr);
  align-items: stretch;
  margin-top: 16px; min-height: 322px;
  /* the gravity well, so the centre reads as downhill from both sides */
  background: radial-gradient(44% 64% at 50% 50%, rgba(169, 180, 255, .1), transparent 72%);
}
/* equal rows, so a chip's centre is a known fraction of the column height
   and the wire can be drawn to meet it without measuring anything */
.ahub-col { display: grid; grid-template-rows: repeat(3, 1fr); min-width: 0; }
.ahub-col.is-soon { grid-template-rows: repeat(2, 1fr); }
.ahub-chip {
  align-self: center; position: relative; z-index: 1;
  display: flex; align-items: center; gap: 12px; min-width: 0;
  border: 1px solid var(--line); border-radius: 12px; background: #12141C;
  padding: 11px 14px;
}
/* not here yet: no ground under it, and the outline says provisional */
.ahub-chip.is-soon { background: transparent; border-style: dashed; border-color: #262B37; }
.ahub-chip.is-soon .afan-tx b { color: var(--ink2); font-weight: 500; }
.ahub-chip.is-soon .afan-m { color: var(--ink3); opacity: .85; }
.ahub-mk { flex: none; display: grid; place-items: center; width: 20px; height: 20px; }
/* THE WIRES. viewBox units, stretched to whatever the band turns out to be —
   the stroke stays a hairline through it because of non-scaling-stroke, and
   the gradient does the arrowhead's job: it brightens toward the core, so
   the direction is legible without five arrowheads crowding one tile. */
.ahub-wire { width: 100%; height: 100%; overflow: visible; }
.ahub-wire path { fill: none; stroke-width: 1; vector-effect: non-scaling-stroke; }
.ahub-wire.is-soon path { stroke-dasharray: 3 5; }
.ahub-core {
  position: relative; z-index: 1; align-self: center;
  display: flex; flex-direction: column; align-items: center; text-align: center;
  padding: 28px 34px 26px; border-radius: 18px;
  border: 1px solid #3B4253;
  background:
    radial-gradient(86% 70% at 50% 0%, rgba(169, 180, 255, .1), transparent 70%),
    linear-gradient(180deg, #1A2029 0%, #12151D 100%);
  box-shadow: 0 22px 48px -26px rgb(0 0 0 / 85%);
}
/* a second ring just outside the tile, so the wires appear to land on
   something rather than to stop short of it */
.ahub-ring {
  position: absolute; inset: -7px; border-radius: 24px;
  border: 1px solid rgba(169, 180, 255, .09); pointer-events: none;
}
.afan-core-icon { width: 58px; height: 58px; border-radius: 15px; flex: none; }
.afan-core-tx { display: flex; flex-direction: column; align-items: center; gap: 4px; margin-top: 14px; }
.afan-core-tx b { font-size: 17px; font-weight: 600; letter-spacing: -.025em; }
.afan-core-tx em { font-style: normal; font-size: 12.5px; line-height: 1.4; color: var(--ink3); }
/* the consequence, under the whole figure rather than inside the tile: it
   is about every app, and the tile is about one connection. It closes the
   act now, so it keeps no room for a band beneath it. */
.ahub-s {
  margin: 22px auto 0; max-width: 62ch;
  text-align: center; font-size: 14px; line-height: 1.6; color: var(--ink2);
  text-wrap: pretty;
}
/* ── 6 · what stays here ─────────────────────────────────────────────── */
/* THE WHOLE SENTENCE, LEFT TO RIGHT: your machine, Soba, the app. Earlier
   versions drew the left half as a region and left the right half as loose
   labels under a heading, so the arrows crossed a wall and then arrived
   nowhere in particular. Two matching boxes with the app between them says
   what is happening before a word of it is read. */
.amach {
  display: grid; grid-template-columns: auto minmax(150px, 1fr) auto;
  align-items: center;
}

.amach-box {
  position: relative;
  border: 1px solid var(--line); border-radius: 18px;
  padding: 34px 34px 28px;
  background: linear-gradient(180deg, rgba(255, 255, 255, .02), transparent 62%);
}
/* a legend sitting in the border, so each side reads as a named place
   rather than as one more card on the page */
.amach-tag {
  position: absolute; top: -8px; left: 26px; padding: 0 11px;
  background: var(--night);
  font-family: "JetBrains Mono", monospace; font-size: 11px; font-weight: 500;
  letter-spacing: .09em; text-transform: uppercase; color: var(--dink);
}
.amach-foot {
  margin: 26px 0 0; padding-top: 18px; border-top: 1px solid var(--line2);
  font-family: "JetBrains Mono", monospace; font-size: 11px; font-weight: 500;
  letter-spacing: .09em; text-transform: uppercase; color: var(--ink3);
}

.amach-list {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-direction: column; gap: 17px;
}
.amach-list li {
  display: flex; align-items: center; gap: 15px;
  font-size: 16px; line-height: 1.5; color: var(--ink2);
}
/* this one is metadata riding along, not a third thing the app is handed */
.amach-list li.is-minor { color: var(--ink3); }

/* each thing is drawn as what it is — the two accounts by their own marks, a
   key, the agent as the program it runs as, a file; and on the far side an
   answer, a meter, a tag. The column is a fixed width so the pair on the
   first row does not push its line out of step with the rest. */
.amach-ico {
  flex: none; width: 42px; display: flex; align-items: center; gap: 7px;
  color: var(--ink3);
}
.amach-ico svg {
  display: block; width: 17px; height: 17px;
  fill: none; stroke: currentColor; stroke-width: 1.35;
  stroke-linecap: round; stroke-linejoin: round;
}
/* the brand marks are solid shapes, not line drawings, so they carry no
   stroke and sit a size down to weigh the same as the drawn ones */
.amach-ico .amach-b { width: 15px; height: 15px; fill: currentColor; stroke: none; }

/* THE CROSSING. Three wires between the two places, and the app sitting on
   them: everything that leaves one side passes through it to reach the
   other. The wires are borders rather than paths, so they span whatever gap
   the two boxes leave and the heads never distort with it. */
.amach-cross { position: relative; align-self: center; padding-block: 26px; }
.amach-wires {
  position: absolute; inset: 0; display: flex; flex-direction: column;
  justify-content: center; gap: 26px;
}
.amach-wires i {
  position: relative; display: block; height: 0;
  border-top: 1px solid var(--periwinkle);
}
.amach-wires i.is-minor { border-top-style: dashed; border-top-color: var(--ink3); }
/* the head, drawn as two borders turned on their corner */
.amach-wires i::before {
  content: ""; position: absolute; right: 0; top: 50%;
  width: 6px; height: 6px; border-top: 1px solid currentColor;
  border-right: 1px solid currentColor; color: inherit;
  transform: translateY(-50%) rotate(45deg);
}
.amach-wires i { color: var(--periwinkle); }
.amach-wires i.is-minor { color: var(--ink3); }

.amach-node {
  position: relative; z-index: 2; display: block;
  width: 86px; height: 86px; margin: 0 auto;
  filter: drop-shadow(0 14px 26px rgb(0 0 0 / 45%));
}
.amach-node svg { display: block; width: 100%; height: 100%; border-radius: 25px; }
/* the same ring the hub core wears, so the two figures are plainly one set */
.amach-node::before {
  content: ""; position: absolute; inset: -10px; border-radius: 35px;
  border: 1px solid rgba(169, 180, 255, .14);
}
/* the mark is the product, so it is named: an unlabelled tile in the middle
   of a diagram is a decoration */
.amach-nodek {
  /* on its own ground, so the wires it sits among are cut rather than run
     through the word — the same trick the box legends use on their border */
  position: relative; z-index: 2; width: max-content; margin: 16px auto 0;
  padding: 0 10px; background: var(--night); text-align: center;
  font-family: "JetBrains Mono", monospace; font-size: 11px; font-weight: 500;
  letter-spacing: .09em; text-transform: uppercase; color: var(--dink);
}

/* THE RUN. A pulse leaves the machine, passes behind the app icon and
   arrives on the far side — the one thing a still figure of this cannot
   say, which is that the crossing happens and happens repeatedly. */
.amach-wires i::after {
  content: ""; position: absolute; top: 50%; left: 0;
  width: 7px; height: 7px; margin-top: -3.5px; border-radius: 50%;
  background: currentColor; opacity: 0;
}
.amach-wires i:not(.is-minor)::after { box-shadow: 0 0 12px 2px rgba(169, 180, 255, .5); }
.amach-wires i.is-minor::after { width: 5px; height: 5px; margin-top: -2.5px; }
/* it starts when the figure does, on the class the reveal script adds */
.amach.is-up .amach-wires i::after {
  animation: amach-run 3.4s cubic-bezier(.42, 0, .58, 1) infinite;
}
.amach.is-up .amach-wires i:nth-child(2)::after { animation-delay: .5s; }
.amach.is-up .amach-wires i:nth-child(3)::after { animation-delay: 1s; }
@keyframes amach-run-y {
  0%        { top: 0; opacity: 0; }
  6%        { opacity: 1; }
  60%       { opacity: 1; }
  62%, 100% { top: 100%; opacity: 0; }
}
@keyframes amach-run {
  0%        { left: 0; opacity: 0; }
  6%        { opacity: 1; }
  60%       { opacity: 1; }
  62%, 100% { left: 100%; opacity: 0; }
}

/* ── the close ───────────────────────────────────────────────────────── */
.close-k {
  margin: 20px auto 0; max-width: 620px;
  font-size: 16.5px; line-height: 1.6; color: var(--dink2);
}

/* ── 7 · getting it ──────────────────────────────────────────────────── */
/* THE DISPENSING HALF. Everything from here down was the old /download, the
   page that handed you the file, and
   the only structural change is that its hero is a section like any other:
   the page already has an opening, 2,000 pixels above this one. */
.dl-main { text-align: center; }
.dl-h1 { font-size: 40px; line-height: 1.05; letter-spacing: -.035em; font-weight: 600; }
.dl-lede { margin: 14px auto 0; font-size: 16.5px; line-height: 1.6; color: var(--ink2); }
.dl-primary { margin: 28px 0 10px; }
.dl-meta { margin: 0; font-size: 13px; color: var(--ink3); }
/* ── the builds ──────────────────────────────────────────────────────── */
/* A row of text links under a pill button is a call to action. A download
   section looks like one when you can see the platforms — so each build is
   a tile with its own mark and its architecture under it, and the whole row
   says "there is a file here for you" before a word is read. */
.dl-os {
  display: grid; grid-template-columns: repeat(5, 1fr); gap: 12px;
  max-width: 820px; margin: 46px auto 0;
}
.dl-os a {
  position: relative;
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  padding: 20px 10px 17px; border-radius: 14px;
  border: 1px solid var(--line); background: var(--night2);
  transition: border-color .2s ease, background .2s ease, transform .2s ease;
}
.dl-os a:hover { border-color: #3B4253; background: #161A24; transform: translateY(-2px); }
.dl-os a:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }
.dl-os-m { display: grid; place-items: center; width: 26px; height: 26px; margin-bottom: 9px; color: var(--ink2); }
.dl-os-m svg { width: 100%; height: 100%; display: block; }
.dl-os a:hover .dl-os-m { color: var(--ink); }
.dl-os b { font-size: 14.5px; font-weight: 600; letter-spacing: -.02em; }
.dl-os em { font-style: normal; font-size: 12px; color: var(--ink3); }
/* ── the one the browser thinks you are on ───────────────────────────── */
/* The button above says "Download for macOS", and until this existed the
   five tiles under it quietly contradicted that: nothing on screen agreed
   with the claim. The badge is the agreement. It is added by script, so a
   page with none of it marks nothing rather than marking the wrong one.
   The tile stays an ordinary link: a guess the visitor can overrule by
   clicking any of its neighbours is the only kind worth making. */
.dl-os a.is-you {
  border-color: #3E4556; background: #171B26;
  box-shadow: 0 0 0 1px rgba(255, 255, 255, .045);
}
.dl-os a.is-you .dl-os-m { color: var(--ink); }
.dl-you {
  position: absolute; top: -9px; left: 50%; transform: translateX(-50%);
  padding: 3px 9px 2px; border-radius: 999px; white-space: nowrap;
  /* the same white as the button above it, because it is the same claim */
  background: var(--ink); color: var(--night);
  font-style: normal; font-size: 9.5px; font-weight: 600;
  letter-spacing: .06em; text-transform: uppercase;
}
/* the one requirement that would actually stop a download working, as a
   sentence rather than as a four-column table under a button */
.dl-needs {
  margin: 34px auto 0; max-width: 56ch;
  font-size: 14px; line-height: 1.6; color: var(--ink3); text-wrap: pretty;
}
.dl-needs b { font-weight: 600; color: var(--ink2); }
/* ── 2b · the app itself ─────────────────────────────────────────────── */
/* THE ONE WHITE BAND ON A NIGHT PAGE, and the reason is the thing in it: a
   Mac app, drawn in its own light palette. Floating a white window on the
   night ground made it look cut out; standing the whole band in daylight
   makes it look photographed.

   night.css inverted the ink scale on :root. This puts the original back
   for one section — the same tokens landing.css ships, which is what the
   window, the headings and the hairlines all read from — so nothing inside
   needs a colour of its own. Loaded after night.css, so .shot wins over its
   `.w { background: var(--night) }
` on order rather than on weight. */
/* This heading is the long one. .act-h's 18ch measure suits "Your bill
   does not change"; here it breaks after the fourth word and leaves half
   the band empty, so this one is given the column. */
.shot .act-h { max-width: none; }
.shot .act-p { max-width: 720px; }
.shot {
  background: #fff;
  --ink:  #0B0C10;
  --ink2: #52566A;
  --ink3: #8A8F9C;
  --line:  #E4E5EA;
  --line2: #EFF0F3;
  --soft:  #F4F4F6;
  color: var(--ink);
}
/* the stepped shoulder is drawn again where there is an edge to cut: night
   above the band and night below it. Everywhere else on this page both
   sides are night, which is why night.css hides it. */
.shot-step { display: block; }
/* The legend is stretched to the window and its rows pushed apart, so the
   first rule sits on the window's top edge and the last on its bottom one.
   Centred, the list floated against it; aligned, the two read as one
   object. */
/* ── 2b · the app itself ─────────────────────────────────────────────── */
/* THE SCREENSHOT IS THE SECTION, not a 400px sidekick to a column of
   prose. Linear labels a shot in two to four words above a full-bleed
   image; Raycast uses category pills and lets each preview speak. Both
   make the picture the biggest thing in the band, and the earlier version
   of this had it exactly backwards.
   ────────────────────────────────────────────────────────────────────── */

/* the tabs: the canvas's own segmented control, at the top where a label
   for a picture belongs */
.shott {
  display: flex; justify-content: center; flex-wrap: wrap; gap: 4px;
  margin: 46px auto 0; padding: 4px;
  background: var(--soft); border-radius: 999px; width: max-content; max-width: 100%;
}
.shotp {
  position: relative; border: 0; background: none; cursor: pointer;
  font: 600 14.5px/1 "Schibsted Grotesk", system-ui, sans-serif;
  letter-spacing: -.015em; color: var(--ink2);
  padding: 11px 18px 12px; border-radius: 999px;
  transition: color .2s ease, background .25s ease, box-shadow .25s ease;
}
.shotp:hover { color: var(--ink); }
.shotp.is-on {
  color: var(--ink); background: #fff;
  box-shadow: 0 0 0 .5px rgba(16, 17, 25, .09), 0 1px 2px rgba(16, 17, 25, .09),
              0 6px 14px -8px rgba(16, 17, 25, .22);
}
.shotp:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }
/* while it is advancing on its own, the chosen tab says how long is left.
   Apple's own galleries do this; a carousel that moves with no warning is
   the thing people hate about carousels. */
/* ONE SEGMENT PER BEAT, not one rule per tab. Connect holds two states — the
   panel, then Soba open beside it — and a single bar under it filled, reset
   and filled again without the tab ever changing, which reads as a stall
   rather than as progress. The script builds the segments from the beat
   list and sets each one's duration, so the rule can never disagree with
   what it is counting. */
.shotp-rule {
  position: absolute; left: 16px; right: 16px; bottom: 6px;
  display: flex; gap: 3px; height: 2px; pointer-events: none;
}
.shotp-rule i {
  flex: 1; border-radius: 2px; background: var(--ink);
  transform: scaleX(0); transform-origin: left; opacity: 0;
}
/* a beat already spent stays filled: the tab reads as 1 of 2, then 2 of 2 */
.shotg.is-auto .shotp.is-on .shotp-rule i.is-done { opacity: .35; transform: scaleX(1); }
.shotg.is-auto .shotp.is-on .shotp-rule i.is-timing {
  opacity: .35; animation: shot-fill var(--seg, 3100ms) linear forwards;
}
@keyframes shot-fill { from { transform: scaleX(0) }
to { transform: scaleX(1) }
}
/* ── the screen ──────────────────────────────────────────────────────── */
/* ONE BROWSER, FILLING THE SCREEN. This is what the screenshots are: the
   app a person already uses, its own page greyed out behind a scrim, Soba
   Connect as a dialog inside it, and the Soba app answering beside it. Not
   a wallpaper with two cards floating on it — the page behind is the whole
   reason the dialog is there. */
.scene {
  position: relative; margin-top: 40px; border-radius: 18px; overflow: hidden;
  /* WIDER THAN THE COLUMN. A dialog centred in the screen and a window
     beside it will not both fit in 1152 without one covering the other, and
     the dialog belongs in the middle — that is where the app it belongs to
     puts it. So the screen breaks out of the text column, up to 1320 and
     never past the window's own edge. The arithmetic: half the column,
     minus half the screen, is how far left to start. */
  width: min(1320px, calc(100vw - 48px));
  margin-inline: calc(50% - min(660px, 50vw - 24px));
  height: 740px;
  background: #fff;
  box-shadow: 0 0 0 1px rgba(16, 17, 25, .1),
              0 2px 6px -2px rgba(16, 17, 25, .12),
              0 56px 100px -40px rgba(16, 17, 25, .38),
              0 20px 40px -20px rgba(16, 17, 25, .16);
}
/* the customer's page. It is read as shape, not as words — the scrim over
   it is doing what a scrim does — so it is written at the size it would be
   and left to blur. */
.page {
  position: absolute; inset: 0; overflow: hidden;
  font-family: "Schibsted Grotesk", system-ui, sans-serif; color: #16171D;
}
/* ── the app's own bar ───────────────────────────────────────────────── */
/* THREE GROUPS, NOT SIX WORDS. It used to be a brand, four sections and an
   account set in one undifferentiated row at one size, which is what made
   it read as a list rather than as the top of an application. Now the mark
   has a tile and a rule after it, the sections are tabs with the open one
   on the same glass as everything else in here, and the account is an
   avatar at the far end. Nothing was added to the page; the same words are
   simply told apart. */
.page-nav {
  display: flex; align-items: center; gap: 2px;
  height: 56px; padding: 0 22px 0 26px;
  border-bottom: 1px solid #ECECF0;
  font-size: 13.5px; color: #74757E;
}
.page-brand {
  display: flex; align-items: center; gap: 10px;
  padding-right: 20px; margin-right: 18px; border-right: 1px solid #ECECF0;
  font-size: 14px; font-weight: 600; color: #16171D; letter-spacing: -.02em;
}
.page-logo {
  flex: none; display: grid; place-items: center; width: 26px; height: 26px;
  border-radius: 8px; color: #16171D;
}
.page-logo svg { width: 15px; height: 15px; }
/* the sections. The open one is the same pane as every other raised thing
   on this page, which is cheaper to read than a weight change and does not
   shift the row by a pixel when it moves. */
.page-tabs { display: flex; align-items: center; gap: 2px; }
.page-tabs b, .page-tabs i {
  display: block; padding: 6px 11px; border-radius: 8px;
  font-style: normal; font-weight: 400;
}
.page-tabs b { font-weight: 500; color: #16171D; }
.page-user {
  display: flex; align-items: center; gap: 9px;
  margin-left: auto; font-size: 13px; color: #8A8B94;
}
.page-ava {
  flex: none; display: grid; place-items: center; width: 26px; height: 26px;
  border-radius: 50%; font-style: normal;
  font-size: 9.5px; font-weight: 600; letter-spacing: .03em; color: #6E6F79;
}
/* ── the app behind the dialog: a chat, and a model using tools ──────── */
/* GLASS, NOT INK. Every control in here used to be a black rectangle, and
   four of them on one screen read as four buttons shouting at each other
   under a scrim that was already flattening the page. They are the same
   surface now: a pane of near-white over the page, a hairline around it and
   a lit top edge, which is what a raised piece of glass looks like from
   straight on. The weight that distinguishes them is the border, not the
   fill, so nothing on this half of the figure competes with the Soba dialog
   standing in front of it. */
.page-new, .page-send, .page-av, .page-msg.is-you p,
.page-logo, .page-ava, .page-tabs b {
  background: linear-gradient(180deg, rgba(255, 255, 255, .92), rgba(246, 246, 250, .74));
  border: 1px solid #DEDEE6;
  box-shadow: 0 1px 2px rgba(16, 17, 25, .05),
              inset 0 1px 0 rgba(255, 255, 255, .9);
  -webkit-backdrop-filter: blur(10px);
          backdrop-filter: blur(10px);
  color: #16171D;
}
/* A support desk with a thread list, a conversation down the middle and a
   composer under it. The one skeleton primitive, .page-l, carries the half
   of it nobody is meant to read; everything above is written out, because
   the point of the figure is that this is an ordinary agentic app and the
   Soba dialog is a visitor in it. */
.page-body { display: grid; grid-template-columns: 252px 1fr; height: calc(100% - 56px); }
.page-brand { font-weight: 600; color: #16171D; letter-spacing: -.02em; margin-right: 6px; }
.page-side { border-right: 1px solid #ECECF0; padding: 16px 14px; }
.page-new {
  display: block; padding: 9px 0; margin-bottom: 14px; border-radius: 10px;
  text-align: center; font-size: 13px; font-weight: 600;
}
.page-thr {
  display: flex; flex-direction: column; gap: 3px;
  padding: 10px 12px; border-radius: 9px;
}
.page-thr.is-on { background: #F3F3F6; }
.page-thr b { font-size: 13.5px; font-weight: 500; letter-spacing: -.01em; }
.page-thr em { font-style: normal; font-size: 12px; color: #9A9BA4; }
.page-thr.is-on em { color: #6E6F79; }
/* the conversation. Bottom-aligned, the way a chat that has been running a
   while sits: the newest turn against the composer, the older ones pushed
   off the top rather than floating in the middle of an empty column. */
/* IT STOPS BEFORE THE SOBA WINDOW STARTS. The window stands at the top
   right of the screen and the dialog in the middle, so a conversation set
   to the full width of the column puts its own newest turn, the one on the
   right, entirely behind them. Capped here it reads as a chat with two
   sides to it, which is the only thing this half of the figure has to say. */
.page-chat {
  display: flex; flex-direction: column; justify-content: flex-end;
  max-width: 620px; padding: 26px 40px 20px;
}
/* the thread the conversation belongs to. It also fills the top of the
   column, which a chat with three turns in it otherwise leaves empty. */
.page-head { padding-bottom: 16px; border-bottom: 1px solid #ECECF0; }
.page-head b { display: block; font-size: 15px; font-weight: 600; letter-spacing: -.02em; }
.page-head em { font-style: normal; font-size: 12.5px; color: #9A9BA4; }
.page-msgs { display: flex; flex-direction: column; gap: 22px; margin-top: auto; padding-top: 26px; }
.page-msg { display: flex; gap: 12px; }
.page-msg.is-you { align-self: flex-end; max-width: 400px; }
/* the person's turn. Without a dark fill the thing that says whose turn it
   is becomes the shape: pulled to the right, and cornered into the side it
   came from. */
.page-msg.is-you p {
  margin: 0; padding: 11px 15px; border-radius: 14px 14px 4px 14px;
  border-color: #D6D7E0; font-size: 14px; line-height: 1.5;
}
.page-av {
  flex: none; display: grid; place-items: center; width: 28px; height: 28px;
  border-radius: 9px; color: #6E6F79;
  font-size: 10.5px; font-weight: 600; letter-spacing: .03em;
}
.page-bub { min-width: 0; flex: 1; }
.page-bub p { margin: 0 0 12px; font-size: 14px; line-height: 1.55; color: #3A3B44; }
/* the tool calls, shown rather than hidden. An agentic app that does not
   say what it just did is a text box with a spinner. */
.page-tool {
  display: flex; align-items: center; gap: 9px; margin-bottom: 8px;
  padding: 7px 12px; border: 1px solid #ECECF0; border-radius: 8px;
  font-size: 12.5px; color: #6E6F79;
}
.page-tool em { font-style: normal; margin-left: auto; color: #A9AAB3; font-size: 11.5px; }
.page-tdot {
  width: 6px; height: 6px; border-radius: 50%; flex: none;
  background: #fff; box-shadow: inset 0 0 0 1.5px #B9BAC4;
}
.page-tool.is-live { border-color: #D7D7E0; color: #16171D; }
.page-tool.is-live .page-tdot { background: #2F8F5B; box-shadow: none; }
.page-l { display: block; height: 8px; border-radius: 4px; background: #ECECF0; margin-bottom: 10px; }
/* the composer is the one pane the glass sits ON rather than beside: a
   wider, flatter sheet with the send button raised out of it */
.page-comp {
  display: flex; align-items: center; margin-top: 24px;
  padding: 13px 14px 13px 18px; border: 1px solid #E4E4EA; border-radius: 13px;
  background: linear-gradient(180deg, rgba(252, 252, 254, .9), rgba(246, 246, 250, .66));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .9);
}
.page-ph { font-size: 14px; color: #A9AAB3; }
.page-send {
  margin-left: auto; display: grid; place-items: center; width: 30px; height: 30px;
  border-radius: 9px; font-size: 9px; color: #6E6F79;
}
/* ONCE THE PAIRING IS ANSWERED THE PAGE COMES BACK, which is what actually
   happens: the dialog closes and the app the person was using carries on.
   From the third screen on, the scrim and the dialog fade and the window is
   alone on a page that is no longer waiting on it. */
.scene-scrim { transition: opacity .5s cubic-bezier(.3, 0, .2, 1), visibility 0s; }
.scene.is-live .scene-scrim { opacity: 0; visibility: hidden; transition: opacity .5s cubic-bezier(.3,0,.2,1), visibility 0s .5s; }
/* the dialog's own states, stacked in one place and cross-faded the way
   the window beside them is */
.modalf { position: absolute; inset: 0; opacity: 0; visibility: hidden; transition: opacity .4s cubic-bezier(.3,0,.2,1), visibility 0s .4s; }
.modalf.is-on { opacity: 1; visibility: visible; transition: opacity .4s cubic-bezier(.3,0,.2,1) .05s, visibility 0s; }
/* what makes the two windows legible, and what makes the grey in the
   screenshots grey */
.scene-scrim {
  position: absolute; inset: 0; background: rgba(22, 23, 29, .52);
  -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px);
}
/* ── the connect modal, as the customer's own page draws it ──────────── */
/* The web half of the pairing, on the desk beside the app that answers it.
   Behind the popover and a shade softer: it is the thing being answered. */
.modal {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); width: 342px;
  border-radius: 14px; overflow: hidden; background: #fff;
  font-family: "Schibsted Grotesk", system-ui, sans-serif; color: #16171D;
  box-shadow: 0 0 0 .5px rgba(0, 0, 0, .28), 0 1px 3px rgba(0, 0, 0, .2),
              0 34px 64px -26px rgba(0, 0, 0, .5);
}
.modal-bar { display: flex; align-items: center; justify-content: space-between; padding: 13px 15px; color: #9A9BA4; }
.modal-bar b { font-size: 13.5px; font-weight: 600; color: #16171D; letter-spacing: -.015em; }
.modal-body { display: flex; flex-direction: column; align-items: center; padding: 4px 22px 20px; text-align: center; }
/* THE ORBIT. Soba's arc going round whichever provider is being opened —
   the one thing on the dialog that says it is waiting rather than done.
   A dash of the circumference, rotating; the stroke is Soba's, the mark
   underneath is the provider's. */
.modal-art { position: relative; display: grid; place-items: center; width: 86px; height: 86px; }
.modal-mark { color: #16171D; }
.modal-orbit { position: absolute; inset: 0; width: 100%; height: 100%; }
.modal-orbit circle {
  fill: none; stroke: #16171D; stroke-width: 5; stroke-linecap: round;
  /* r=44 → a circumference of about 276, so 62 is a 80° arc */
  stroke-dasharray: 62 214;
  transform-origin: 50% 50%;
  animation: modal-spin 1.15s cubic-bezier(.45, 0, .55, 1) infinite;
}
@keyframes modal-spin { to { transform: rotate(360deg) }
}
.modal-h { margin: 13px 0 0; font-size: 17px; font-weight: 600; letter-spacing: -.028em; }
.modal-s { margin: 6px 0 0; font-size: 12.5px; color: #6E6F79; }
.modal-n { display: flex; align-items: center; gap: 6px; margin: 13px 0 0; font-size: 11.5px; color: #6E6F79; }
.modal-n svg { width: 13px; height: 13px; color: #16171D; flex-shrink: 0; }
.modal-btn {
  display: block; width: 100%; margin-top: 17px; padding: 11px 0; border-radius: 10px;
  font-size: 13px; font-weight: 600; text-align: center; box-shadow: inset 0 0 0 1px #E3E3E8;
}
.modal-s2 { margin-top: 13px; }
.modal-get {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  width: 100%; margin-top: 17px; padding: 9px 9px 9px 13px;
  border-radius: 10px; background: #F5F5F7; font-size: 12px; color: #45464F;
}
.modal-get i {
  font-style: normal; font-size: 12.5px; font-weight: 600; color: #16171D;
  background: #fff; border-radius: 8px; padding: 6px 11px;
  box-shadow: 0 0 0 1px #E3E3E8, 0 1px 2px rgba(0, 0, 0, .06);
}
.modal-foot {
  display: flex; align-items: center; justify-content: center; gap: 7px;
  padding: 12px 0 14px; border-top: 1px solid #EFEFF2; font-size: 11.5px; color: #6E6F79;
}
.modal-foot svg { width: 15px; height: 13px; color: #16171D; }
/* ── the app, as it ships ────────────────────────────────────────────── */
/* Real macOS chrome, because the thing has real macOS chrome: a titled bar
   with traffic lights in it, not the bare panel the canvas drew. */
.shotw { position: absolute; top: 52px; right: 56px; width: 400px; height: 624px; }
/* the frames are stacked in one box and cross-fade: a hard cut between two
   screens of the same app reads as a glitch, not as a choice */
.shotf { position: absolute; inset: 0; }
.js .shotf {
  opacity: 0; visibility: hidden;
  transform: translateY(12px) scale(.986);
  transition: opacity .44s cubic-bezier(.3, 0, .2, 1),
              transform .66s cubic-bezier(.22, .61, .36, 1),
              visibility 0s .44s;
}
.js .shotf.is-on {
  opacity: 1; visibility: visible; transform: none;
  transition: opacity .44s cubic-bezier(.3, 0, .2, 1) .06s,
              transform .66s cubic-bezier(.22, .61, .36, 1) .06s;
}
.app {
  position: absolute; inset: 0; display: flex; flex-direction: column;
  border-radius: 12px; overflow: hidden; background: #F2F2F4;
  font-family: "Schibsted Grotesk", system-ui, sans-serif;
  color: #16171D; font-size: 13px; letter-spacing: -.008em;
  box-shadow: 0 0 0 .5px rgba(0, 0, 0, .35), 0 2px 6px -1px rgba(0, 0, 0, .3),
              0 46px 84px -24px rgba(0, 0, 0, .55), 0 18px 32px -16px rgba(0, 0, 0, .35);
}
.app-bar {
  position: relative; display: flex; align-items: center; flex-shrink: 0;
  height: 38px; padding: 0 13px; background: #F7F7F9;
  border-bottom: 1px solid rgba(16, 17, 25, .08);
}
.app-dots { display: flex; gap: 7px; z-index: 1; }
.app-dots i { width: 11px; height: 11px; border-radius: 50%; background: #DCDCE1; }
.app-dots i:first-child { background: #FF5F57; }
.app-dots i:nth-child(2) { background: #FEBC2E; }
.app-bar b { position: absolute; left: 0; right: 0; text-align: center; font-size: 13.5px; font-weight: 600; letter-spacing: -.015em; }
.app-body { flex: 1; min-height: 0; display: flex; flex-direction: column; padding: 12px 12px 0; }
.app-card { background: #fff; border-radius: 11px; box-shadow: 0 0 0 .5px rgba(16, 17, 25, .09), 0 1px 2px rgba(16, 17, 25, .05); }
.app-row { position: relative; display: flex; align-items: center; gap: 11px; padding: 9px 12px; min-height: 42px; }
.app-row + .app-row::before { content: ""; position: absolute; top: 0; left: 50px; right: 0; height: 1px; background: rgba(16, 17, 25, .07); }
.app-gi { width: 29px; height: 29px; border-radius: 8px; flex-shrink: 0; display: grid; place-items: center; background: #F1F1F4; color: #45464F; }
.app-rt { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.app-rt b { font-size: 13px; font-weight: 600; letter-spacing: -.012em; }
.app-rt em { font-style: normal; font-size: 11.5px; color: #9A9BA4; }
/* ── the six screens, from the v2 canvas ─────────────────────────────── */
/* Board "02 · Pairing" end to end, then "03 · Home". Every screen below is
   the canvas's; the window around them is the one the app ships with. */

/* a screen that is all one thing: Soba facing the app, or a code, or the
   moment it is done */
.app-ask { display: flex; flex-direction: column; padding: 26px 20px 20px; flex: 1; min-height: 0; }
.app-mid { justify-content: center; }
.app-pair { display: flex; align-items: center; justify-content: center; gap: 13px; }
.app-dots { display: flex; gap: 4px; }
.app-dots i { width: 4px; height: 4px; border-radius: 50%; background: #C9C9D1; }
/* once it is connected the line between them fills in */
.app-dots.is-on i { background: #16171D; }
.app-gi-lg { width: 52px; height: 52px; border-radius: 14px; font-size: 21px; }
/* the front page's placeholder app: a dashed tile, because the app in the
   picture is whichever one the reader is building */
.app-gi-ph { background: none; overflow: hidden; }
.app-ph { width: 100%; height: 100%; display: block; border-radius: inherit; }
.app-gi-s { position: relative; background: #141210; color: #fff; }
.app-gi-s svg { width: 26px; height: 22px; }
/* the green light on the Soba tile, once the pairing is answered */
.app-lamp {
  position: absolute; right: -3px; bottom: -3px; width: 15px; height: 15px;
  border-radius: 50%; background: #22A06B; box-shadow: 0 0 0 3px #F2F2F4;
}
.app-solo { margin: 0 auto; }
/* mid-handshake the dots travel; connected, they are simply filled */
.app-dots.is-go i { animation: app-go 1.1s ease-in-out infinite; }
.app-dots.is-go i:nth-child(2) { animation-delay: .16s; }
.app-dots.is-go i:nth-child(3) { animation-delay: .32s; }
@keyframes app-go { 0%, 100% { opacity: .35 }
50% { opacity: 1; background: #16171D }
}
/* ── connect: the AI you already have ────────────────────────────────── */
/* The front page's own first pairing screen. Three ways in that cost
   nothing, then a key as the "or" — in that order, because the order is
   the argument. */
/* WITH THE KEY FIELD GONE the screen is shorter than the window, and a
   column that starts at the top leaves 130px of nothing under "Secured by
   Soba". Centred, the same content reads as a screen that fits. */
.app-tight { justify-content: center; padding-top: 22px; }
/* ── Connect: the choices ────────────────────────────────────────────── */
/* EACH ONE IS ITS OWN ROW. They used to be three rows inside one card,
   divided by hairlines, which is the shape a list of settings takes — and
   it made "Continue with Claude" and "Sign in with ChatGPT" look like two
   of a kind when the whole point is that one runs here and the other runs
   in a browser tab. Separated, with the fine print and a rule between the
   two groups, the screen says which is which before a word is read. */
.app-pick {
  position: relative; display: flex; align-items: center; justify-content: center;
  margin-top: 10px; padding: 12px; min-height: 48px;
  border: 1px solid #E4E4EA; border-radius: 11px; background: #fff;
}
.app-pick b { font-size: 13.5px; font-weight: 600; letter-spacing: -.012em; }
/* the mark sits at the left edge and the label stays in the middle of the
   whole row — see the note on _pick in windows.py */
.app-pg {
  position: absolute; left: 14px; width: 26px; height: 26px;
  display: grid; place-items: center; color: #16171D;
}
.app-pg svg { width: 19px; height: 19px; }
/* the one piece of state on the screen: this machine already has Claude */
.app-rdy {
  position: absolute; right: 12px; font-style: normal;
  padding: 3px 8px 2px; border-radius: 999px;
  background: #EAF6EF; color: #2F8F5B;
  font-size: 10.5px; font-weight: 600; letter-spacing: -.01em;
}
/* .app-or marked where this machine stopped and a browser tab began. Every
   route on this screen runs on the machine now, so there is no border left
   for a rule to draw. */
/* the way in for somebody the three choices above do not fit */
.app-code { margin: 22px 0 0; text-align: center; font-size: 13px; color: #45464F; }
/* the footer is the window's, not the column's: a rule across the whole
   panel with the mark and the one promise under it */
.app-foot {
  display: flex; align-items: center; justify-content: center; gap: 7px;
  margin: 0 -12px; padding: 13px 0 14px; border-top: 1px solid #ECECF0;
  font-size: 11.5px; color: #9A9BA4;
}
.app-foot svg { width: 13px; height: 13px; }
.app-ask h3 {
  margin: 22px 0 0; text-align: center;
  font-size: 18px; font-weight: 600; letter-spacing: -.028em; line-height: 1.28;
  text-wrap: balance;
}
.app-ask > p { margin: 9px 0 0; text-align: center; font-size: 12.5px; line-height: 1.5; color: #7B7C85; }
/* what it gets: three rows, no ticks. A tick would read as a choice that
   has already been made. */
.app-grants { margin-top: 22px; }
.app-grants .app-row { padding: 11px 13px; min-height: 48px; }
.app-grants .app-gi { background: #F4F4F7; color: #5B5C66; }
.app-fine { margin: 14px 2px 0; text-align: center; font-size: 11.5px; line-height: 1.5; color: #9A9BA4; }
.app-fine b { font-weight: 600; color: #45464F; }
/* Authorize leads; the second answer is a real one and stays legible
   rather than greyed into a hint */
.app-btn { display: flex; align-items: center; justify-content: center; gap: 7px; margin-top: 16px; padding: 13px 0; border-radius: 11px; text-align: center; font-size: 14.5px; font-weight: 600; background: #16171D; color: #fff; }
.app-ask .app-btn:first-of-type { margin-top: auto; }
.app-mid .app-btn:first-of-type { margin-top: 26px; }
.app-tight .app-btn:first-of-type { margin-top: 16px; }
.app-btn-g { margin-top: 9px; background: transparent; color: #45464F; box-shadow: inset 0 0 0 1px #E5E5EA; }
/* ── the responsive layer ────────────────────────────────────────────── */
@media (max-width: 1100px) {
  .act-h { font-size: 40px; }
/* the desk loses its margins before the window inside it does */
  .scene { min-height: 0; }
.shotw { right: 48px; }
/* the asking app is the first thing the desk gives up */
  /* the page gives up its margins before the dialog on it does, and the
     thread rail before the conversation does: a chat is still a chat
     without the list beside it. */
  .page-chat { padding-inline: 26px; }
  .page-nav { padding-inline: 18px; }
  .page-body { grid-template-columns: 200px 1fr; }
.modal { width: 318px; }
.shotw { right: 32px; }
.cn-rail { grid-template-columns: 1fr 1fr; gap: 24px 28px; }
/* the wires give up their room before a chip gives up a line: the fan
     still reads at half the sweep, and "already paid" on its own line does
     not */
  .ahub {
    grid-template-columns:
      minmax(0, 1fr) minmax(38px, 54px) auto minmax(38px, 54px) minmax(0, 1fr);
  }
.ahub-core { padding-inline: 22px; }
.ahub-chip { gap: 10px; padding: 11px 12px; }
}
@media (max-width: 900px) {
  /* THE HUB, STACKED. Five chips cannot lean in from the sides of a phone,
     so the fan becomes a column: everything above the core, the core, then
     what is coming. The wires go — a drawn curve in a 56px gutter is a
     smudge — and the chips carry the convergence by sitting under one tile. */
  .ahub {
    grid-template-columns: minmax(0, 1fr);
    min-height: 0; gap: 10px;
    background: radial-gradient(74% 26% at 50% 50%, rgba(169, 180, 255, .075), transparent 72%);
  }
.ahub-wire { display: none; }
.ahub-col { display: flex; flex-direction: column; gap: 10px; }
.ahub-chip { align-self: stretch; }
.ahub-core { align-self: stretch; margin-block: 4px; }
/* the second label was pinned opposite the first, which only pointed at
     the right column while there was a right column; stacked, it belongs
     over the chips it names */
  .acost-heads p + p { display: none; }
.ahub-col.is-soon::before {
    content: "Not here yet"; margin-top: 8px;
    font-family: "JetBrains Mono", monospace; font-size: 10.5px; font-weight: 500;
    letter-spacing: .09em; text-transform: uppercase; color: var(--ink3);
  }
  /* the three places stack, and the wires turn with them */
  .amach { grid-template-columns: minmax(0, 1fr); }
  .amach-cross { padding-block: 0; margin: 26px 0; }
  .amach-wires {
    inset: 0; flex-direction: row; justify-content: center; gap: 26px;
  }
  .amach-wires i {
    flex: none; width: 0; height: auto; align-self: stretch;
    border-top: 0; border-left: 1px solid currentColor;
  }
  .amach-wires i.is-minor { border-left-style: dashed; }
  .amach-wires i::before {
    right: auto; top: auto; left: 50%; bottom: 0;
    transform: translateX(-50%) rotate(135deg);
  }
  .amach-wires i::after { left: 50%; top: 0; margin: 0 0 0 -3.5px; }
  .amach-wires i.is-minor::after { margin-left: -2.5px; }
  .amach.is-up .amach-wires i::after { animation-name: amach-run-y; }
}
/* Below a point the screen stops being a screen: a 392px dialog and a
   400px window will not sit side by side in 900. The page, its scrim and
   the dialog go, and the app stands on the band on its own. */
@media (max-width: 900px) {
  .scene {
    aspect-ratio: auto; background: none; box-shadow: none;
    border-radius: 0; min-height: 0; margin-top: 32px;
  }
.page, .scene-scrim, .modal { display: none; }
/* back to the on-white shadow: the hard one was cut for a scrimmed
     screen, and on a white band it reads as a grey halo */
  .app {
    box-shadow: 0 0 0 1px rgb(10 10 10/7%), 0 2px 4px -2px rgb(10 10 10/8%),
                0 40px 72px -28px rgb(10 10 10/22%), 0 14px 28px -14px rgb(10 10 10/10%);
  }
.shotw { position: relative; top: 0; right: auto; margin-inline: auto; }
.shott { margin-top: 38px; }
}
@media (max-width: 900px) {
  /* five across needs 820; below that they go two rows deep rather than
     shrinking to a strip of unreadable tiles */
  .dl-os { grid-template-columns: repeat(3, 1fr); max-width: 520px; }
}

@media (max-width: 620px) {
  .dl-os { grid-template-columns: repeat(2, 1fr); max-width: 360px; gap: 10px; }
  .inner { padding-inline: 20px; }
.shotp { font-size: 13.5px; padding: 10px 14px 11px; min-height: 44px; }
/* 400px of window between two 20px gutters needs 440; below that it scales
     rather than reflows, because a screenshot that reflows is not a
     screenshot.

     AUTO MARGINS NEVER GO NEGATIVE, which is why this was not centred: the
     box stays 400 wide and `margin-inline: auto` on a box wider than its
     container resolves to zero, so it sat at the left gutter and the scale
     — taken about its own centre — carried the picture off the right edge.
     Measured at 430, 390, 360 and 320 it was the same 52-to-396 every time.
     An explicit half-difference does go negative, and centres it. */
  .shotw {
    transform: scale(.86); transform-origin: top center; height: 537px;
    margin-inline: calc((100% - 400px) / 2);
  }
.dl-h1 { font-size: 30px; }
.section { padding-block: 84px; }
.cn-flow { padding-bottom: 72px; }
/* the clamp's floor is a desktop floor: on a phone the headline comes
     down to where landing.css puts .h1, or it fills the screen */
  .cn-h1 { font-size: 36px; max-width: none; }
.cn-sub { font-size: 16px; }
.cn-in { padding-block: 44px 56px; }
.cn-icon { width: 52px; height: 52px; border-radius: 13px; }
.cn-flow { padding-block: 24px 64px; }
.act-fig { margin-top: 38px; }
.act-h { font-size: 30px; max-width: none; }
.act-p { font-size: 16px; }
/* ── FOUR THINGS STAY FOUR THINGS ──────────────────────────────
     Stacked, the rail was four rows and most of a phone screen — spent
     before the page has made its case. Four across does not fit either:
     "macOS, Windows and Linux, with Android next" is not going into 97px.
     So it scrolls sideways, which keeps all four as four rather than
     shortening them into uselessness.

     IT BLEEDS TO THE SCREEN EDGES. A scroller inset inside the gutter reads
     as a boxed widget and hides that there is more to the right; running to
     the edge, the cut-off fourth card is the affordance. Out by the gutter
     on the margin, back in on the padding, so the first card still lines up
     with everything above it. */
  .cn-rail {
    display: flex; grid-template-columns: none; gap: 0;
    margin-top: 44px; margin-inline: -20px; padding-inline: 20px;
    overflow-x: auto; overscroll-behavior-x: contain;
    scroll-snap-type: x proximity;
    scrollbar-width: none;
  }
  .cn-rail::-webkit-scrollbar { display: none; }
  .cn-rail > div {
    flex: 0 0 auto; width: 58vw; max-width: 230px;
    scroll-snap-align: start;
    padding-top: 13px; padding-right: 22px;
  }
  /* the last card keeps the gutter's worth of room after it, so the strip
     ends where the text above it ends rather than hard against the glass */
  .cn-rail > div:last-child { padding-right: 20px; }
  .cn-rail-v { margin-top: 7px; font-size: 14px; }
}
/* ── arriving ────────────────────────────────────────────────────────── */
/* The class is added by script to things that already look finished, so a
   page with no JavaScript has never hidden any of it. 24px and a breath of
   opacity — enough to feel placed, not enough to be a performance. */
.rise {
  opacity: 0; transform: translateY(24px);
  transition: opacity .7s cubic-bezier(.3, 0, .2, 1),
              transform .9s cubic-bezier(.22, .61, .36, 1);
}
.rise.is-up { opacity: 1; transform: none; }
/* each band's heading, line and figure arrive in that order rather than
   together, which is the difference between a page settling and a page
   flinching */
.act-h.rise { transition-delay: .06s; }
.act-p.rise { transition-delay: .12s; }
.shott.rise { transition-delay: .16s; }
.scene.rise, .act-fig.rise { transition-delay: .22s; }
/* ── motion ──────────────────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important; }
/* nothing may be left mid-arrival */
  .rise { opacity: 1; transform: none; }
.js .shotf.is-on { opacity: 1; visibility: visible; }
}

/* ── SOBA CONNECT, v2 ───────────────────────────────────────────
   The window beside the dialog, redrawn on the v2 design.

   WHAT IT REPLACES asked you to pick an AI, and the app does not do that —
   the picking happens in the panel one beat earlier, and a window asking
   again was the scene telling the same step twice. v2 does not ask
   anything. It reports: Soba opened, here is what it has, it is ready. One
   question answered, which is the whole brief of a status app.

   Drawn rather than screenshotted because the design is not in this repo
   yet; when the app ships it, a real capture replaces this and nothing
   around it has to move. */
.v2-win {
  --v-ink: #16171D; --v-ink2: #5A5B66; --v-muted: #8A8B95;
  --v-line: #E7E7EB; --v-ground: #F2F2F4;
  position: absolute; inset: 0; display: flex; flex-direction: column;
  border-radius: 12px; overflow: hidden; background: var(--v-ground);
  color: var(--v-ink); font-size: 13px; letter-spacing: -.005em;
}
.v2-bar {
  display: flex; align-items: center; justify-content: space-between;
  padding: 10px 12px; background: #FAFAFB; border-bottom: 1px solid var(--v-line);
}
.v2-brand { display: flex; align-items: center; gap: 7px; font-size: 12.5px; font-weight: 600; }
.v2-brand svg { width: 15px; height: 13px; }
.v2-bar-r { display: flex; align-items: center; gap: 9px; }
/* the one control on the bar: serving, or not */
.v2-sw {
  width: 28px; height: 16px; border-radius: 999px; background: var(--v-ink); position: relative;
}
.v2-sw::after {
  content: ""; position: absolute; top: 2px; right: 2px;
  width: 12px; height: 12px; border-radius: 50%; background: #fff;
}
.v2-more { font-style: normal; color: var(--v-muted); font-size: 15px; line-height: 1; }

.v2-body { flex: 1; padding: 12px; display: flex; flex-direction: column; gap: 12px; min-height: 0; }

/* THE LIT HERO: the mark with a light on it, the state in words, and the
   three facts that say what "ready" is made of. */
.v2-hero {
  background: #fff; border-radius: 12px; padding: 18px 14px 14px; text-align: center;
  box-shadow: 0 0 0 1px rgba(16,17,25,.05), 0 1px 2px rgba(16,17,25,.05);
}
.v2-tile {
  position: relative; display: inline-grid; place-items: center;
  width: 54px; height: 54px; border-radius: 15px; background: #141210;
}
.v2-tile svg { width: 26px; height: 22px; }
.v2-lamp {
  position: absolute; right: -3px; bottom: -3px; width: 13px; height: 13px;
  border-radius: 50%; background: #2F8F5B; box-shadow: 0 0 0 2.5px #fff;
}
.v2-state { margin-top: 11px; font-size: 16px; font-weight: 600; letter-spacing: -.02em; }
.v2-facts {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px;
  margin-top: 14px; padding-top: 13px; border-top: 1px solid var(--v-line);
}
.v2-fk { font-size: 9.5px; letter-spacing: .07em; text-transform: uppercase; color: var(--v-muted); }
.v2-fv { margin-top: 3px; font-size: 19px; font-weight: 600; letter-spacing: -.03em; }
.v2-fs { font-size: 11px; color: var(--v-muted); }

/* two chips, because the list below is one of two things */
.v2-chips { display: flex; gap: 6px; }
.v2-chips span {
  padding: 5px 11px; border-radius: 999px; font-size: 12px; font-weight: 500; color: var(--v-ink2);
}
.v2-chips span.is-on { background: #fff; color: var(--v-ink); font-weight: 600;
  box-shadow: 0 0 0 1px rgba(16,17,25,.06), 0 1px 2px rgba(16,17,25,.06); }

.v2-list {
  background: #fff; border-radius: 12px; overflow: hidden;
  box-shadow: 0 0 0 1px rgba(16,17,25,.05), 0 1px 2px rgba(16,17,25,.05);
}
.v2-row {
  display: grid; grid-template-columns: 26px minmax(0,1fr) auto auto;
  align-items: center; gap: 9px; padding: 9px 11px;
  border-top: 1px solid var(--v-line);
}
.v2-row:first-child { border-top: 0; }
.v2-ico { display: grid; place-items: center; width: 22px; height: 22px; border-radius: 6px; background: var(--v-ground); }
.v2-ico svg { width: 13px; height: 13px; }
.v2-tx b { display: block; font-size: 12.5px; font-weight: 600; letter-spacing: -.01em; }
.v2-tx em { display: block; font-style: normal; font-size: 11px; color: var(--v-muted); margin-top: 1px; }
.v2-dot { width: 6px; height: 6px; border-radius: 50%; background: #2F8F5B; }
.v2-go { width: 11px; height: 11px; color: var(--v-muted); }
/* the row that is an action rather than a thing */
.v2-add { color: var(--v-ink2); }
.v2-add .v2-ico { font-size: 14px; color: var(--v-muted); }
.v2-add .v2-tx b { font-weight: 500; }

.v2-nav {
  display: grid; grid-template-columns: repeat(3, 1fr);
  border-top: 1px solid var(--v-line); background: #FAFAFB;
}
.v2-nav span {
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  padding: 8px 0 9px; font-size: 10px; color: var(--v-muted);
}
.v2-nav span svg { width: 16px; height: 16px; }
.v2-nav span.is-on { color: var(--v-ink); }

/* ── the smallest screens ────────────────────────────────────────
   .86 of 400 is 344, and a 320pt phone has 280 between its gutters. One more
   step rather than a formula: scale() cannot read the width it has, and a
   breakpoint that names the number is easier to check than arithmetic that
   hides it. */
@media (max-width: 380px) {
  .shotw { transform: scale(.70); height: 437px; }
  /* A GRID ITEM'S AUTOMATIC MINIMUM IS ITS MIN-CONTENT WIDTH, so the flow's
     single 280px track was being widened to 308 by what sits in it — the
     track is not the constraint until the item is told it may be narrower
     than its contents. min-width: 0 is the whole fix; max-width alone does
     nothing, because the item was never over its own max. */
  .nvf > * { min-width: 0; }
  .nvf-node, .nvf-mid, .nvf-opt, .dmg { max-width: 100%; }
  .nvf-node { padding-inline: 12px; }
  .dmg-title { font-size: 11px; }
}

/* ── prose, on a phone ──────────────────────────────────────────
   NOTHING. This block used to left-align .cn-sub, .cn-flow-p, .ahub-s,
   .dl-lede and .dl-needs on a phone, on the general ground that centred
   prose is slow to read once it is four lines. Every one of them was wrong,
   and for the same reason: they are not loose paragraphs, they sit on a
   centred axis. .cn-in is align-items:center, .cn-flow-h and .dl-main are
   text-align:center. Pulling the sentence off that axis while the icon, the
   headline and the button stay on it does not read as better typography, it
   reads as broken.

   Centring is a property of the composition, not a per-element call to make
   at a breakpoint. The one place it was right is a standalone note under a
   grid — see .tb-f in pricing.css, which is six centred lines on a phone
   and on nobody's axis. */
