/* The web app. Same tokens as the marketing site so the two feel like one
   product, but laid out as a tool rather than a page: fixed height, the
   composer pinned, the transcript the only thing that scrolls.

   Loaded after paper.css, which supplies every colour, radius and face. */

/* HEIGHT, not min-height — and this is the whole of the first bug reported
   from the field: "the menu doesn't track with the conversation, I have to
   scroll to the top to get at it".

   min-height lets this box grow as tall as its content. Once it does, the
   DOCUMENT is what scrolls, .log's overflow-y never engages because .log is
   already as tall as its own content, and the sidebar — an ordinary flex
   child, not a fixed one — slides off the top with everything else. The
   header of this file has claimed "fixed height, the composer pinned, the
   transcript the only thing that scrolls" since it was written; it was never
   true, because none of the three can happen without a definite height here.

   Every descendant that can outgrow its box now carries its own scroll path
   (see .side, .view and .dash below). A fixed height without those is how you
   turn a scrolling page into a clipped one, which is the worse bug. */
.app{height:100dvh;display:flex;flex-direction:column;
  max-width:52rem;margin:0 auto;padding:0 24px}
/* Old browsers, and anything without dvh: keep the previous behaviour rather
   than collapsing to nothing. */
@supports not (height:100dvh){ .app{height:100vh} }

.app-top{display:flex;align-items:center;justify-content:space-between;
  height:64px;gap:16px}
.app-top .brand{display:flex;align-items:center;gap:8px;color:var(--obsidian);
  text-decoration:none;font-size:14px;font-weight:500;letter-spacing:-.01em}
.app-top .brand svg{width:18px;height:18px;color:var(--carbon)}
.app-top-right{display:flex;align-items:center;gap:12px}
.app-top-tail{display:flex;align-items:center;gap:12px}
/* Report a problem — a header control, not a footer link.
   Deliberately quiet (ghost, mono, small) so it is not competing with Build,
   and deliberately ALWAYS there: the moment somebody needs it is the moment
   something else on the screen is lying to them, and a support route they have
   to go looking for is one they will not find while it is happening. */
.reportbtn{font-family:var(--sans);font-weight:600;font-size:11px;letter-spacing:.05em;cursor:pointer;
  color:var(--stone);background:transparent;border:0;box-shadow:var(--ring-ghost);
  padding:5px 10px;border-radius:var(--radius-pill);white-space:nowrap}
.reportbtn:hover{color:var(--obsidian);box-shadow:var(--ring-card-hover)}
.reportbtn:focus-visible{outline:2px solid var(--obsidian);outline-offset:2px}
.report-text{width:100%;box-sizing:border-box;font:inherit;font-size:16px;
  padding:10px 12px;margin:6px 0 8px;border:0;border-radius:12px;
  background:var(--white);box-shadow:var(--ring-card);color:var(--obsidian);resize:vertical}
.report-text:focus-visible{outline:2px solid var(--obsidian);outline-offset:1px}
.mstate{font-family:var(--sans);font-weight:600;font-size:11px;letter-spacing:.071em;
  text-transform:uppercase;color:var(--stone)}
.mstate::before{content:'';display:inline-block;width:6px;height:6px;
  border-radius:var(--radius-pill);background:var(--live);
  margin-right:6px;vertical-align:middle}
/* The last few minutes, and then the truth. A sandbox dying under a live
   conversation used to show nothing at all. */
.mstate.is-low{color:var(--obsidian)}
.mstate.is-low::before{background:var(--obsidian)}
.mstate.is-gone{color:var(--alarm)}
.mstate.is-gone::before{background:var(--alarm)}
/* The model readout is a button now — same clothes, plus a hand cursor and
   the hover the other quiet controls up here already have. */
.mstate-btn{appearance:none;background:none;border:0;padding:4px 6px;margin:-4px -6px;
  border-radius:6px;cursor:pointer;font-family:var(--sans);font-weight:600}
.mstate-btn:hover{background:var(--hairline);color:var(--obsidian)}
.mstate-btn:focus-visible{outline:2px solid var(--obsidian);outline-offset:2px}

/* `safe center` and an overflow, together, because the pair is what makes
   centring survive a fixed height. Plain `justify-content:center` on a box
   that cannot grow overflows EQUALLY in both directions, so the top of a tall
   view goes above the box's start edge where no scrollbar can ever reach it —
   the headline disappears and nothing indicates it is there. `safe` says
   "fall back to flex-start rather than centre when it does not fit", which is
   the behaviour anybody actually wants. */
.view{flex:1;min-height:0;overflow-y:auto;
  display:flex;flex-direction:column;align-items:center;
  justify-content:safe center;text-align:center;gap:14px;padding:32px 0}
.view[hidden]{display:none}

.app-h1{font-family:var(--display);font-weight:700;
  font-size:clamp(2rem,6vw,3rem);line-height:1;letter-spacing:-.06em;
  margin:0;color:var(--obsidian)}
.app-sub{color:var(--charcoal);font-size:1rem;line-height:1.5;margin:0;max-width:32rem}
/* Fine print is PROSE — sentences a person is meant to read, used 87 places.
   It was 11px mono, which is how "a key saved here reaches every machine"
   ended up looking like terminal output. Mono stays for values and figures;
   sentences get the reading face. */
.app-fine{font-family:var(--sans);color:var(--stone);font-size:12.5px;
  line-height:1.55;margin:0}
.gbtn-wrap{margin:8px 0;min-height:44px;display:flex;justify-content:center}
/* Sign in with Apple. Sized and worded to sit beside Google's pill without
   either looking like the afterthought; black-on-white is Apple's own spec and
   is left the same in both themes on purpose — their guidelines allow black or
   white, and a recoloured Apple button reads as a phishing page. */
.abtn{display:none;align-items:center;justify-content:center;gap:8px;width:260px;height:40px;
  margin:0 auto 8px;border-radius:999px;background:#000;color:#fff;text-decoration:none;
  font-family:var(--sans);font-size:14px;font-weight:600;letter-spacing:.01em;cursor:pointer}
.abtn:not([hidden]){display:flex}
.abtn:hover{background:#1a1a1a}
.abtn:focus-visible{outline:2px solid var(--obsidian);outline-offset:2px}
.abtn-i{width:16px;height:16px;display:block;position:relative;top:-1px}

.spinner{width:24px;height:24px;border-radius:var(--radius-pill);
  border:2px solid var(--hairline);border-top-color:var(--obsidian);
  animation:spin .8s linear infinite}
@keyframes spin{to{transform:rotate(360deg)}}
@media(prefers-reduced-motion:reduce){.spinner{animation-duration:2.4s}}

/* ── chat ─────────────────────────────────────────────────────────────────
   The transcript is the product doing its job, so it is set like the CLI
   panels on the marketing page: white surface, hairline ring, nothing that
   floats. What you said takes the brand fill; what the agent said stays on
   paper, under the mark, as its sender.

   This replaces an obsidian/paper pair that carried no colour at all. The
   reason to spend --brand here is that the ads sell the product AS this
   exchange — a blue line you sent, a white line that came back — and a person
   who clicks one should land on the thing they were shown. It is also the job
   paper.css reserves --brand for: identity, never a state. Whose voice a line
   is is not a status, so this does not put the palette back where it was when
   one hue meant Memory, live, done and cheapest at once. */
/* The one view that must NOT scroll as a whole. The composer is pinned to the
   bottom of it, so giving this box a scrollbar would carry the composer off
   the screen — .log is the scroller here and always has been. */
.view.chat{justify-content:flex-end;align-items:stretch;text-align:left;
  gap:12px;padding-bottom:20px;min-height:0;overflow:hidden;
  /* A four-pixel gutter each side, taken back with a matching negative margin
     so nothing moves. The composer is as wide as this box, its ring is drawn
     OUTSIDE its box, and overflow:hidden clipped both straight sides — the
     focus ring read as a top and bottom edge with rounded corners floating on
     nothing, which Richy saw on his phone. Same fix reaches every full-width
     card in the log (.approve, the welcome cards), whose 1px ring lost its
     left and right edge the same way, more quietly. */
  padding-left:4px;padding-right:4px;margin-left:-4px;margin-right:-4px}
.log{flex:1;overflow-y:auto;display:flex;flex-direction:column;gap:12px;
  padding:8px 0;min-height:0}

/* 20px, with the corner nearest its own sender squared off to 7px. That one
   asymmetric corner is what makes a rounded rectangle read as a spoken line
   rather than a card, and it costs nothing. */
.msg{max-width:min(90%,38rem);padding:13px 17px;border-radius:20px;
  font-family:var(--sans);font-size:14px;line-height:1.5;white-space:pre-wrap;
  overflow-wrap:anywhere}
.msg.me{align-self:flex-end;background:var(--brand);color:var(--brand-on);
  border-bottom-right-radius:7px}
.msg.ai{position:relative;align-self:flex-start;background:var(--white);
  color:var(--obsidian);box-shadow:var(--ring-card);
  border-bottom-left-radius:7px;margin-left:40px}

/* The mark sits outside the bubble as the sender. Purely decorative — the
   run's position already tells a screen reader who is speaking — so it is a
   ::before rather than an <img> the message builders would each have to add. */
.msg.ai::before{content:"";position:absolute;left:-40px;bottom:0;
  width:30px;height:30px;border-radius:8px;
  background:var(--sunk) url("/img/mark-48.png") center/cover no-repeat;
  box-shadow:var(--ring-ghost)}
/* Only the last bubble of a consecutive run is signed, the way every messaging
   app does it — a column of identical faces is noise. Browsers without :has
   simply show all of them, which is the harmless direction to degrade in. */
.msg.ai:has(+ .msg.ai)::before{display:none}
.msg.thinking{color:var(--stone)}
.msg.err{color:var(--alarm);box-shadow:0 0 0 1px var(--alarm-halo)}

/* ── your brain ─────────────────────────────────────────────────────────────
   Files as chips, contents as a plain pre — deliberately un-flashy, because
   "it is really just markdown you own" is the entire message. */
/* One row: what it is, when it last moved, and the three things you can do to
   it. This block was the top of the Memory screen and most of its height; the
   notes are the screen now and this is a line under them. */
.brain-row{display:flex;align-items:center;flex-wrap:wrap;gap:10px;
  background:var(--white);box-shadow:var(--ring-card);border-radius:var(--radius);
  padding:12px 14px;margin:0 0 12px}
.brain-facts{flex:1 1 16rem;min-width:0;margin:0}
.brain-offer{align-items:flex-start}
.brain-offer p{margin:0;font-size:13px;color:var(--charcoal);flex:1 1 18rem}
.brain-head{margin:0 0 10px}
.brain-acts{display:flex;gap:8px;flex-wrap:wrap;margin:0 0 14px}

/* ── what Amelia remembers ──────────────────────────────────────────────────
   One card per project, one for the notes about the PERSON. A note is a
   sentence with a way to delete it, and nothing else: this screen is read by
   somebody checking what is known about them, and every extra control on the
   row is a reason to stop reading. */
.mem-grp{background:var(--white);box-shadow:var(--ring-card);border-radius:var(--radius);
  padding:14px 16px;margin:0 0 12px}
.mem-h{display:flex;align-items:baseline;justify-content:space-between;gap:10px;
  margin:0 0 10px;font-size:15px;font-weight:600;color:var(--obsidian)}
.mem-n{font-family:var(--sans);font-size:11px;font-weight:600;letter-spacing:.06em;
  text-transform:uppercase;color:var(--smoke);flex:0 0 auto}
.mem-note{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;
  padding:8px 0;border-top:1px solid var(--hairline)}
.mem-t{margin:0;font-size:13.5px;line-height:1.55;color:var(--charcoal);min-width:0}
.mem-x{flex:0 0 auto;border:0;background:none;cursor:pointer;padding:2px 6px;border-radius:4px;
  font-family:var(--sans);font-size:11px;font-weight:600;letter-spacing:.05em;
  text-transform:uppercase;color:var(--smoke)}
.mem-x:hover{background:var(--sunk);color:var(--alarm)}
.mem-x:disabled{opacity:.4;cursor:default}
.mem-empty{background:var(--white);box-shadow:var(--ring-card);border-radius:var(--radius);
  padding:22px;max-width:44rem}
.brain-note{color:var(--live)}
.brain-files{display:flex;flex-wrap:wrap;gap:6px;margin:0 0 10px}
.brain-file{font-family:var(--mono);font-size:11.5px}
.brain-file.is-on{background:var(--obsidian);color:var(--paper)}
.brain-view{background:var(--white);box-shadow:var(--ring-card);border-radius:var(--radius);
  padding:14px 16px;font-family:var(--sans);font-weight:600;font-size:12.5px;line-height:1.6;
  color:var(--charcoal);white-space:pre-wrap;overflow-wrap:break-word;
  max-height:26rem;overflow-y:auto;margin:0 0 22px}
.brain-import{max-width:34rem;margin:0 0 20px}
.brain-import[hidden]{display:none}
.brain-import .cap{margin:0 0 6px}
.brain-import-row{display:flex;gap:8px;margin-top:8px}
.brain-import-row input{flex:1;min-width:0;background:var(--white);border:0;
  box-shadow:var(--ring-ghost);border-radius:var(--radius);padding:9px 12px;
  font:13px var(--sans);color:var(--obsidian)}
.brain-import-row input:focus{outline:none;box-shadow:0 0 0 1px var(--obsidian)}

.acct-secrets{max-width:34rem}
.acct-secrets .cap{margin:0 0 6px}
.acct-secrets-list{list-style:none;margin:8px 0 12px;padding:0;
  display:flex;flex-direction:column;gap:6px}
.acct-secrets-list li{display:flex;align-items:center;justify-content:space-between;
  gap:10px;background:var(--white);box-shadow:var(--ring-ghost);
  border-radius:var(--radius);padding:7px 8px 7px 12px}
.acct-secrets-form{display:flex;gap:8px;flex-wrap:wrap}
.acct-secrets-form input{flex:1 1 140px;min-width:0;background:var(--white);border:0;
  box-shadow:var(--ring-ghost);border-radius:var(--radius);padding:9px 12px;
  font:13px var(--mono);color:var(--obsidian)}
.acct-secrets-form input:focus{outline:none;box-shadow:0 0 0 1px var(--obsidian)}
.acct-secrets-err{flex-basis:100%;color:var(--alarm)}

/* The own-model-key card left the account page on 2026-09-06; its rules went
   with it rather than sitting here styling an element that does not exist. */

/* ── the gear the conversation is in ────────────────────────────────────────
   Plan and Build as a visible pair above the composer. Plan is the free
   clarifying conversation; Build runs the machine and is priced. The active
   one carries the brand fill so the state is legible from across the room. */
/* The row WRAPS. Three pills spend ~215px, so the sentence had 309px for the
   322px it needs in the embedded editor (a 560px sheet on the customer's own
   site) and 139px on a phone — and the half that got cut is the half that says
   what happens: "…you approve before anything runs". Measured at 560/390/320;
   only a 1440px desktop ever showed it whole. Wrapping keeps one line wherever
   there is room and gives the hint its own full-width line where there is not,
   which is the only way it is READ rather than merely present. */
/* The other conversations in this project. A single scrolling row rather than
   a wrapping block: it sits directly above the composer, and a strip that grows
   to three lines pushes the box somebody is trying to type in off the screen on
   a phone. Scrolls sideways instead, which is the one place that is right —
   the page itself must never do it. */
/* The one line that answers "where is my site". Compressed: dot, sentence,
   button, nothing else. Never hidden by reading mode — it is the point. */
.sitebar{display:flex;align-items:center;gap:8px;margin:0 0 6px;min-width:0;
  font-size:12.5px;color:var(--charcoal)}
.sitebar-dot{width:7px;height:7px;border-radius:50%;background:var(--live);flex:0 0 auto}
.sitebar-dot.is-off{background:var(--smoke)}
.sitebar-dot.is-wait{background:var(--brand)}
.sitebar-t{flex:1 1 auto;min-width:0;overflow-wrap:anywhere}
.sitebar-t strong{font-weight:600;color:var(--obsidian)}
.sitebar-t a{color:var(--live);text-decoration:none}
.sitebar-t a:hover{text-decoration:underline;text-underline-offset:2px}
.sitebar-go{flex:0 0 auto;font:inherit;font-size:12px;font-weight:600;line-height:1.2;
  color:var(--brand-on);background:var(--brand);border:0;border-radius:9999px;
  padding:5px 12px;cursor:pointer;text-decoration:none;white-space:nowrap}
.sitebar-go:hover{filter:brightness(1.08);text-decoration:none}
.built-gone{margin:10px 0 0;font-size:12.5px;color:var(--stone)}
.built-gone-look{font:inherit;font-size:inherit;color:var(--obsidian);background:none;border:0;
  padding:0;cursor:pointer;text-decoration:underline;text-underline-offset:2px}
.convbar{display:flex;align-items:center;gap:6px;margin:0 0 6px;min-width:0;
  overflow-x:auto;overflow-y:hidden;scrollbar-width:none;-webkit-overflow-scrolling:touch}
.convbar::-webkit-scrollbar{display:none}
.convbar-l{flex:0 0 auto;font-family:var(--sans);font-weight:600;font-size:10px;letter-spacing:.06em;
  text-transform:uppercase;color:var(--stone);padding-right:2px}
.convbar-b{flex:0 0 auto;max-width:62vw;font:inherit;font-size:12px;line-height:1.2;
  cursor:pointer;color:var(--obsidian);background:var(--white);border:0;
  box-shadow:var(--ring-ghost);padding:5px 10px;border-radius:var(--radius-pill);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.convbar-b:hover{box-shadow:var(--ring-card-hover)}
.convbar-w{color:var(--stone);font-family:var(--mono);font-size:10px}
/* The way out of the conversation on screen: right end of the mode row,
   brand ink on the ordinary mode-button shape. */
.modebar-b.modebar-new{margin-left:auto;color:var(--brand);font-weight:600}
/* Narrow: the button stays ON the pill line, right after Overnight, and the
   hint takes the next line whole — two lines, not three. It used to wrap to a
   third line of its own under the hint (Richy, 2026-09-03: "stop adding too
   much padding everywhere and separating, we want it compressed"). */
@media(max-width:640px){
  .modebar-b.modebar-new{margin-left:0}
  .modebar-hint{order:3;flex:1 1 100%}
}
.modebar{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin:0 0 8px;min-width:0}
/* The overnight sheet's model and price. */
.sheet-select{display:block;width:100%;box-sizing:border-box;font:inherit;font-size:14px;
  padding:9px 12px;margin:2px 0 8px;border:0;border-radius:10px;
  background:var(--white);box-shadow:var(--ring-card);color:var(--obsidian)}
.night-est{font-size:13.5px;line-height:1.45;color:var(--charcoal);margin:0 0 10px}
.night-est b{color:var(--obsidian);font-weight:600}
/* Under the select, in the smaller voice: this is the machine's opinion,
   not the price. The price is the line below it. */
.night-rec{margin:6px 0 10px;color:var(--stone)}
.night-rec[hidden]{display:none}
.night-top{margin:0 0 12px}
.night-top[hidden]{display:none}
/* A disabled Queue button that still looks live is a button people press and
   then decide the product is broken. It is off while there is not enough
   left, and it has to read as off. */
#nightGo[disabled]{opacity:.45;cursor:not-allowed}
/* Quick credit on the account page. */
.credit-row{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin:6px 0 4px}
.credit-row .btn.sm{padding:.4rem .7rem}
.credit-any{display:inline-flex;align-items:center;gap:4px}
.credit-any input{width:5.5rem;font:inherit;font-size:14px;padding:.4rem .5rem;border:0;
  border-radius:8px;background:var(--white);box-shadow:var(--ring-card);color:var(--obsidian)}
.acct-amt{font:inherit;font-size:12.5px;padding:2px 6px;border:0;border-radius:6px;
  background:var(--white);box-shadow:var(--ring-ghost);color:var(--obsidian);margin-left:4px}
.modebar-b{flex:0 0 auto;font-family:var(--sans);font-weight:600;font-size:11px;letter-spacing:.05em;
  text-transform:uppercase;cursor:pointer;color:var(--stone);
  background:var(--white);border:0;box-shadow:var(--ring-ghost);
  padding:5px 12px;border-radius:var(--radius-pill)}
.modebar-b.is-on{background:var(--brand);color:var(--brand-on);box-shadow:none}
.modebar-b:focus-visible{outline:2px solid var(--obsidian);outline-offset:2px}
/* flex-basis:auto is the load-bearing part: the browser measures the sentence
   at its max-content width, so it drops to its own line as a WHOLE rather than
   being squeezed. Once there it may wrap to two lines, which is why nothing
   clips it any more. No margin-left either — the row's 6px gap already
   separates it from the last pill, and a margin would indent it 4px past PLAN
   on the line it now drops to. */
.modebar-hint{flex:1 1 auto;font-family:var(--sans);font-weight:600;font-size:11px;color:var(--stone);
  letter-spacing:.02em;min-width:0;line-height:1.45}
/* Smaller, never GONE: hiding this under 560px left a first-timer's phone
   showing two unexplained mono pills — PLAN | BUILD — above the composer,
   with "free — I ask first" nowhere. */
@media(max-width:560px){.modebar-hint{font-size:10px}}

/* The plan-approval card: the moment Plan hands over to Build, made explicit.
   Nothing runs until the button is pressed. */
.approve{display:flex;flex-wrap:wrap;align-items:center;gap:10px;
  background:var(--white);box-shadow:var(--ring-card);border-radius:var(--radius);
  padding:14px 16px;margin:4px 0}
.approve-t{font-size:14px;color:var(--charcoal);flex:1 1 220px;margin:0}
.approve .btn{white-space:nowrap}
/* Where the plan is going to live — said, not asked. */
.approve-proj{flex:1 1 100%;margin:0;font-size:13px;color:var(--charcoal)}
.approve-proj strong{font-weight:600;color:var(--obsidian)}
.approve-addr{font-family:var(--mono);font-size:11px;color:var(--stone)}
.approve-alt{font:inherit;font-size:12px;color:var(--stone);background:none;border:0;
  padding:0;cursor:pointer;text-decoration:underline;text-underline-offset:2px}
.approve-alt:hover{color:var(--obsidian)}

/* Build categories on the fresh screen — what CAN it make, as tabs. */
.starters-cats{display:flex;flex-wrap:wrap;gap:6px;margin:0 0 10px}
.starters-cats .cat{font-family:var(--sans);font-weight:600;font-size:11px;letter-spacing:.05em;
  text-transform:uppercase;cursor:pointer;color:var(--stone);
  background:none;border:0;box-shadow:var(--ring-ghost);
  padding:5px 11px;border-radius:var(--radius-pill)}
.starters-cats .cat.is-on{background:var(--obsidian);color:var(--white);box-shadow:none}
.starters-cats .cat:focus-visible{outline:2px solid var(--obsidian);outline-offset:2px}

.composer{display:flex;gap:8px;align-items:flex-end;
  background:var(--white);border:0;box-shadow:var(--ring-card);
  border-radius:var(--radius);padding:8px 8px 8px 16px;
  transition:box-shadow .15s ease}
.composer:focus-within{box-shadow:0 0 0 1px var(--obsidian),0 0 0 3px var(--focus-halo)}
.composer textarea{flex:1;resize:none;background:none;border:0;color:var(--obsidian);
  font-family:var(--sans);font-size:1rem;line-height:1.5;max-height:9rem;
  padding:.5rem 0;min-width:0}
.composer textarea:focus{outline:none}
.composer textarea::placeholder{color:var(--smoke)}

/* .send is defined in paper.css — obsidian square, 6px radius, white glyph. */

/* Same guard as paper.css, and it belongs in both: a page that loads only one
   of them still needs it. */
[hidden]{display:none !important}

/* Each boot line fades in so the change reads as a beat rather than a flicker. */
.fade-in{animation:fadeUp .45s ease both}
@keyframes fadeUp{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}
@media(prefers-reduced-motion:reduce){.fade-in{animation:none}}
#bootStep{min-height:1.6em}

/* ── live progress ────────────────────────────────────────────────────────
   A long turn behind one unchanging word is indistinguishable from a hang.
   The agent knows which file it is writing; this shows it, with a clock and
   the running cost, so waiting feels like watching rather than wondering.

   The pulsing dot is compute, so it is obsidian — the same rule that makes
   Memory green everywhere else on the property. */
.msg.work{background:var(--white);box-shadow:var(--ring-card);min-width:15rem}
.work-row{display:flex;align-items:center;gap:8px}
.work-dot{width:7px;height:7px;border-radius:var(--radius-pill);background:var(--obsidian);
  flex:0 0 auto;animation:work-pulse 1.25s ease-in-out infinite}
@keyframes work-pulse{0%,100%{opacity:.35;transform:scale(.82)}50%{opacity:1;transform:scale(1)}}
.work-label{color:var(--obsidian);font-size:14px}

/* The whole header is the control. A button rather than a div so it takes
   keyboard focus and announces expanded/collapsed without any extra wiring. */
.work-head{display:flex;align-items:center;justify-content:space-between;gap:10px;
  width:100%;background:none;border:0;padding:0;cursor:pointer;text-align:left;
  font:inherit;color:inherit}
.work-head:focus-visible{outline:2px solid var(--obsidian);outline-offset:3px;
  border-radius:var(--radius)}
.work-caret{font-family:var(--mono);font-size:11px;color:var(--stone);
  transition:transform .18s ease;flex:0 0 auto}
.work.is-open .work-caret{transform:rotate(90deg)}

/* 11px grey-on-grey was reported as unreadable, and it is the one line that
   answers "is this still going and what is it costing me". 12.5px at --slate,
   with the elapsed time carrying the weight since that is what gets watched. */
.work-meta{display:flex;align-items:center;flex-wrap:wrap;gap:6px;margin-top:8px;
  font-family:var(--mono);font-size:12.5px;letter-spacing:.02em;color:var(--slate);
  font-variant-numeric:tabular-nums}
.work-time{color:var(--charcoal);font-weight:600}
.work-hint{color:var(--stone)}
/* Past twenty seconds the elapsed time stops being a detail. */
.work.is-slow .work-time{color:var(--obsidian)}
.work-sep{color:var(--ash)}

/* The receipt that stays with the answer. */
.msg-meta{margin-top:10px;padding-top:8px;border-top:1px solid var(--hairline);
  font-family:var(--mono);font-size:11px;letter-spacing:.02em;color:var(--stone);
  font-variant-numeric:tabular-nums}

@media(prefers-reduced-motion:reduce){.work-dot{animation:none;opacity:.8}}

/* ── what it built ────────────────────────────────────────────────────────
   An answer saying "open index.html" is useless on its own: the file is on a
   machine the person cannot reach. This is the link and the file list that
   make the work real. */
.built{margin-top:12px;padding-top:12px;border-top:1px solid var(--hairline)}
/* Opening what was built is the primary action here, and in this system a
   primary action is obsidian on a 6px rectangle — green is reserved for
   Memory. */
.built-open{display:inline-block;font-weight:400;font-size:14px;
  text-decoration:none;padding:.5rem .9rem;border-radius:var(--radius);
  background:var(--obsidian);color:var(--white)}
.built-open:hover{background:var(--charcoal);text-decoration:none}
/* The second door, beside Open site: the panel rather than a new tab. Quieter
   than the primary, because leaving for the real thing is still the first
   thing most people want from a published site. */
.built-here{background:transparent;color:var(--obsidian);
  box-shadow:inset 0 0 0 1px var(--edge)}
.built-here:hover{background:var(--sunk);color:var(--obsidian)}
.built-files{display:flex;flex-direction:column;gap:2px;margin-top:10px}
.built-file{font-family:var(--mono);font-size:11px;letter-spacing:.02em;
  color:var(--stone);text-decoration:none;white-space:pre}
.built-file:hover{color:var(--obsidian);text-decoration:underline}

/* ── allowance ────────────────────────────────────────────────────────────
   Visible from the first turn, not discovered by hitting the ceiling. Shares
   the machine-state treatment but drops the status dot — it is a quantity,
   not a state. */
.mstate.alw::before{display:none}
.mstate.alw{color:var(--stone)}

/* ── out of allowance ─────────────────────────────────────────────────── */
.up-actions{display:flex;gap:12px;flex-wrap:wrap;justify-content:center;margin-top:8px}
#viewUpgrade .cap{margin:0}
/* Sits inside the composer once the trial is spent, so the next thing to do is
   where the next thing to type used to be. */
.out-cta{flex:0 0 auto;white-space:nowrap}

/* ── the inline preview ───────────────────────────────────────────────────
   A link that says "open what it built" still asks someone to leave, decide
   the new tab is the thing they wanted, and come back. Showing it in place
   answers "did it work?" before the question gets asked. Small on purpose —
   this is the glance; the full-screen previewer is the real look. */
.built-preview{position:relative;margin-top:12px;border-radius:var(--radius);
  overflow:hidden;box-shadow:var(--ring-ghost);background:var(--white);
  height:260px;cursor:pointer}
.built-preview iframe{width:200%;height:200%;border:0;display:block;
  transform:scale(.5);transform-origin:0 0;pointer-events:none;background:var(--white)}
/* The whole card is the button. A preview you can only open from a small link
   beside it is a preview people scroll past. */
.built-preview::after{content:'Open preview';position:absolute;inset:auto 0 0 0;
  padding:8px 12px;font-family:var(--mono);font-size:11px;letter-spacing:.071em;
  text-transform:uppercase;color:var(--white);background:var(--scrim-strong);
  opacity:0;transition:opacity .15s ease}
.built-preview:hover::after,.built-preview:focus-visible::after{opacity:1}
.built-preview:focus-visible{outline:2px solid var(--obsidian);outline-offset:2px}
@media(hover:none){.built-preview::after{opacity:1}}

/* ── publish ──────────────────────────────────────────────────────────────
   Preview and publish sit together: looking at it and putting it online are
   the same moment's decision. Publish is the quieter of the two — most people
   want another turn before they commit to a URL. */
.built-actions{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.built-deploy{flex:0 0 auto}

/* The payoff line. Above everything else in the block, because once a thing is
   live that is the fact the person came for. */
.built-live{margin-bottom:12px;font-family:var(--mono);font-size:12px;
  letter-spacing:.02em;line-height:1.6}
.built-live-lead{color:var(--stone)}
.built-live a{color:var(--live);font-weight:500}
.built-live a:hover{text-decoration:underline;text-underline-offset:3px}

/* ── your copy, and how to run it ─────────────────────────────────────────
   The preview above is ours and dies with the machine. This is the one that
   does not, with the command to pull it down — "your code is yours" as
   something you can act on rather than a claim on a marketing page. */
.built-own{margin-top:12px;padding-top:12px;border-top:1px solid var(--hairline)}
/* The copy control lives in this row, not floated over the commands — an
   absolutely positioned button sat on top of the `git clone` line, which is
   the one line people actually need to read. */
.built-own-lead{display:flex;align-items:center;gap:8px;flex-wrap:wrap;
  font-size:13px;line-height:1.5;color:var(--stone)}
.built-own-lead a{color:var(--obsidian);font-weight:500}
.built-own-kept{flex:1 1 auto;min-width:0}
.built-cmds{margin:8px 0 0;background:var(--paper);box-shadow:var(--ring-ghost);
  border-radius:var(--radius);padding:12px;overflow-x:auto;white-space:pre;
  font-family:var(--mono);font-size:11px;line-height:1.8;color:var(--obsidian)}
.built-copy{flex:0 0 auto}

/* Said plainly when a browser cannot show the thing — a React app that was
   never built, an Expo project, a script. Better than an empty white frame. */
.built-note{font-family:var(--sans);font-weight:600;font-size:11px;letter-spacing:.02em;
  line-height:1.6;color:var(--stone);margin-top:10px}

/* ── the full-screen previewer ────────────────────────────────────────────
   Lives outside the transcript so appending a message never reloads the frame,
   which would throw away scroll position and any state the built page holds. */
.previewer{position:fixed;inset:0;z-index:60;background:var(--paper);
  display:flex;flex-direction:column}
.pv-bar{display:flex;align-items:center;gap:12px;height:56px;flex:0 0 56px;
  padding:0 16px;border-bottom:1px solid var(--hairline);background:var(--paper)}
.pv-title{font-family:var(--sans);font-weight:600;font-size:11px;letter-spacing:.071em;
  text-transform:uppercase;color:var(--stone);white-space:nowrap;
  overflow:hidden;text-overflow:ellipsis;min-width:0}
.pv-widths{display:flex;gap:2px;margin-left:auto;padding:2px;
  border-radius:var(--radius);box-shadow:var(--ring-ghost);background:var(--white)}
.pv-w{font-family:var(--sans);font-weight:600;font-size:11px;letter-spacing:.05em;
  text-transform:uppercase;border:0;background:none;color:var(--stone);
  padding:5px 10px;border-radius:4px;cursor:pointer}
.pv-w:hover{color:var(--obsidian)}
.pv-w.is-on{background:var(--obsidian);color:var(--white)}
.pv-w:focus-visible{outline:2px solid var(--obsidian);outline-offset:1px}
/* One row that scrolls, not a bar that wraps. Wrapping kept every control
   visible and took three lines of a phone screen to do it — a fifth of the
   height, spent on chrome, in a panel whose entire purpose is the thing behind
   it. The narrow rules are further down, with the rest of the phone layout. */
.pv-actions{display:flex;align-items:center;gap:8px;flex-wrap:nowrap}
.pv-ico .pv-ico-g{display:none}

/* var(--hairline) behind the frame so a white page reads as a sheet of paper on a
   surface rather than dissolving into the app around it. */
.pv-stage{flex:1;min-height:0;background:var(--hairline);
  padding:16px;overflow:auto;text-align:center;-webkit-overflow-scrolling:touch}
/* Vertical centring is done in JS, by margin, in pvApplySize — not by flex,
   grid or a vertical-align strut. All three of those centre by shrinking the
   available box, so the moment the content is BIGGER than it — which is the
   state zooming in exists to reach — the top of the page becomes unscrollable
   and unreachable. A margin that is only ever positive cannot do that. It
   matters because a 1280x800 desktop window shrunk onto a phone is a quarter
   of the screen: pinned to the top it read as a rendering fault with the rest
   of the page missing. */
/* ── A real device viewport, scaled, not a squeezed column ─────────────────
   The frame used to be max-width'd, which narrows the page but leaves the
   viewport whatever the phone's is — so "Desktop" on a phone showed the mobile
   layout at desktop's name. The frame is now laid out AT the device size and
   scaled down to fit, which is what a desktop visitor actually sees.

   inline-block inside a text-align:center scroller, deliberately: auto margins
   and flex/grid centring both clip the leading edge once the content is bigger
   than the box, which is exactly the state zooming in puts this in. */
.pv-canvas{display:inline-block;position:relative;vertical-align:top}
.pv-frame{border:0;background:var(--white);display:block;
  border-radius:var(--radius);box-shadow:0 0 0 1px var(--edge);
  transform-origin:0 0}
/* The gesture sheet. A cross-origin frame keeps every touch that lands on it,
   so a pinch over the page is invisible to us; in Zoom mode this sits over the
   frame and the gesture is ours. Removed entirely in Use mode — a transparent
   layer left over a site is a site whose buttons do nothing. */
.pv-shield{position:absolute;inset:0;background:transparent;cursor:grab;
  touch-action:none}
.pv-shield:active{cursor:grabbing}
.pv-zoom{display:flex;gap:2px;padding:2px;border-radius:var(--radius);
  box-shadow:var(--ring-ghost);background:var(--white)}
.pv-z{font-family:var(--sans);font-weight:600;font-size:11px;letter-spacing:.05em;
  text-transform:uppercase;border:0;background:none;color:var(--stone);
  padding:5px 9px;border-radius:4px;cursor:pointer;min-width:30px}
.pv-z:hover{color:var(--obsidian)}
.pv-z:focus-visible{outline:2px solid var(--obsidian);outline-offset:1px}
.pv-zval{min-width:46px}
.pv-touch{display:flex;gap:2px;padding:2px;border-radius:var(--radius);
  box-shadow:var(--ring-ghost);background:var(--white)}
.pv-t{font-family:var(--sans);font-weight:600;font-size:11px;letter-spacing:.05em;
  text-transform:uppercase;border:0;background:none;color:var(--stone);
  padding:5px 10px;border-radius:4px;cursor:pointer}
.pv-t:hover{color:var(--obsidian)}
.pv-t.is-on{background:var(--obsidian);color:var(--white)}
.pv-t:focus-visible{outline:2px solid var(--obsidian);outline-offset:1px}

/* ── The bar gets out of the way ───────────────────────────────────────────
   On a phone the controls are worth about a fifth of the screen, and the point
   of the panel is the site. It slides away when the stage is scrolled down or
   after a few seconds untouched, and a handle brings it back. */
/* position:absolute, not just a transform. A transformed element keeps its
   layout box, so the first version slid the bar out of sight and left the hole
   it had been sitting in — the site got not one pixel of the space back, which
   is the entire reason the bar hides. Out of the flow, the stage grows into it
   (and pvApplySize re-fits the page to the new height). */
.previewer.pv-bar-away .pv-bar{position:absolute;top:0;left:0;right:0;
  transform:translateY(-102%)}
.pv-bar{transition:transform .18s ease}
.pv-bartab{position:absolute;top:0;left:50%;transform:translateX(-50%);
  z-index:2;border:0;border-radius:0 0 var(--radius) var(--radius);
  background:var(--obsidian);color:var(--white);font-size:13px;line-height:1;
  padding:5px 18px 7px;cursor:pointer;box-shadow:var(--drop-lg)}
.pv-bartab:focus-visible{outline:2px solid var(--brand);outline-offset:2px}

/* ── What is happening, while you carry on looking ─────────────────────── */
.pv-banner{position:absolute;left:50%;transform:translateX(-50%);
  bottom:calc(16px + env(safe-area-inset-bottom));z-index:3;
  display:flex;align-items:center;gap:10px;max-width:min(560px,calc(100% - 24px));
  padding:9px 10px 9px 14px;border-radius:var(--radius-pill);
  background:var(--obsidian);color:var(--white);box-shadow:var(--drop-lg);
  font-family:var(--sans);font-size:13px;line-height:1.35}
.pv-banner-text{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pv-banner-dot{flex:0 0 auto;width:8px;height:8px;border-radius:50%;background:var(--white)}
.pv-banner[data-kind="work"] .pv-banner-dot{background:var(--brand);
  animation:pv-pulse 1.4s ease-in-out infinite}
.pv-banner[data-kind="done"] .pv-banner-dot{background:var(--live)}
.pv-banner[data-kind="fail"] .pv-banner-dot{background:var(--alarm)}
@keyframes pv-pulse{0%,100%{opacity:1}50%{opacity:.35}}
@media(prefers-reduced-motion:reduce){
  .pv-banner[data-kind="work"] .pv-banner-dot{animation:none}
  .pv-bar{transition:none}}
.pv-banner-act{flex:0 0 auto;font:inherit;font-weight:600;cursor:pointer;
  border:1px solid var(--white);background:none;color:var(--white);
  border-radius:999px;padding:4px 12px}
.pv-banner-x{flex:0 0 auto;font:inherit;font-size:17px;line-height:1;cursor:pointer;
  border:0;background:none;color:var(--white);opacity:.7;padding:0 4px}
.pv-banner-x:hover{opacity:1}

/* ── Asking for a change without leaving the page ──────────────────────── */
.pv-dockbtn{position:absolute;right:16px;z-index:4;
  bottom:calc(16px + env(safe-area-inset-bottom));
  display:flex;align-items:center;gap:8px;cursor:pointer;border:0;
  padding:11px 16px;border-radius:var(--radius-pill);
  background:var(--brand);color:var(--brand-on);box-shadow:var(--drop-lg);
  font-family:var(--sans);font-weight:600;font-size:13px}
.pv-dockbtn-i{font-size:15px;line-height:1}
.pv-dockbtn-n{min-width:18px;height:18px;border-radius:999px;font-size:11px;
  display:inline-flex;align-items:center;justify-content:center;
  background:var(--white);color:var(--obsidian)}
.pv-dockbtn:focus-visible{outline:2px solid var(--obsidian);outline-offset:2px}
/* The banner and the button share the bottom edge; the banner steps up. */
.previewer.pv-has-banner .pv-dockbtn{bottom:calc(64px + env(safe-area-inset-bottom))}
.pv-dock{position:absolute;right:16px;z-index:5;
  bottom:calc(16px + env(safe-area-inset-bottom));
  width:min(380px,calc(100% - 32px));max-height:min(60%,460px);
  display:flex;flex-direction:column;overflow:hidden;
  border-radius:var(--radius);background:var(--paper);
  box-shadow:var(--drop-lg),0 0 0 1px var(--edge)}
.pv-dock-bar{display:flex;align-items:center;gap:8px;padding:10px 10px 10px 14px;
  border-bottom:1px solid var(--hairline);flex:0 0 auto}
.pv-dock-t{font-family:var(--sans);font-weight:600;font-size:11px;
  letter-spacing:.071em;text-transform:uppercase;color:var(--stone);
  flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pv-dock-x{font:inherit;font-size:19px;line-height:1;cursor:pointer;border:0;
  background:none;color:var(--stone);padding:0 4px}
.pv-dock-x:hover{color:var(--obsidian)}
.pv-dock-log{flex:1;min-height:64px;overflow:auto;padding:12px 14px;
  display:flex;flex-direction:column;gap:8px}
.pv-dock-line{font-size:13px;line-height:1.5;max-width:100%}
.pv-dock-line.me{align-self:flex-end;background:var(--sunk);color:var(--obsidian);
  padding:7px 11px;border-radius:var(--radius)}
.pv-dock-line.ai{color:var(--charcoal)}
.pv-dock-empty{font-size:13px;line-height:1.5;color:var(--stone);margin:0}
.pv-dock-compose{flex:0 0 auto;display:flex;gap:8px;align-items:flex-end;
  padding:10px;border-top:1px solid var(--hairline)}
.pv-dock-compose textarea{flex:1;resize:none;font:inherit;font-size:14px;
  background:var(--white);color:var(--obsidian);padding:9px 11px;
  border:1px solid var(--hairline);border-radius:var(--radius);
  max-height:120px;min-height:38px}
.pv-dock-compose textarea:focus{outline:2px solid var(--brand);outline-offset:-1px}

/* ── The minimised preview ────────────────────────────────────────────────
   Closing the panel used to blank the frame, which threw away the page it was
   on and everything the built app was holding. This parks it instead. */
.pv-pill{position:fixed;z-index:59;right:16px;
  bottom:calc(96px + env(safe-area-inset-bottom));
  display:flex;align-items:center;gap:9px;cursor:pointer;border:0;
  padding:10px 16px;border-radius:var(--radius-pill);
  background:var(--obsidian);color:var(--white);box-shadow:var(--drop-lg);
  font-family:var(--sans);font-weight:600;font-size:12px;letter-spacing:.02em;
  max-width:min(320px,calc(100vw - 32px))}
.pv-pill-t{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pv-pill-dot{flex:0 0 auto;width:8px;height:8px;border-radius:50%;background:var(--white)}
.pv-pill[data-kind="work"] .pv-pill-dot{background:var(--brand);
  animation:pv-pulse 1.4s ease-in-out infinite}
.pv-pill[data-kind="done"] .pv-pill-dot{background:var(--live)}
.pv-pill[data-kind="fail"] .pv-pill-dot{background:var(--alarm)}
@media(prefers-reduced-motion:reduce){.pv-pill[data-kind="work"] .pv-pill-dot{animation:none}}
.pv-pill:focus-visible{outline:2px solid var(--brand);outline-offset:2px}

@media(max-width:720px){
  /* Two slim rows, not one scrolling one and not three stacked ones.
     A single row that scrolled sideways put Done and Minimise past the right
     edge of a 390px phone, where nothing suggested they were there — the two
     controls somebody needs most, hidden behind a gesture. Two rows fit every
     control on screen at 320px and still cost less height than the old wrap,
     and the whole bar hides itself anyway. */
  .pv-bar{flex-wrap:wrap;height:auto;flex:0 0 auto;padding:6px 10px;gap:6px;
    row-gap:6px;align-items:center}
  .pv-title{display:none}
  .pv-widths{order:1;margin-left:0}
  .pv-zoom{order:2;margin-left:auto}
  /* The row break. An integer order between the two groups, zero height, full
     width — the ordinary way to force a wrap without a wrapper element. */
  .pv-bar::before{content:"";order:3;flex:0 0 100%;height:0}
  .pv-touch{order:4}
  .pv-jump{order:5;max-width:120px;flex:0 1 auto}
  .pv-actions{order:6;margin-left:auto;flex-wrap:nowrap}
  .pv-w,.pv-t,.pv-z{padding:5px 8px}
  .pv-z{min-width:28px}
  .pv-zval{min-width:42px}
  /* Glyph instead of word for the two that carry one. */
  .pv-ico .pv-ico-w{display:none}
  .pv-ico .pv-ico-g{display:inline}
  .pv-ico{padding-left:11px;padding-right:11px}
  .pv-stage{padding:8px}
  .pv-dock{right:8px;left:8px;width:auto;max-height:64%}
  .pv-dockbtn{right:12px}
  /* The label is the reason somebody presses it the first time; on a phone the
     pencil has to carry it alone, and the button is smaller for it. */
  .pv-dockbtn-l{display:none}
  .pv-dockbtn{padding:13px 15px}
  .pv-banner{left:8px;right:8px;transform:none;max-width:none}
  .pv-pill{right:12px;bottom:calc(84px + env(safe-area-inset-bottom))}}

/* ── the first-run screen ─────────────────────────────────────────────────
   An empty transcript used to be a chat bubble at the top, chips at the
   bottom, and seven hundred blank pixels in between. The blank pixels now
   carry the four facts that separate this from a website builder — each a
   real control — and the whole group takes the room the transcript is not
   using. is-fresh comes off the moment a conversation exists. */
.chat.is-fresh .log{flex:0 0 auto;padding:0}
/* Centred with auto margins, not justify-content: a centred flex container
   that overflows clips BOTH ends and the headline becomes unreachable on a
   phone. Auto margins collapse to zero the moment there is no spare room, so
   the same rule centres a desktop and scrolls a phone. */
.chat.is-fresh .starters{flex:1;min-height:0;overflow-y:auto;
  display:flex;flex-direction:column}
.chat.is-fresh .hello{margin-top:auto}
.chat.is-fresh .starters-row{margin-bottom:auto}

.hello{margin:0 0 24px}
.hello-h{font-family:var(--display);font-weight:700;margin:0;
  font-size:clamp(1.6rem,4.5vw,2.4rem);line-height:1.1;letter-spacing:-.045em;
  color:var(--obsidian)}
.hello-sub{margin:10px 0 0;color:var(--charcoal);font-size:14.5px;
  line-height:1.55;max-width:34rem}

.hello-facts{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:20px}
@media(max-width:560px){.hello-facts{grid-template-columns:1fr}}
.fact{display:flex;flex-direction:column;gap:5px;text-align:left;
  background:var(--white);border:0;border-radius:var(--radius);
  box-shadow:var(--ring-card);padding:14px 16px;cursor:pointer;
  font-family:var(--sans);transition:box-shadow .15s ease}
.fact:hover{box-shadow:var(--ring-card-hover)}
.fact:focus-visible{outline:2px solid var(--obsidian);outline-offset:2px}
.fact-h{margin:0;font-size:14px;font-weight:500;color:var(--obsidian)}
.fact-b{margin:0;font-size:12.5px;line-height:1.5;color:var(--stone)}
/* The one place the brand hue appears on this screen: the word that says
   each card goes somewhere. An arrow drawn in CSS rather than typed, so the
   four buttons cannot drift into three glyph styles. */
.fact-go{margin:2px 0 0;font-family:var(--sans);font-weight:600;font-size:11px;
  letter-spacing:.071em;text-transform:uppercase;color:var(--brand-ink)}
.fact-go::after{content:' →'}

/* ── starters ─────────────────────────────────────────────────────────────
   Clicking one fills the box rather than sending, so the wording stays
   editable. */
.starters{margin:0 0 12px}
.starters-q{font-family:var(--sans);font-weight:600;font-size:11px;letter-spacing:.071em;
  text-transform:uppercase;color:var(--stone);margin:0 0 8px}
.starters-row{display:flex;flex-wrap:wrap;gap:8px}
.starters .chip{font-size:13px}

/* ── Template picker: real previews, reversible selection ───────────────── */
.kits{min-width:0;margin:18px 0 0;padding:16px 0 0;box-shadow:inset 0 1px 0 var(--hairline)}
.kits-heading{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:4px 12px}
.kits-q{font-family:var(--sans);font-weight:600;font-size:12px;color:var(--obsidian);margin:0}
.kits-browse,.kits-retry{display:inline-flex;align-items:center;min-height:44px;font-size:12px;color:var(--obsidian)}
.kits-status{font-size:12px;line-height:1.5;color:var(--stone);margin:4px 0 10px}
.kits-search{appearance:none;font-family:var(--sans);width:100%;box-sizing:border-box;min-height:44px;
  padding:10px 12px;margin:0 0 10px;font-size:13px;color:var(--obsidian);background:var(--white);
  border:1px solid var(--hairline);border-radius:var(--radius)}
.kits-search::placeholder{color:var(--stone)}
.kits-count{font-size:11px;line-height:1.5;color:var(--stone);margin:0 0 10px}
.kits-filters{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:12px}
.kits-filter,.kits-retry,.kits-drop{appearance:none;font-family:var(--sans);cursor:pointer;background:var(--white);
  border:1px solid var(--hairline);border-radius:var(--radius-pill);color:var(--obsidian);min-height:44px;padding:8px 12px}
.kits-filter{font-size:11px;font-weight:600}
.kits-filter[aria-pressed="true"]{background:var(--obsidian);border-color:var(--obsidian);color:var(--white)}
.kits-row{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(min(205px,100%),1fr));
  max-height:540px;overflow:auto;overscroll-behavior:contain;padding:3px;scrollbar-width:thin}
.kit-card{min-width:0;display:flex;flex-direction:column;background:var(--white);border:1px solid var(--hairline);
  border-radius:var(--radius);overflow:hidden}
.kit{appearance:none;display:flex;flex-direction:column;flex:1;min-width:0;min-height:44px;width:100%;text-align:left;
  cursor:pointer;font-family:var(--sans);background:var(--white);border:0;padding:0;color:var(--obsidian);
  transition:background .15s ease}
.kit:hover{background:var(--paper)}
.kit[aria-pressed="true"]{box-shadow:inset 0 0 0 2px var(--obsidian)}
.kit-image{display:block;width:100%;height:auto;aspect-ratio:3/2;object-fit:cover;background:var(--paper)}
.kit-image[hidden]{display:none}
.kit-copy{display:flex;flex:1;flex-direction:column;gap:5px;padding:12px;min-width:0;overflow-wrap:anywhere}
.kit-h{margin:0;font-size:15px;font-weight:600;line-height:1.3}
.kit-industry{font-size:10px;line-height:1.4;color:var(--stone)}
.kit-b{margin:0;font-size:12px;line-height:1.45;color:var(--stone);display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}
.kit-pages{font-size:10px;line-height:1.4;color:var(--stone)}
.kit-w{margin:2px 0 0;font-family:var(--sans);font-weight:600;font-size:10px;letter-spacing:.02em;color:var(--smoke)}
.kit-select{display:flex;align-items:center;min-height:32px;margin-top:auto;font-size:11px;font-weight:600}
.kit-links{display:flex;flex-wrap:wrap;gap:4px 16px;border-top:1px solid var(--hairline);padding:0 12px}
.kit-links a{display:inline-flex;align-items:center;min-height:44px;font-size:11px;color:var(--obsidian)}
.kits-armed{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin:0 0 12px;padding:12px;
  border:1px solid var(--hairline);border-radius:var(--radius);font-size:12px;line-height:1.5;color:var(--obsidian)}
.kits-armed[hidden]{display:none}
.kits-drop{font-size:11px;font-weight:600}
.kit:focus-visible,.kits-filter:focus-visible,.kits-drop:focus-visible,.kits-retry:focus-visible,.kit-links a:focus-visible,
.kits-browse:focus-visible{outline:2px solid var(--obsidian);outline-offset:2px}
@media(max-width:480px){.kits-row{grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;max-height:510px}
  .kit-copy{padding:10px}.kit-h{font-size:14px}.kit-b{font-size:11px}.kit-links{gap:4px 12px;padding:0 10px}}
@media(max-width:350px){.kits-row{grid-template-columns:1fr}}

/* The one tip /v1/tips offers, laid between the hello and the starter chips.
   Dressed like the .fact cards so it reads as furniture, not a toast — with
   a dismiss, because a tip you cannot wave away is a nag. */
.tip-card{position:relative;background:var(--white);border-radius:var(--radius);
  box-shadow:var(--ring-card);padding:14px 42px 14px 16px;margin:0 0 14px;text-align:left}
.tip-h{margin:0;font-size:14px;font-weight:500;color:var(--obsidian)}
.tip-b{margin:4px 0 0;font-size:12.5px;line-height:1.5;color:var(--stone)}
.tip-x{position:absolute;top:8px;right:8px;width:26px;height:26px;border:0;
  background:none;border-radius:6px;cursor:pointer;color:var(--stone);
  font-size:16px;line-height:1;font-family:var(--sans)}
.tip-x:hover{color:var(--obsidian)}
.tip-x:focus-visible{outline:2px solid var(--obsidian);outline-offset:2px}
/* The tip's own door — a filled button, because the whole point of carrying
   an action is that nobody has to go find the place the text described. */
.tip-go{margin-top:10px;font:inherit;font-size:12.5px;font-weight:600;
  color:var(--brand-on);background:var(--brand);border:0;border-radius:9999px;
  padding:7px 14px;cursor:pointer}
.tip-go:hover{filter:brightness(1.08)}

/* The trail of finished steps above the current one. Mono, because this is
   machine output — and ✓ in terminal green, which is the confirmation mark
   the rest of the property already uses. */
/* Below the header now, not above it: it is the detail behind the summary,
   and it only exists once you have asked for it. */
.work-trail{display:flex;flex-direction:column;gap:2px;margin-top:10px;
  padding-top:9px;border-top:1px solid var(--hairline)}
.work-trail[hidden]{display:none}
.work-trail:empty{display:none}
.work-done{font-family:var(--sans);font-weight:600;font-size:12px;letter-spacing:.02em;
  line-height:1.65;color:var(--slate)}
.work-done::before{content:'✓ ';color:var(--live)}

/* ── projects ─────────────────────────────────────────────────────────────
   A second project is unreachable without this, and a first one is invisible.
   Sits in the header because it names what everything below it belongs to. */
.projwrap{position:relative}
.projbtn{display:flex;align-items:center;gap:6px;font-family:var(--sans);font-weight:600;
  font-size:11px;letter-spacing:.071em;text-transform:uppercase;
  color:var(--obsidian);background:var(--white);border:0;cursor:pointer;
  box-shadow:var(--ring-ghost);border-radius:var(--radius);padding:5px 9px;
  max-width:14rem}
.projbtn:hover{box-shadow:0 0 0 1px var(--ash)}
.projbtn:focus-visible{outline:2px solid var(--obsidian);outline-offset:2px}
#projName,#chatSwitchName{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.projcar{color:var(--stone);flex:0 0 auto}
/* The chat switcher is the project switcher for the other kind of
   conversation: same corner, same button, one of the two on screen at a time.
   A chat's name is a sentence somebody typed rather than a slug, so it is set
   in sentence case and given more room before the ellipsis takes over.
   "+ New chat" reads first here — starting one is the commonest reason the
   menu is open at all — so the rule that puts a divider ABOVE .projnew is
   flipped for this list. */
.chatwrap .projbtn{text-transform:none;letter-spacing:0;font-size:12.5px;max-width:18rem}
.chatwrap .projnew{border-top:0;border-bottom:1px solid var(--hairline);
  margin:0 0 4px;padding-top:8px;padding-bottom:8px}
/* Drops down from the button's left edge. The project menu hangs from the
   right because it sits at the right end of the header; this one is at the
   left end, where right-anchoring throws it back across the rail. */
.chatwrap .projmenu{left:0;right:auto;min-width:17rem}

.projmenu{position:absolute;top:calc(100% + 6px);right:0;z-index:30;
  min-width:15rem;max-height:60vh;overflow-y:auto;background:var(--white);
  border-radius:var(--radius);box-shadow:var(--ring-card);padding:4px}
.projmenu[hidden]{display:none}
.projitem{display:flex;flex-direction:column;gap:2px;width:100%;text-align:left;
  border:0;background:none;cursor:pointer;padding:8px 10px;border-radius:4px;
  font-family:var(--sans);font-size:13px;color:var(--obsidian)}
.projitem:hover{background:var(--paper)}
.projitem.is-on{background:var(--paper);font-weight:500}
.projitem small{font-family:var(--mono);font-size:10px;letter-spacing:.02em;
  color:var(--stone)}
.projnew{border-top:1px solid var(--hairline);margin-top:4px;padding-top:8px;
  color:var(--obsidian)}

/* The header's way into restore points — see #histWrap below for the panel
   itself. A plain link rather than a second .projbtn: it is a secondary
   action next to the primary switcher, not another button competing with it. */
.projhist{font-family:var(--mono);font-size:11px;letter-spacing:.05em;
  color:var(--stone);background:none;border:0;cursor:pointer;padding:5px 4px;
  text-decoration:underline;text-underline-offset:2px}
.projhist:hover{color:var(--obsidian)}
.projhist:focus-visible{outline:2px solid var(--obsidian);outline-offset:2px}

/* ── Project history ──────────────────────────────────────────────────────
   Restore points, in the sheet behind #histWrap and the "History" control on
   a project card. .data-t (above) is the closest sibling in weight — this
   reuses its rhythm rather than inventing a new row shape. */
.hist-row{border:1px solid var(--hairline);border-radius:10px;padding:11px 14px;
  margin-bottom:8px}
.hist-top{display:flex;align-items:center;justify-content:space-between;gap:10px}
.hist-when{font-family:var(--mono);font-size:12.5px;color:var(--charcoal)}
.hist-live{flex:0 0 auto;background:var(--live);color:var(--white);
  border-radius:var(--radius-pill,999px);padding:.1rem .55rem;
  font-size:.68rem;font-weight:700;letter-spacing:.03em;text-transform:uppercase}
.hist-sum{margin:6px 0 2px;font-size:13.5px;color:var(--obsidian);line-height:1.4}
.hist-meta{margin:0 0 8px;font-size:12.5px;color:var(--stone)}
.hist-note{margin:8px 0 0}
.hist-success{margin-bottom:10px;padding-bottom:10px;border-bottom:1px solid var(--hairline);
  color:var(--charcoal)}

/* ── what next ────────────────────────────────────────────────────────────
   A blank composer after a successful build is a small cliff. These come from
   what is actually in the project, so they stay worth reading. */
.built-next{display:flex;flex-wrap:wrap;gap:8px;margin-top:12px;
  padding-top:12px;border-top:1px solid var(--hairline)}
.built-next .chip{font-size:12px;text-align:left}
.built-gh{flex:0 0 auto}

/* ── plans ────────────────────────────────────────────────────────────────
   Rendered from what Stripe actually sells. Two or three of them, so they are
   buttons rather than a table — the decision is which one, not a comparison. */
.plans{display:flex;gap:12px;flex-wrap:wrap;justify-content:center;margin:8px 0}
.plan{display:flex;flex-direction:column;gap:4px;align-items:flex-start;
  min-width:11rem;padding:16px;cursor:pointer;border:0;background:var(--white);
  box-shadow:var(--ring-card);border-radius:var(--radius);text-align:left;
  font-family:var(--sans);transition:box-shadow .15s ease}
.plan:hover{box-shadow:0 0 0 1px var(--obsidian),0 0 0 3px var(--focus-halo)}
.plan:focus-visible{outline:2px solid var(--obsidian);outline-offset:2px}
/* The plan a refusal sent them to buy. A ring rather than a fill: is-featured
   is our marketing opinion about which plan is best, and this is an answer to
   the specific thing they were just stopped from doing. The two must not look
   the same, or "the one you need" and "the one we push" become indistinguishable. */
.plan.is-wanted{box-shadow:0 0 0 2px var(--brand),0 0 0 6px var(--brand-halo)}
.plan.is-featured{background:var(--obsidian)}
.plan.is-featured .plan-name,.plan.is-featured .plan-price{color:var(--white)}
.plan-name{font-family:var(--sans);font-weight:600;font-size:11px;letter-spacing:.071em;
  text-transform:uppercase;color:var(--stone)}
.plan-price{font-size:1.5rem;font-weight:450;letter-spacing:-.03em;color:var(--obsidian)}

/* ── the six claims ───────────────────────────────────────────────────────
   What a plan buys, on the two screens that ask somebody to buy one: the wall
   at the end of the trial, and /billing. Two columns where there is room and
   one on a phone — six rows of icon-and-sentence in a single column is a
   scroll, and this has to be readable without one on the screen it argues on.
   Left-aligned inside a centred view on purpose: the sentences are long enough
   that centring them makes every line start in a different place. */
/* The first-run line under the composer. Centred with the fine print it sits
   beside, and gone for good after the first turn. */
.firstrun{margin:6px 0 0;text-align:center}
.claims{display:grid;grid-template-columns:1fr 1fr;gap:16px 24px;
  margin:24px auto 4px;max-width:46rem;padding:0;list-style:none;text-align:left}
.claim{display:flex;gap:10px;align-items:flex-start}
.claim>svg{flex:0 0 auto;margin-top:2px;color:var(--brand)}
.claim-t{display:block;font-family:var(--sans);font-size:14px;line-height:1.45;
  color:var(--obsidian)}
.claim-b{display:block;font-family:var(--sans);font-size:12.5px;line-height:1.5;
  color:var(--stone)}
@media (max-width:640px){.claims{grid-template-columns:1fr;gap:14px}}

/* ── dashboard ───────────────────────────────────────────────────────────────
   Four numbers first, because the questions people arrive with are what have I
   spent, what am I on, what do I have, and is anything running. Everything
   below is detail for after those are answered. */
.dsec .app-h1{text-align:left;margin:4px 0 0}
/* The account heading is an email address: one unbreakable word that overflowed its box by 6px at 375. */
#acctEmail{overflow-wrap:anywhere;word-break:break-word}
/* The sidebar's phone behaviour lives with the .side rules further down —
   this block was an empty husk whose comment pointed at rules that were
   never here, inviting the next edit into the wrong place. */
.dash-head{display:flex;align-items:flex-end;justify-content:space-between;gap:16px;
  flex-wrap:wrap;margin-bottom:24px}
.dash-head .app-h1{margin:4px 0 0;text-align:left}
.dash-who{margin:6px 0 0;text-align:left;color:var(--stone)}
.dash-cards{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:12px}
@media(max-width:880px){.dash-cards{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media(max-width:520px){.dash-cards{grid-template-columns:minmax(0,1fr)}}
.dcard{background:var(--white);border-radius:var(--radius);box-shadow:var(--ring-card);
  padding:16px 18px}
.dcard-h{margin:0;font-family:var(--sans);font-weight:600;font-size:10px;letter-spacing:.071em;
  text-transform:uppercase;color:var(--stone)}
.dcard-n{margin:8px 0 0;font-size:24px;font-weight:450;letter-spacing:-.03em;
  color:var(--obsidian);font-variant-numeric:tabular-nums}
.dcard-s{margin:6px 0 0;font-family:var(--sans);font-weight:600;font-size:10.5px;letter-spacing:.02em;
  color:var(--smoke);line-height:1.5}
.dbar{height:3px;border-radius:9999px;background:var(--hairline);margin:10px 0 0;overflow:hidden}
.dbar i{display:block;height:100%;background:var(--obsidian);width:0}
.dbar i.is-low{background:var(--alarm)}

.dash-h2{font-family:var(--display);font-size:1.15rem;font-weight:600;letter-spacing:-.01em;color:var(--obsidian);
  margin:36px 0 4px}
.dash-sub{margin:0 0 14px;text-align:left;max-width:44rem}

/* minmax(0,1fr) everywhere, never a bare 1fr: the implicit minimum on a grid
   track is `auto`, so one long project name or a nowrap cell widens the track
   past the container and the whole page scrolls sideways. This has now caused
   three separate overflow bugs in this app. */
/* A repository list, not a grid of cards.
 *
 * Two columns of stacked cards put two projects on a screen; rows put ten
 * there. Each card also carried its own copy of the last four sessions, above
 * a ledger that lists forty across every project and reopens them too — so the
 * card was a shorter duplicate of the thing directly underneath it. */
.dash-projects{display:flex;flex-direction:column}
/* Each project is visibly ONE thing. Hairline row separators made the list
   read as a run of text where nobody could tell where a project ended and
   the next began — cards get a surface, a border and room instead. */
/* One row, the way a list of repositories reads: what it is on the left,
   what you can do with it on the right, on the SAME line as the name. The
   card used to stack its controls underneath — a status sentence, then a
   blue button, then a meta line, then a second row with another blue button
   — so one project stood 180px tall and two filled the screen. Everything
   under the name is now a short left column and the actions sit at the top
   right; the More shelf, when opened, drops the row to full width so the
   extra controls have room (the :has() rule below; browsers without it keep
   the controls in the right column, narrower but whole). */
.repo{display:grid;grid-template-columns:minmax(0,1fr) auto;column-gap:18px;
  align-items:start;padding:14px 16px;margin:0 0 10px;background:var(--white);
  border:1px solid var(--hairline);border-radius:var(--radius);min-width:0}
.repo>*{grid-column:1;min-width:0}
.repo:last-child{margin-bottom:0}
.repo-empty{display:flex;flex-direction:column;align-items:flex-start;gap:12px;padding:22px 18px}
.repo-empty .app-fine{margin:0}
.proj-new{white-space:nowrap}
.repo-top{display:flex;align-items:center;gap:9px;min-width:0}
.repo-mark{flex:0 0 auto;width:30px;height:30px;border-radius:9px;
  display:grid;place-items:center;font-size:11px;font-weight:600;
  background:var(--pc,var(--hairline));color:var(--paper)}
.repo-n{margin:0;font-family:var(--display);font-size:16px;font-weight:600;color:var(--obsidian);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}
/* The state it IS, in the shape GitHub uses for the same control. A button
   reading "Make public" on a public repository is the ambiguity this avoids. */
.repo-vis{flex:0 0 auto;font:inherit;font-size:10.5px;font-family:var(--sans);font-weight:600;
  line-height:1;padding:3px 8px;cursor:pointer;
  border:1px solid var(--hairline);border-radius:var(--radius-pill);
  background:transparent;color:var(--stone)}
.repo-vis:hover:not(:disabled){color:var(--obsidian);box-shadow:var(--ring-ghost)}
.repo-vis:disabled{opacity:.5;cursor:progress}
.repo-vis.is-public{color:var(--obsidian);border-color:var(--slate)}
/* ── A project that is not yours ─────────────────────────────────────────
   Reads as a statement rather than a control, which is the difference from
   .repo-vis beside it: the visibility pill is a button somebody presses, and
   this is a fact about whose project this is. Same size and shape so the card
   header still scans as one row. */
.repo-shared{flex:0 0 auto;font-size:10.5px;font-family:var(--sans);font-weight:600;
  line-height:1;padding:3px 8px;white-space:nowrap;
  border:1px solid var(--hairline);border-radius:var(--radius-pill);
  background:transparent;color:var(--stone)}
/* Who is billed, said once on the card it is about. Quiet on purpose — it is
   context for a decision, not a warning about one. */
.collab-note{margin:6px 0 0;font-size:12px;color:var(--stone)}
.collab-note b{color:var(--obsidian);font-weight:600}
/* What became of an invitation link, at the top of the app rather than in a
   native alert(). One sentence, one way to dismiss it. */
.collab-say{display:flex;align-items:flex-start;gap:12px;margin:0 0 14px;padding:12px 14px;
  background:var(--paper);border:1px solid var(--hairline);border-radius:var(--radius)}
.collab-say p{margin:0;flex:1;min-width:0;font-size:13.5px;line-height:1.5;color:var(--obsidian)}
.collab-say b{font-weight:600}
/* The owner's problem, in the conversation where it stopped the work. Not a
   wall and not a button: there is nothing the reader can buy that would fix
   it. */
.collab-wall{margin:10px 0;font-size:13px;color:var(--stone)}
/* One person on the People sheet. */
.ppl-row{display:flex;align-items:center;gap:8px;padding:8px 0;
  border-bottom:1px solid var(--hairline)}
.ppl-row:last-child{border-bottom:0}
.ppl-who{flex:1 1 auto;min-width:0}
.ppl-who b{display:block;font-size:13px;font-weight:600;color:var(--obsidian);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.ppl-who small{display:block;font-size:11.5px;color:var(--stone)}
.ppl-role{flex:0 0 auto;width:auto;font-size:12px;padding:4px 6px}
.ppl-role-flat{flex:0 0 auto;font-size:11.5px;color:var(--stone)}
.repo-d{margin:5px 0 0;font-size:12.5px;color:var(--stone);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}
.repo-d a{color:inherit;text-decoration:none}
.repo-d a:hover{color:var(--obsidian);text-decoration:underline}
.repo-d-none{font-style:italic;color:var(--smoke)}
/* A preview's last day. Its own line rather than a tail on .repo-d, which
   ellipsises — the same trap that clipped the publish button here to invisible.
   Only ever rendered when the server sent a date. */
.repo-until{margin:3px 0 0;font-size:12px;color:var(--stone)}
/* The exit from "nobody can see this one". A real button, not a link-weight
   aside — this is the single step most accounts never take, so it must not
   whisper. Blue like every primary act; small enough to live in the line. */
.repo-m{margin:6px 0 0;font-family:var(--mono);font-size:10px;color:var(--stone)}
.repo-acts{grid-column:2;grid-row:1 / span 9;display:flex;flex-wrap:wrap;align-items:center;
  justify-content:flex-end;gap:8px;margin:0;min-width:0;max-width:26rem}
.repo:has(.repo-more[open]) .repo-acts{grid-column:1 / -1;grid-row:auto;
  justify-content:flex-start;margin-top:12px;max-width:none}
@media(max-width:760px){
  .repo{grid-template-columns:minmax(0,1fr)}
  .repo-acts{grid-column:1;grid-row:auto;justify-content:flex-start;margin-top:12px;max-width:none}
}
/* One primary act per row. When the site can be put online, that is it, and
   the preview beside it steps back to an outline so the eye has one place to
   land; once it is live, "Open site" takes the fill instead. */
.repo-acts:has(.repo-pub) .is-go:not(.repo-pub){background:var(--paper);
  border-color:var(--hairline);color:var(--obsidian)}
.repo-acts:has(.repo-pub) .is-go:not(.repo-pub):hover{filter:none;border-color:var(--obsidian)}
/* Buttons LOOK like buttons. These were bare text with the chrome stripped
   (background:none;border:0;padding:0) and the founder, testing his own
   product, could not tell they were clickable — "Open site" read as a label.
   Everything actionable on a card now has a pill shape and a hover state;
   the pair that lets you SEE the thing gets the brand fill. */
.repo-acts a,.repo-acts button{font:inherit;font-size:12.5px;font-weight:600;color:var(--obsidian);
  background:var(--paper);border:1px solid var(--hairline);border-radius:9999px;
  padding:7px 14px;cursor:pointer;text-align:center;text-decoration:none;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:100%}
.repo-acts a:hover,.repo-acts button:hover{border-color:var(--obsidian)}
/* The domain button's label is "hostname · status", and status is the half
   that must never truncate — "needs attention" clipped by the generic
   ellipsis above reads as fine, on the one row where that is a lie. Only
   the hostname is allowed to shrink; the status is flex:0 0 auto so it
   always renders whole. */
.repo-acts .repo-domain-btn{display:inline-flex;align-items:center;min-width:0}
.repo-acts .repo-domain-btn .repo-dom-host{overflow:hidden;text-overflow:ellipsis;
  white-space:nowrap;min-width:0}
.repo-acts .repo-domain-btn .repo-dom-stat{flex:0 0 auto;white-space:nowrap}
.repo-acts .is-go{background:var(--brand);border-color:var(--brand);color:var(--brand-on)}
.repo-acts .is-go:hover{filter:brightness(1.08);border-color:var(--brand)}
.repo-acct{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--stone);min-width:0}
.repo-acct select{font:inherit;font-size:12px;min-width:0;max-width:14rem;
  background:transparent;color:var(--obsidian);
  border:1px solid var(--hairline);border-radius:6px;padding:2px 6px}
.repo-acct select:disabled{opacity:.5;cursor:progress}
.dchip{font-family:var(--sans);font-weight:600;font-size:10px;letter-spacing:.02em;color:var(--stone);
  background:var(--paper);box-shadow:var(--ring-ghost);border-radius:9999px;padding:3px 8px}

.dash-recs{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}
@media(max-width:760px){.dash-recs{grid-template-columns:minmax(0,1fr)}}
.drec{background:var(--white);border-radius:var(--radius);box-shadow:var(--ring-card);padding:14px 16px}
.drec-j{margin:0;font-family:var(--sans);font-weight:600;font-size:10px;letter-spacing:.071em;
  text-transform:uppercase;color:var(--stone)}
.drec-m{margin:6px 0 0;font-size:14px;color:var(--obsidian)}
.drec-w{margin:6px 0 0;font-size:12.5px;line-height:1.5;color:var(--stone)}

.dash-models{display:grid;gap:2px}
/* This section argues that the spread is about 550x and therefore worth
   choosing on, and ten rows of "<$0.01 … $0.39" do not carry that: the cheapest
   and the dearest are the same length of text in the same weight. The bar does.
   Obsidian rather than a hue — cost is a scale somebody is picking a point on,
   not a state, and a traffic light here would call the frontier tier bad. */
.dmodel{display:grid;grid-template-columns:minmax(0,1fr) auto minmax(3rem,7rem) 3.4rem;
  align-items:center;gap:12px;background:var(--white);
  padding:10px 16px;box-shadow:var(--ring-ghost);border-radius:var(--radius)}
.dmodel-l{min-width:0;font-size:13.5px;color:var(--obsidian);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.dmodel-t{font-family:var(--sans);font-weight:600;font-size:10px;color:var(--smoke);text-transform:uppercase;
  letter-spacing:.05em;white-space:nowrap}
.dmodel-bar{display:block;height:6px;border-radius:var(--radius-pill);
  background:var(--hairline);overflow:hidden;min-width:0}
.dmodel-bar i{display:block;height:100%;border-radius:var(--radius-pill);
  background:var(--obsidian)}
.dmodel-p{font-family:var(--mono);font-size:12px;color:var(--obsidian);
  font-variant-numeric:tabular-nums;text-align:right}
.dmodel.is-locked .dmodel-l,.dmodel.is-locked .dmodel-p{color:var(--smoke)}
.dmodel.is-locked .dmodel-bar i{background:var(--ash)}
/* Narrow: the tier and the bar drop under the name rather than being crushed.
   Every child placed explicitly — moving two and letting the rest auto-place is
   how the leaders list put a percentage into a 3px column. */
@media (max-width:30rem){
  .dmodel{grid-template-columns:minmax(0,1fr) 3.4rem;row-gap:6px}
  .dmodel-l{grid-area:1/1/2/2}
  .dmodel-p{grid-area:1/2/2/3}
  .dmodel-t{grid-area:2/1/3/2}
  .dmodel-bar{grid-area:2/2/3/3}
}
/* `.dashbtn{display:none}` used to live here, and the only reason the Overview
   button ever appeared was JS setting an inline display to out-shout it. The
   nav's own [hidden] controls visibility now; a blanket display:none on a
   navigation item is how a whole dashboard ends up unreachable. */

/* A ledger row that reopens the session it describes. */
.t-open{cursor:pointer}
.t-open:hover td,.t-open:focus-visible td{background:var(--hairline)}

/* ── Explore tabs ─────────────────────────────────────────────────────── */
.xtabs{display:flex;gap:8px;flex-wrap:wrap;margin:18px 0 16px}
.xtab{appearance:none;border:0;cursor:pointer;font:inherit;font-size:13px;
  padding:7px 14px;border-radius:9999px;background:var(--hairline);color:var(--smoke)}
.xtab.is-on{background:var(--obsidian);color:var(--paper)}

/* ══ The Library room ═══════════════════════════════════════════════════════
   One vocabulary for five tabs. Every section is a .lib-sec: a heading, one
   sentence saying what it is for, then the thing itself — so a beginner
   landing on any tab is told what they are looking at before they have to
   read a number.

   The room's colour comes from [data-room="library"] on the section, the same
   triple every other room uses (--rc fill, --rt tint, --ri ink). Nothing here
   names a hue: the tint is what makes this screen feel like the Library and
   the Map feel like the Map, and it has to flip with the theme. */
.lib-sec{margin:0 0 26px}
.lib-h{margin:0 0 4px;font-family:var(--display);font-size:19px;font-weight:600;
  letter-spacing:-.01em;color:var(--obsidian);line-height:1.25}
.lib-s{margin:0 0 12px;font-size:13.5px;line-height:1.55;color:var(--stone);max-width:46rem}
.lib-s2{margin-top:-6px}
.lib-more{margin-top:12px}
/* Ours, and therefore quieter than the public rankings above it. */
.lib-sec.is-small .lib-h{font-size:16px}
.lib-sec.is-small .lib-s{font-size:12.5px}

/* ── Best for… ─────────────────────────────────────────────────────────────
   Three rows, each one a place on somebody else's ranking. The rank number
   sits in the room's tint so the ordering reads as the point of the card
   rather than as decoration. */
.bestrow{display:flex;flex-direction:column;gap:8px;margin:0 0 12px}
.bestcard{display:grid;grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:14px;
  background:var(--white);border-radius:var(--radius);box-shadow:var(--ring-card);padding:13px 16px}
.bestcard-n{display:inline-grid;place-items:center;width:26px;height:26px;border-radius:8px;
  background:var(--rt,var(--sunk));color:var(--ri,var(--obsidian));
  font-family:var(--sans);font-weight:700;font-size:12px}
.bestcard-b{min-width:0}
.bestcard-t{margin:0;font-size:15px;font-weight:500;color:var(--obsidian);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.bestcard-w{margin:2px 0 0;font-size:12.5px;line-height:1.45;color:var(--carbon)}
.bestcard-f{margin:2px 0 0;font-size:11.5px;line-height:1.45;color:var(--stone)}
.bestcard-s{display:flex;flex-direction:column;align-items:flex-end;gap:6px;flex:none}
.bestcard-p{margin:0;font-family:var(--mono);font-size:14px;color:var(--obsidian);
  display:flex;flex-direction:column;align-items:flex-end;line-height:1.2}
.bestcard-p small{font-family:var(--sans);font-size:10px;color:var(--stone);margin-top:2px}
.bestcard-use.is-on{color:var(--brand)}

/* ── The whole catalogue, in the pane ─────────────────────────────────── */
.allcat{margin:14px 0 0;background:var(--paper);border-radius:var(--radius);
  box-shadow:var(--ring-ghost);padding:14px 16px}
.allcat .tune-find{position:static;margin:0 0 10px}
.allrow{display:grid;grid-template-columns:minmax(0,1fr) auto auto;align-items:center;gap:12px;
  padding:9px 0;border-bottom:1px solid var(--hairline)}
.allrow:last-of-type{border-bottom:0}
.allrow-b{min-width:0}
.allrow-t{margin:0;font-size:14px;color:var(--obsidian);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.allrow-s{margin:2px 0 0;font-size:11.5px;line-height:1.4;color:var(--stone)}
.allrow-p{font-family:var(--mono);font-size:12.5px;color:var(--obsidian);white-space:nowrap}

/* ── Worth knowing ─────────────────────────────────────────────────────── */
.libfacts{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px}
.libfact{background:var(--white);border-radius:var(--radius);box-shadow:var(--ring-card);padding:14px 16px}
.libfact-h{margin:0;font-family:var(--sans);font-weight:600;font-size:10px;letter-spacing:.07em;
  text-transform:uppercase;color:var(--stone)}
.libfact-n{margin:6px 0 0;font-family:var(--display);font-size:22px;font-weight:700;
  letter-spacing:-.02em;color:var(--obsidian);line-height:1.1;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.libfact-s{margin:6px 0 0;font-size:12.5px;line-height:1.5;color:var(--stone)}

/* ── Showcase ──────────────────────────────────────────────────────────────
   Richy, 2026-09-06: "two columns per row." Two at every width above a phone,
   and the screenshot is the card — a gallery whose pictures are thumbnails
   beside text is a list. */
.showgrid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px}
.showcard{display:flex;flex-direction:column;background:var(--white);border-radius:var(--radius);
  box-shadow:var(--ring-card);overflow:hidden;text-decoration:none;color:inherit}
.showcard:hover{box-shadow:var(--ring-card-hover)}
.showcard-img{display:block;aspect-ratio:4/3;background:var(--sunk);overflow:hidden}
.showcard-img img{display:block;width:100%;height:100%;object-fit:cover;object-position:top center}
.showcard-b{display:block;padding:12px 14px 4px}
.showcard-t{display:block;font-size:15px;font-weight:500;color:var(--obsidian)}
.showcard-s{display:block;margin-top:3px;font-size:12.5px;line-height:1.45;color:var(--stone)}
.showcard-c{display:inline-block;margin:8px 14px 13px;align-self:flex-start;
  font-family:var(--mono);font-size:11.5px;color:var(--ri,var(--obsidian));
  background:var(--rt,var(--sunk));border-radius:var(--radius-pill);padding:3px 9px}

/* ── Instead of / Save money ──────────────────────────────────────────── */
.dash-instead{display:flex;flex-direction:column}
.insgroup{margin-bottom:22px}
.insgrid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}
/* A group with one row in it is one row, not a half-width card with an empty
   column beside it — which is what "Hosting: Vercel" looked like on the only
   screen most accounts will ever see here. */
.insgrid > .dins:only-child{grid-column:1 / -1}
.dins{background:var(--white);border-radius:var(--radius);box-shadow:var(--ring-card);padding:16px 18px;
  display:flex;flex-direction:column}
.dins-top{display:flex;align-items:baseline;justify-content:space-between;gap:10px}
.dins-p{margin:0;font-size:15px;font-weight:500;color:var(--obsidian)}
.dins-c{margin:0;font-family:var(--sans);font-weight:600;font-size:11px;color:var(--smoke);
  text-align:right;flex:none}
.dins-ex,.dins-real{font-family:var(--sans);font-weight:600;font-size:9.5px;letter-spacing:.06em;
  text-transform:uppercase;border-radius:var(--radius-pill);padding:2px 7px;white-space:nowrap}
.dins-ex{background:var(--sunk);color:var(--stone)}
.dins-real{background:var(--rt,var(--sunk));color:var(--ri,var(--obsidian))}
.dins-o{margin:8px 0 0;font-size:14px;font-weight:500;color:var(--live)}
.dins-n{margin:6px 0 0;font-size:12.5px;line-height:1.55;color:var(--stone)}
.dins-seen{margin:6px 0 0;font-size:11.5px;line-height:1.5;color:var(--smoke)}
.dins-money{margin:12px 0 0;display:grid;gap:6px;border-top:1px solid var(--hairline);padding-top:10px}
.dins-money div{display:grid;grid-template-columns:8.5rem minmax(0,1fr);gap:10px}
.dins-money dt{font-family:var(--sans);font-weight:600;font-size:10px;letter-spacing:.06em;
  text-transform:uppercase;color:var(--stone);padding-top:2px}
.dins-money dd{margin:0;font-size:12.5px;line-height:1.5;color:var(--obsidian)}
.dins-go{margin-top:13px;align-self:flex-start}

/* ── Marketplace ──────────────────────────────────────────────────────── */
.dash-market{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:10px 0 0}
.dmkt{background:var(--white);border-radius:var(--radius);box-shadow:var(--ring-card);padding:16px 18px}
/* An example is not a listing. Flat, tinted, no shadow: it must never be
   mistaken for something a person published and you can install. */
.dmkt.is-ex{background:var(--paper);box-shadow:var(--ring-ghost)}
.dmkt-h{margin:0;font-family:var(--sans);font-weight:600;font-size:10px;letter-spacing:.07em;
  text-transform:uppercase;color:var(--stone)}
.dmkt-t{margin:6px 0 0;font-size:16px;font-weight:500;color:var(--obsidian)}
.dmkt-s{margin:6px 0 0;font-size:13px;line-height:1.5;color:var(--stone)}
.dmkt-a{display:flex;gap:10px;align-items:center;margin-top:14px}

/* ── Automations ──────────────────────────────────────────────────────── */
.autokinds{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px}
.autokind{display:flex;flex-direction:column;background:var(--white);border-radius:var(--radius);
  box-shadow:var(--ring-card);padding:16px 18px}
.autokind-t{margin:0;font-size:15.5px;font-weight:500;color:var(--obsidian)}
.autokind-s{margin:6px 0 0;font-size:12.5px;line-height:1.5;color:var(--stone)}
.autokind-d{margin:12px 0 0;display:grid;gap:6px}
.autokind-d div{display:grid;grid-template-columns:4.6rem minmax(0,1fr);gap:8px}
.autokind-d dt{font-family:var(--sans);font-weight:600;font-size:10px;letter-spacing:.06em;
  text-transform:uppercase;color:var(--stone);padding-top:2px}
.autokind-d dd{margin:0;font-size:12.5px;line-height:1.5;color:var(--obsidian)}
.autokind-f{margin-top:auto;padding-top:14px;display:flex;flex-direction:column;
  align-items:flex-start;gap:6px}
.auto-mo{margin:5px 0 0;font-size:12px;line-height:1.45;color:var(--smoke)}
.autojobs{display:flex;flex-direction:column}
.autojob{display:grid;grid-template-columns:minmax(0,1fr) auto auto;align-items:center;gap:12px;
  padding:10px 0;border-bottom:1px solid var(--hairline)}
.autojob:last-child{border-bottom:0}
.autojob-t{min-width:0;font-size:14px;color:var(--obsidian);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.autojob-s{font-family:var(--sans);font-weight:600;font-size:10px;letter-spacing:.06em;
  text-transform:uppercase;color:var(--stone)}
.autojob-c{font-family:var(--mono);font-size:12.5px;color:var(--obsidian)}

/* ── The phone ─────────────────────────────────────────────────────────────
   820px is where the dashboard's pane stops being a desktop column. Two
   columns survive down to 480 on the showcase, because Richy asked for two
   per row and a 4:3 screenshot at 220px still reads; everything else that
   depends on side-by-side text goes to one. */
/* Fourteen chips scrolling sideways with no visible edge is a control that
   hides most of itself. There is room to wrap them above the phone; below it
   there is not, and .rec-pills scrolls as it does everywhere else. */
@media(min-width:821px){ #bestCats{flex-wrap:wrap} }
@media(max-width:1000px){
  .libfacts{grid-template-columns:repeat(2,minmax(0,1fr))}
  .autokinds{grid-template-columns:minmax(0,1fr)}
}
@media(max-width:820px){
  .lib-h{font-size:17px}
  .lib-s{font-size:13px}
  .insgrid,.dash-market{grid-template-columns:minmax(0,1fr)}
  .libfacts{grid-template-columns:minmax(0,1fr)}
  .dins-money div{grid-template-columns:minmax(0,1fr);gap:1px}
  .dins-money dd{color:var(--carbon)}
  /* The price and the button stack under the name rather than squeezing it
     into a column too narrow to hold a model's name. */
  .bestcard{grid-template-columns:auto minmax(0,1fr);row-gap:10px}
  .bestcard-s{grid-column:1 / -1;flex-direction:row;align-items:center;justify-content:space-between;
    width:100%}
  .bestcard-p{flex-direction:row;align-items:baseline;gap:6px}
  .bestcard-p small{margin-top:0}
  .allrow{grid-template-columns:minmax(0,1fr) auto;row-gap:8px}
  .allrow button{grid-column:1 / -1;justify-self:start}
}
@media(max-width:480px){
  .showgrid{gap:10px}
  .showcard-b{padding:10px 12px 4px}
  .showcard-t{font-size:13.5px}
  .showcard-s{display:none}
  .showcard-c{margin:6px 12px 11px}
}

/* ── Connect ──────────────────────────────────────────────────────────── */
.dash-conns{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}
@media(max-width:760px){.dash-conns{grid-template-columns:minmax(0,1fr)}}
/* A heading INSIDE the machines grid, spanning it.
   .dash-conns is a two-column grid, so a heading without this sits in one cell
   beside a machine card and reads as a label for that one machine — which is
   the opposite of what it says. */
.dconn-group{grid-column:1/-1;margin:8px 0 0;font-family:var(--sans);font-weight:600;
  font-size:10px;letter-spacing:.08em;text-transform:uppercase;color:var(--slate)}
.dconn-group:first-child{margin-top:0}
.dconn{background:var(--white);border-radius:var(--radius);box-shadow:var(--ring-card);padding:16px 18px}
.dconn-top{display:flex;align-items:center;justify-content:space-between;gap:10px}
.dconn-n{margin:0;font-size:16px;font-weight:500;color:var(--obsidian)}
.dconn-state{font-family:var(--sans);font-weight:600;font-size:9.5px;letter-spacing:.08em;
  padding:4px 8px;border-radius:9999px;background:var(--hairline);color:var(--smoke)}
.dconn-state.is-on{color:var(--live)}
.dconn-li{margin:4px 0 0;font-size:13px;line-height:1.5;color:var(--stone)}
.dconn-scopes{margin:10px 0 0;font-size:12px;line-height:1.5;color:var(--stone)}
.dconn-alt{margin:10px 0 0;font-size:12px;line-height:1.5;color:var(--stone);font-style:italic}

/* ── Account ──────────────────────────────────────────────────────────── */
.dash-acct{display:flex;flex-direction:column;gap:12px;margin-top:18px;max-width:44rem}
.dacct{background:var(--white);border-radius:var(--radius);box-shadow:var(--ring-card);
  padding:16px 18px;display:flex;align-items:center;justify-content:space-between;gap:14px}
.dacct-t{margin:0;font-size:15px;color:var(--obsidian)}
.dacct-s{margin:4px 0 0;font-size:12.5px;color:var(--stone);line-height:1.5}

/* ── The line under the composer, now a control ───────────────────────── */
/* --stone, not --smoke: this line carries the PRICE — the product's whole
   pitch — and smoke's own comment says "placeholders, never content". It
   measured 2.27:1; the one sentence that must always be legible was the
   least legible text on the page. */
/* font:inherit resets the size .app-fine set a few hundred lines up, so this
   caption rendered at body size — 17px — and wrapped onto three lines under
   the composer on a tablet, the tallest thing on the screen after the
   heading. The size is restated after the reset, on purpose. */
.chatfine{appearance:none;background:none;border:0;cursor:pointer;font:inherit;
  font-size:12.5px;line-height:1.4;
  color:var(--stone);padding:6px 8px;border-radius:8px}
.chatfine:hover{background:var(--hairline);color:var(--obsidian)}
/* The model's name is the part that opens the sheet, so it alone is dressed as
   a control — a bordered pill with a chevron — rather than a dotted-underlined
   word that read as a footnote and left people sure there was no way to change
   the model at all. The rest of the line stays caption. */
.chatfine #chatModel{display:inline-flex;align-items:center;gap:4px;
  padding:1px 6px 1px 9px;border-radius:999px;border:1px solid var(--hairline);
  color:var(--carbon);text-decoration:none;line-height:1.5;vertical-align:baseline}
.chatfine #chatModel::after{content:'\25BE';font-size:9px;color:var(--stone);position:relative;top:-1px}
.chatfine:hover #chatModel{border-color:var(--stone);color:var(--obsidian)}
/* The switch, quieter than the line it sits beside: it is a way out, not an
   instruction, and dressed as a button it competed with the model pill for
   the one glance this strip gets. */
.chatswap{appearance:none;background:none;border:0;cursor:pointer;font:inherit;
  font-size:11.5px;line-height:1.4;color:var(--graphite);padding:6px 6px;
  border-radius:8px;text-decoration:underline;text-decoration-style:dotted;
  text-underline-offset:2px}
.chatswap:hover{background:var(--hairline);color:var(--obsidian)}

/* ── @mentions ────────────────────────────────────────────────────────── */
.mentions{background:var(--white);border-radius:var(--radius);box-shadow:var(--ring-card);
  overflow:hidden;margin-bottom:8px}
.mention{display:flex;align-items:center;justify-content:space-between;gap:12px;
  width:100%;appearance:none;border:0;background:none;cursor:pointer;font:inherit;
  padding:11px 14px;border-bottom:1px solid var(--hairline);text-align:left}
.mention:last-child{border-bottom:0}
.mention:hover{background:var(--hairline)}
.mention-n{font-size:14px;font-weight:500;color:var(--obsidian)}
.mention-s{font-size:12px;color:var(--stone)}
.mention-s.is-on{color:var(--live)}

/* ── Project secrets ──────────────────────────────────────────────────────
   Names in mono because they ARE environment variables; no value column
   exists to style, which is the point of the panel. */
.sec-row{display:flex;align-items:center;gap:10px;padding:9px 0;
  border-bottom:1px solid var(--hairline)}
.sec-name{font-family:var(--mono);font-size:12px;color:var(--obsidian);
  overflow-wrap:anywhere}
.sec-when{flex:1;font-family:var(--mono);font-size:10.5px;color:var(--stone);
  text-align:right}
.sec-form{display:flex;flex-direction:column;gap:8px;margin-top:14px}
.sec-key{font-family:var(--mono);text-transform:uppercase}

/* ── Sheets ───────────────────────────────────────────────────────────── */
.sheet-wrap{position:fixed;inset:0;z-index:60;background:var(--scrim);
  display:flex;align-items:center;justify-content:center;padding:20px}
.sheet{background:var(--paper);border-radius:18px;padding:22px;width:min(560px,100%);
  max-height:86vh;overflow-y:auto;box-shadow:0 24px 60px rgba(0,0,0,.28)}
.sheet-head{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:14px}
.sheet-title{margin:0;font-family:var(--display);font-size:20px;font-weight:600;letter-spacing:-.01em;color:var(--obsidian)}
.sheet-input{width:100%;box-sizing:border-box;background:var(--white);border:1px solid var(--hairline);
  border-radius:12px;padding:12px 14px;font:inherit;font-size:14px;color:var(--obsidian);margin-bottom:12px}
.sheet-tall{min-height:130px;resize:vertical}
.sheet-err{color:var(--alarm);margin:0 0 10px}
/* Success has its own colour. It landed in .sheet-err before, so "Invited.
   They will get an email" was printed in red error text (breakit2, finding 10). */
.sheet-ok{color:var(--live);margin:0 0 10px}
.tune-now{background:var(--white);border-radius:14px;box-shadow:var(--ring-card);padding:14px 16px;margin-bottom:16px}
.tune-lbl{margin:0 0 8px;font-family:var(--sans);font-weight:600;font-size:10px;letter-spacing:.08em;
  text-transform:uppercase;color:var(--stone)}
.tune-model{margin:0;font-size:17px;font-weight:500;color:var(--obsidian)}
.tune-cost{margin:6px 0 0;font-size:13px;line-height:1.5;color:var(--stone)}
.tune-efforts,.tune-models{display:flex;flex-direction:column;gap:6px;margin-bottom:18px}
.tune-row{display:flex;align-items:center;justify-content:space-between;gap:12px;width:100%;
  appearance:none;border:0;background:var(--white);box-shadow:var(--ring-card);cursor:pointer;
  font:inherit;padding:11px 14px;border-radius:12px;text-align:left}
.tune-row:hover{background:var(--hairline)}
.tune-row.is-on{outline:2px solid var(--obsidian)}
/* Block, not inline — as spans the label and the blurb ran together into
   "DeepSeek V4 FlashThe cheapest thing here". */
.tune-row-n{display:block;font-size:14px;color:var(--obsidian)}
.tune-row-s{display:block;font-size:12px;color:var(--stone);margin-top:3px;line-height:1.45}
.tune-row > span:first-child{min-width:0}
/* The search box over the full catalogue. Full width and sticky, because the
   list under it is hundreds of rows and a filter you have scrolled past is a
   filter you have lost. 16px on purpose: anything smaller makes iOS Safari
   zoom the whole sheet on focus. */
.tune-find{position:sticky;top:0;z-index:1;width:100%;box-sizing:border-box;
  font:inherit;font-size:16px;padding:10px 12px;margin:2px 0 8px;
  border:0;border-radius:12px;background:var(--white);box-shadow:var(--ring-card);
  color:var(--obsidian)}
.tune-find:focus-visible{outline:2px solid var(--obsidian);outline-offset:1px}
.tune-more .tune-row-n{color:var(--brand)}
/* Filter and sort pills over the full catalogue.
   Wrapping, not scrolling: unlike the conversation strip these are a set you
   are meant to READ before choosing, and a row you have to swipe hides options
   from the person who most needs them. */
.mpills{display:flex;flex-wrap:wrap;gap:6px;margin:0 0 10px}
.mpills-l{font-family:var(--sans);font-weight:600;font-size:10px;letter-spacing:.06em;
  text-transform:uppercase;color:var(--stone);margin:8px 0 5px}
.mpill{flex:0 0 auto;font-family:var(--sans);font-weight:600;font-size:11px;letter-spacing:.03em;
  cursor:pointer;color:var(--obsidian);background:var(--white);border:0;
  box-shadow:var(--ring-ghost);padding:5px 11px;border-radius:var(--radius-pill)}
.mpill:hover{box-shadow:var(--ring-card-hover)}
.mpill.is-on{background:var(--obsidian);color:var(--white)}
.mpill:focus-visible{outline:2px solid var(--obsidian);outline-offset:2px}
/* The way out of a refusal, styled as the action it is. It sits inside an
   error line, and an underlined blue word among red text reads as a footnote —
   this is the thing we actually want them to press. */
.err-cta{display:inline-block;margin-top:4px;font-family:var(--sans);font-weight:600;font-size:12px;
  letter-spacing:.04em;text-decoration:none;color:var(--white);background:var(--brand);
  padding:5px 11px;border-radius:var(--radius-pill)}
.err-cta:hover{box-shadow:0 0 0 3px var(--brand-halo)}
.tune-row-c{font-family:var(--mono);font-size:12px;color:var(--obsidian);white-space:nowrap}
/* Which shelf Amelia shops from, inside "let Amelia choose". Indented against
   the postures above it so it reads as part of that decision rather than a
   fourth thing to decide — the whole bug it fixes was two controls that looked
   independent and were not. */
.tune-pool{margin:-8px 0 18px;padding-left:12px;border-left:2px solid var(--hairline)}
.tune-pool .tune-lbl{margin-bottom:6px}
.tune-pool .tune-postures{margin-bottom:0}
/* The sentence that says Amelia is NOT choosing. It only appears when the
   posture rows have gone dark, so it has to carry the explanation on its own. */
.tune-manual{margin:-8px 0 18px;font-size:12.5px;line-height:1.5;color:var(--obsidian)}
/* The computers this account can reach, and — when there are none — the three
   steps to connect one. Indented against the three ways above them for the
   same reason .tune-pool is: they are part of that decision, not a fourth
   thing to decide. */
.tune-own{margin:-8px 0 18px;padding-left:12px;border-left:2px solid var(--hairline)}
.tune-own .tune-lbl{margin-bottom:6px}
.tune-own-rows{display:flex;flex-direction:column;gap:6px}
.tune-own-row{padding:9px 12px;border-radius:12px;background:var(--white);box-shadow:var(--ring-card)}
/* ONLINE / OFFLINE beside the name rather than under it: the name is what is
   being looked for and the state is what is being checked, and stacking them
   makes a three-machine list twice as tall for no more information. */
.tune-own-state{margin-left:8px;font-family:var(--sans);font-weight:600;font-size:9px;
  letter-spacing:.08em;color:var(--stone)}
.tune-own-state.is-on{color:var(--brand)}
/* Three steps, numbered, because "download it, pair it, pick a model" is an
   ORDER and a bulleted list says it is not. */
.tune-steps{margin:0 0 10px;padding-left:18px;font-size:12.5px;line-height:1.55;color:var(--obsidian)}
.tune-steps li{margin-bottom:5px}
/* A heading inside the model list, naming whose computer the rows under it
   belong to. Their models sit in the SAME list as ours, because a second list
   somewhere else is how two selections came to be lit at once. */
.tune-lbl-sub{margin-top:2px}
/* Where the answer will run, in the header. "Free on Studio iMac" is longer
   than a model name and this sits beside the clock, so it is allowed to end in
   an ellipsis rather than push the rest of the row off screen. */
#modelState{max-width:24ch;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  vertical-align:middle}

/* A dashboard heading is not a hero. .app-h1 clamps to 3rem for the centred
   landing states, which inside a section makes a four-word title wrap twice. */
.dsec .app-h1{font-size:clamp(1.5rem,2.4vw,1.9rem);line-height:1.15;letter-spacing:-.03em}
.dsec .cap{margin:0 0 2px}
.dsec .dash-head{margin-bottom:20px}
/* A button that cannot be pressed has to look like one. */
.dconn .btn[disabled]{opacity:.4;cursor:not-allowed}

/* The first glyph was being shaved off ("Vhat's worth using"). .dash is a
   scroll container, and a display face with negative side bearing plus the
   tight letter-spacing puts the leading stroke a hair outside the box, where
   the container clips it. A couple of pixels of gutter is all it needs. */
.dash-pane{padding-left:2px}
.dsec .app-h1{padding-left:1px}

/* ── Dashboard width ──────────────────────────────────────────────────────
   .app is 52rem because a conversation reads badly wider than that. A
   dashboard is not a conversation: at 52rem the sidebar and the content pane
   fight for room, and the nav ends up overlapping the heading it sits beside. */
.app:has(.side:not([hidden])){max-width:1280px}
/* Both tracks must be allowed to shrink, or a wide child (the + Build button,
   a long model name) pushes its track past the column and paints over the
   next one. */

/* ── The header at phone widths ───────────────────────────────────────────
   Allowance, machine state, Overview, theme and Sign out do not fit across
   390px, and the row pushed the whole page into a sideways scroll. Predates
   the dashboard; visible now because the dashboard is wider. */
@media(max-width:560px){
  .app{padding:0 14px}
  /* Two rows, decided rather than wrapped.
     Wrapping put Sign out — the widest, tallest control — alone on a third
     row, right-aligned under forty pixels of nothing, and at 430px the header
     was 146px tall before a word of conversation. Richy, 2026-09-02: "the
     sign out is pushing everything down".
     Row one: the brand, then theme and Sign out at the far right. Row two:
     project, allowance, Report. The break between them is forced by the
     ::after item so nothing depends on how wide a project name happens to
     be. display:contents lifts the right-hand group's children into the
     header's own flex flow so they can be ordered across the two rows. */
  .app-top{height:auto;padding:6px 0;flex-wrap:wrap;gap:4px 8px}
  .app-top .brand{order:0;flex:1 1 auto;min-width:0}
  .app-top-right{display:contents}
  .app-top-tail{order:1;gap:6px}
  .app-top::after{content:"";order:2;flex:0 0 100%;height:0}
  /* :not(), because this rule and .app-top-tail's weigh the same and this
     one is later — without it the tail lands on row two after all. */
  .app-top-right>*:not(.app-top-tail){order:3}
  .app-top-right .mstate{font-size:10px}
  .projwrap{flex:0 1 auto;min-width:0}
  .projbtn{max-width:100%}
  /* Report rides on the brand row, beside theme and Sign out — Richy,
     2026-09-02, after a long project name pushed it onto a third row of its
     own. One word here: "Report a problem" is a third of a phone's width,
     and the title of the sheet it opens says the rest. Row two is then the
     project and the allowance, which are the two that change. */
  .app-top-right>.reportbtn{order:1;font-size:0;padding:6px 10px}
  .reportbtn::before{content:"Report";font-size:10px;letter-spacing:.05em}
  .app-top-right #allowance{margin-left:auto}
  /* The project menu hangs off the button's RIGHT edge, which is right for a
     button at the right of a wide header and wrong for the same button at the
     left of a phone's second row — it opened 200px off the left of the
     screen. Left-anchored here, and never wider than the screen. */
  .projmenu{left:0;right:auto;max-width:calc(100vw - 28px);
    min-width:min(15rem,calc(100vw - 28px))}
  /* The machine clock and the model button are the least useful of these
     on a small screen — the allowance is the number that changes a
     decision, and the model is named under the composer already. */
  .app-top-right #machineState,.app-top-right #modelState{display:none}
  /* Forty, not the coarse-pointer forty-four: these two sit beside the brand
     and set the row's height. Still a fingertip. */
  .app-top-tail .themebtn{width:40px;height:40px}
  .app-top-tail .btn.sm{padding:.55rem .75rem;font-size:12px}
  /* The chat's tail: the mode bar, the composer and the price line were four
     spaced blocks under a keyboard. Tighter on a phone, and the safe area is
     respected when there is one. */
  .view.chat{gap:4px;padding-bottom:calc(4px + env(safe-area-inset-bottom,0px))}
  .modebar{margin:0}
  /* The composer row and the price line under it, trimmed. Richy,
     2026-09-02: "too much spacing below 'ask for anything'". The textarea
     keeps 16px — anything smaller makes iOS zoom the page on focus, which
     is the bug every other bug on this list was small next to. */
  .composer{padding:5px 5px 5px 13px;gap:6px}
  .composer textarea{padding:.3rem 0}
  .chatfine{padding:1px 6px;font-size:11px;line-height:1.25}
  .chatfine #chatModel{padding:0 4px 0 6px;line-height:1.3}
}

/* ── Header nav ───────────────────────────────────────────────────────────
   Naming the sections is what makes the dashboard discoverable at all; one
   word ("Overview") beside a theme icon read as a status label, not a door. */
/* .topnav removed — nothing in app.html has carried the class since the
   sidebar became the only navigation. */

/* The dashboard pane.
   Restored after a cleanup regex removed it along with the old sidebar rules.
   Without it #viewDash falls back to .view — a centred flex column — and every
   card in every section came out centre-aligned. */
/* overflow-y:auto, not visible. This rule sits after .view, so `visible` here
   silently cancelled the scroller .view grants — and the dashboard is the
   longest thing in the app. */
.dash{display:block;text-align:left;align-items:stretch;
  max-width:1100px;margin:0;padding:24px 0 64px;overflow-y:auto}
/* An overlay scrollbar on macOS is invisible until something moves, so a pane
   that scrolls perfectly well reads as a page that is simply cut off ("I'm not
   able to scroll down", Richy, 2026-09-06). On a pointer screen the gutter is
   reserved and the bar is always drawn, so the screen says out loud that there
   is more of it.

   Desktop only, and on purpose. A touch screen has no pointer to aim at a bar,
   nobody there is looking for one, and reserving eleven pixels on a 390px
   phone spends them on nothing — it takes the right-hand margin off every card
   and leaves the column visibly off-centre. */
@media (min-width:821px){
  .dash{scrollbar-gutter:stable;scrollbar-width:thin;scrollbar-color:var(--ash) transparent}
  .dash::-webkit-scrollbar{width:11px}
  .dash::-webkit-scrollbar-track{background:transparent}
  .dash::-webkit-scrollbar-thumb{background:var(--ash);border-radius:6px;
    border:3px solid transparent;background-clip:padding-box}
  .dash::-webkit-scrollbar-thumb:hover{background:var(--smoke);background-clip:padding-box;
    border:3px solid transparent}
}
.dash-pane{min-width:0}

/* ══ App shell ═════════════════════════════════════════════════════════════
   The menu is the left column of the whole signed-in app. Dense rather than
   airy: small rows, an icon on every one, grouped under labels — the shape of
   a control panel you check often, not a page you read once.

   Radius stays 6px throughout. paper.css calls that out as "never larger", and
   the density here comes from spacing and type, not from rounder corners. */
.shell{flex:1;display:flex;min-height:0;min-width:0}
.side{flex:0 0 216px;width:216px;box-sizing:border-box;
  display:flex;flex-direction:column;gap:2px;
  /* Its own scroller. The menu is now pinned beside the conversation rather
     than above it, so on a laptop in a short window the last rows would
     otherwise be unreachable — the same complaint moved from the page to the
     sidebar. */
  min-height:0;overflow-y:auto;
  padding:12px 12px 12px 0;border-right:1px solid var(--hairline)}
/* min-height:0 alongside min-width:0. Belt and braces, and labelled as such:
   removing it again does NOT reproduce the bug once .app has a definite
   height, because .shell stretches this box to a known height and
   min-height:auto never bites. Measured, not assumed — the guard passes
   without this line, so it is insurance against the chain changing rather
   than part of the fix. */
.main{flex:1;min-width:0;min-height:0;display:flex;flex-direction:column;padding-left:24px}
.side[hidden] + .main{padding-left:0}

/* Every icon inherits the row's colour, so hover and the active state need no
   extra rules for the glyph. */
.ic{flex:0 0 auto;display:block}

/* ── who you are, at the top ──────────────────────────────────────────── */
.side-acct{display:flex;align-items:center;gap:9px;width:100%;box-sizing:border-box;
  appearance:none;background:none;border:0;cursor:pointer;text-align:left;
  padding:7px 9px;border-radius:var(--radius);margin-bottom:6px}
.side-acct:hover{background:var(--hairline)}
.side-mark{width:22px;height:22px;border-radius:var(--radius-pill);flex:0 0 auto}
.side-acct-t{min-width:0;display:flex;align-items:center;gap:7px}
.side-acct-n{font-size:13px;color:var(--obsidian);font-weight:500;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.side-plan{flex:0 0 auto;font-size:9px;padding:2px 6px;border-radius:var(--radius-pill);
  background:var(--hairline);color:var(--stone);letter-spacing:.06em}

/* ── find ─────────────────────────────────────────────────────────────── */
.side-find{display:flex;align-items:center;gap:8px;width:100%;box-sizing:border-box;
  appearance:none;cursor:pointer;text-align:left;font-family:var(--sans);font-size:13px;
  background:var(--white);border:1px solid var(--hairline);color:var(--stone);
  padding:7px 9px;border-radius:var(--radius);margin-bottom:12px}
.side-find:hover{border-color:var(--ash);color:var(--obsidian)}
.side-find > span:nth-child(2){flex:1}
.side-kbd{font-family:var(--mono);font-size:10px;color:var(--stone);
  border:1px solid var(--hairline);border-radius:4px;padding:1px 5px;background:var(--paper)}

/* ── the rows ─────────────────────────────────────────────────────────── */
.side-nav{display:flex;flex-direction:column;align-items:stretch;gap:1px;min-width:0}
/* Full-strength --stone, no opacity: the token passes contrast at 5.5:1 and
   the .85 that used to sit here diluted it just under the line — the one
   axe finding in the whole chat view. */
.side-grp{margin:12px 0 4px;padding:0 9px;color:var(--stone);font-size:9.5px}
.side-grp:first-child{margin-top:0}
.snav{display:flex;align-items:center;gap:9px;width:100%;box-sizing:border-box;
  appearance:none;background:none;border:0;cursor:pointer;text-align:left;
  font-family:var(--sans);font-size:13px;line-height:18px;
  /* Height from padding and line-height, never constrained — a fixed height
     against a taller line-height is what clipped the previous rail's labels. */
  padding:7px 9px;border-radius:var(--radius);color:var(--stone);
  /* Wrap, never clip. The rail is 216px and two labels are honestly longer
     than that; nowrap painted them out past the pill and the column edge cut
     them mid-word. The phone rail below restores nowrap — there the row
     scrolls sideways instead. */
  white-space:normal;overflow-wrap:break-word;overflow:visible;
  transition:background .12s ease,color .12s ease}
.snav:hover{background:var(--hairline);color:var(--obsidian)}
.snav.is-on{background:var(--obsidian);color:var(--paper)}

/* ── identity and theme, pinned ───────────────────────────────────────── */
.side-foot{margin-top:auto;padding-top:12px;border-top:1px solid var(--hairline);
  display:flex;align-items:center;gap:8px;min-width:0}
.side-mail{flex:1;min-width:0;font-family:var(--sans);font-weight:600;font-size:10px;color:var(--stone);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.side-theme{flex:0 0 auto}

@media(max-width:820px){
  .shell{flex-direction:column}
  /* min-width:0 is the whole fix. A flex item defaults to min-width:auto, so
     the sidebar refused to shrink below its seven items laid end to end and
     pushed the PAGE sideways instead of letting the nav scroll inside itself. */
  .side{flex:none;width:auto;min-width:0;max-width:100%;padding:8px 0;
    border-right:0;border-bottom:1px solid var(--hairline);
    flex-direction:row;align-items:center;gap:8px}
  .side-nav{max-width:100%;flex:1}
  /* On a phone the rail becomes one scrolling strip: the account block, find
     and the footer would eat the whole screen stacked. */
  .side-acct,.side-find,.side-foot{display:none}
  .side-grp{display:none}
  .side-nav{position:static;flex-direction:row;overflow-x:auto;gap:6px;
    -webkit-overflow-scrolling:touch;
    /* The strip is longer than the screen and the only hint was a word cut
       in half at the edge. A fade says "more" without cutting anything; the
       padding keeps the last item clear of it at the end of the scroll. */
    padding-right:28px;scrollbar-width:none;
    -webkit-mask-image:linear-gradient(to right,#000 calc(100% - 28px),transparent);
    mask-image:linear-gradient(to right,#000 calc(100% - 28px),transparent)}
  .side-nav::-webkit-scrollbar{display:none}
  .snav{width:auto;flex:0 0 auto;white-space:nowrap}
  .main{padding-left:0}
  /* Reading mode. Scroll back through the conversation and the header, the
     tab strip, the mode bar and the price line get out of the way; scroll
     toward the newest message, or touch the composer, and they return. On a
     phone those four take a third of the screen, and while somebody is
     reading what was said they are furniture. Richy, 2026-09-02: "if I
     scroll up the chat it should hide the top bar and categories". The
     composer itself stays — leaving is not the same as losing the reply. */
  .app.is-reading .app-top,.app.is-reading .side,
  .app.is-reading .modebar,.app.is-reading .chatfine{display:none}}

/* A couple of pixels of gutter. The display face has negative side bearing and
   the pane is a scroll boundary, so without it the first glyph of every eyebrow
   and heading is shaved ("VERVIEW"). */
.dash-pane{padding-left:7px}

/* ── Find ─────────────────────────────────────────────────────────────────
   Sits near the top rather than centred: you are reading downward through
   results, and a centred box jumps as the list grows. */
.pal-wrap{align-items:flex-start;padding-top:12vh}
.pal{background:var(--paper);border-radius:var(--radius);width:min(560px,100%);
  box-shadow:0 24px 60px rgba(0,0,0,.30),var(--ring-card);overflow:hidden}
.pal-top{display:flex;align-items:center;gap:9px;padding:12px 14px;
  border-bottom:1px solid var(--hairline)}
.pal-ic{display:flex;color:var(--stone)}
.pal-input{flex:1;min-width:0;background:none;border:0;outline:none;
  font-family:var(--sans);font-size:15px;color:var(--obsidian)}
.pal-input::placeholder{color:var(--stone)}
.pal-list{max-height:min(52vh,420px);overflow-y:auto;padding:6px}
.pal-row{display:flex;align-items:center;gap:10px;width:100%;box-sizing:border-box;
  appearance:none;background:none;border:0;cursor:pointer;text-align:left;
  font-family:var(--sans);font-size:13.5px;color:var(--obsidian);
  padding:8px 10px;border-radius:var(--radius)}
.pal-row .ic{color:var(--stone)}
/* Highlighted by keyboard AND hover — the two must look identical or arrowing
   down and then moving the mouse shows two selections at once. */
.pal-row.is-sel,.pal-row:hover{background:var(--hairline)}
.pal-row.is-sel .ic{color:var(--obsidian)}
.pal-kind{margin-left:auto;font-family:var(--sans);font-weight:600;font-size:9.5px;letter-spacing:.06em;
  text-transform:uppercase;color:var(--stone);flex:0 0 auto}
.pal-sub{color:var(--stone);font-size:12px;margin-left:6px;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pal-none{padding:18px 14px;color:var(--stone);font-size:13px}

/* ── Overview, two columns ────────────────────────────────────────────────
   Narrow left for what the account is doing, wide right for what you built. */
.dash-split{display:grid;grid-template-columns:300px minmax(0,1fr);gap:24px;align-items:start}
.dash-col{display:flex;flex-direction:column;gap:12px;min-width:0}
.dash-h2-first{margin-top:0}
@media(max-width:1000px){.dash-split{grid-template-columns:minmax(0,1fr)}}

/* ── Tables ───────────────────────────────────────────────────────────────
   The OpenRouter transactions pattern: hairline rows, numerals in mono and
   tabular so columns line up down the page. Wrapped in its own scroller so a
   wide table never widens the page — the failure mode twice already. */
.tbl-wrap{overflow-x:auto;-webkit-overflow-scrolling:touch}
.tbl{width:100%;border-collapse:collapse;font-size:13px}
.tbl th{text-align:left;font-family:var(--sans);font-weight:600;font-size:9.5px;letter-spacing:.07em;
  text-transform:uppercase;color:var(--stone);font-weight:600;
  padding:0 14px 8px 0;white-space:nowrap}
.tbl td{padding:11px 14px 11px 0;border-top:1px solid var(--hairline);
  color:var(--charcoal);vertical-align:top}
.tbl tr:hover td{color:var(--obsidian)}
.tbl .t-main{color:var(--obsidian);max-width:34rem}
.tbl .t-num{font-family:var(--mono);font-variant-numeric:tabular-nums;
  white-space:nowrap;color:var(--obsidian)}
.tbl .t-dim{font-family:var(--sans);font-weight:600;font-size:11px;color:var(--stone);white-space:nowrap}
.tbl-none{color:var(--stone);font-size:13px;padding:14px 0}

/* ── Nothing may be sized by its widest child ─────────────────────────────
   A table of six nowrap columns has a min-content width of ~620px. Every
   ancestor between it and the viewport has to be told it may be narrower than
   that, or the table's intrinsic width propagates up and the page scrolls
   sideways instead of the table scrolling inside its own wrapper.

   min-width:0 alone is not enough here: these are block ancestors, where the
   fix is an explicit max-width rather than a flex/grid minimum. */
.main,.dash,.dash-pane,.dsec{max-width:100%;min-width:0}
.tbl-wrap{max-width:100%}

/* ── The model suggestion above the composer ──────────────────────────────
   Quiet on purpose: it sits between the transcript and the box people are
   about to type in, and anything loud there reads as an error. */
.rec{display:flex;align-items:center;gap:10px;min-width:0;
  background:var(--hairline);border-radius:var(--radius);padding:10px 12px;margin:0 0 8px}
.rec-body{flex:1 1 auto;min-width:0}
.rec-line{margin:0;font-size:13px;line-height:1.45;color:var(--carbon)}
.rec-job{color:var(--stone)}
.rec-why{margin:3px 0 0;font-size:12px;line-height:1.4;color:var(--stone)}
.rec-go{flex:none;appearance:none;cursor:pointer;font:inherit;font-size:12px;font-weight:700;
  border:0;border-radius:999px;padding:6px 12px;background:var(--obsidian);color:var(--paper)}
.rec-x{flex:none;appearance:none;cursor:pointer;background:none;border:0;font-size:13px;
  line-height:1;color:var(--stone);padding:4px}
.rec-x:hover{color:var(--carbon)}
@media (max-width:520px){
  /* Stack rather than squeeze — three items on one line at 390px pushed the
     button to two characters wide. */
  .rec{flex-wrap:wrap}
  .rec-body{flex:1 0 100%}
  /* And clamp the reasoning. Stacked and unclamped this row measured 170px on
     a 390px viewport — a third of the space above the composer, spent on an
     aside. Two lines is enough to decide with; the rest is in the model sheet. */
  .rec-why{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}}

/* ── Attachments ──────────────────────────────────────────────────────────
   A row that scrolls sideways rather than wrapping into the transcript: it
   sits directly above the composer and must not push it off screen. */
.atts{display:flex;gap:8px;overflow-x:auto;padding:0 0 8px;scrollbar-width:none}
.atts::-webkit-scrollbar{display:none}
.att{display:inline-flex;align-items:center;gap:7px;flex:none;max-width:200px;
  background:var(--hairline);border-radius:999px;padding:5px 9px 5px 7px;font-size:12px}
.att-thumb{width:20px;height:20px;border-radius:4px;object-fit:cover;flex:none}
.att-ico{display:grid;place-items:center;color:var(--stone);flex:none}
.att-n{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--carbon)}
.att-x{appearance:none;background:none;border:0;cursor:pointer;color:var(--stone);
  font-size:11px;line-height:1;padding:2px;flex:none}
.att-x:hover{color:var(--carbon)}
/* Held, not yet on a machine — a hairline ring so "it's attached" reads before
   the build has even started, without shouting about the plumbing. */
.att-pend{box-shadow:inset 0 0 0 1px var(--hairline)}
.attach{appearance:none;flex:none;align-self:flex-end;cursor:pointer;background:none;border:0;
  color:var(--stone);font-size:21px;line-height:1;padding:0 2px 6px 4px}
.attach:hover{color:var(--carbon)}
.attach:disabled{opacity:.3;cursor:default}

/* Composer-side notes about what an attachment is about to do: "this skips
   planning" before send, "attached what you pasted" right after a paste.
   Same quiet register as .plan-note, just anchored above the composer
   instead of in the transcript. */
.atts-hint{font-family:var(--sans);font-weight:600;font-size:12px;line-height:1.5;color:var(--stone);
  margin:0 0 6px;padding-left:2px}

/* What the machine reports back after unpacking a dropped zip — read under
   the message that sent it, once the upload response says so. */
.att-unpacked{font-family:var(--sans);font-weight:600;font-size:12px;line-height:1.5;color:var(--stone);
  margin:6px 0 0}

/* ── Say it instead of typing it ──────────────────────────────────────────
   Three states in one button, because they are one control: idle is a mic,
   recording is a stop square with a running clock, and busy is the clock
   alone. The clock matters more than it looks — not knowing whether a
   microphone is hearing you is the single commonest way a voice UI feels
   broken, so there is always a number moving while it listens. */
.mic{appearance:none;flex:none;align-self:flex-end;cursor:pointer;background:none;border:0;
  color:var(--stone);padding:0 4px 5px;display:flex;align-items:center;gap:6px;
  font:600 12px/1 var(--mono);letter-spacing:.02em}
.mic:hover{color:var(--carbon)}
.mic:disabled{opacity:.5;cursor:default}
.mic-i{width:19px;height:19px;display:block}
.mic-stop{display:none;width:11px;height:11px;border-radius:2px;background:currentColor}
.mic-time{display:none}
/* Recording: the mic becomes the stop control it now is, and the whole button
   takes the one accent colour used for things that are actively happening. */
.mic.is-rec{color:var(--brand-ink)}
.mic.is-rec .mic-i{display:none}
.mic.is-rec .mic-stop,
.mic.is-rec .mic-time{display:block}
.mic.is-busy .mic-i{opacity:.45}
.mic.is-busy .mic-time{display:block}
/* Near the two-minute ceiling, so running out is something you saw coming
   rather than something that happened to you. Weight, not a hue: this system
   has no warning colour on purpose — the one amber it had was retired for
   colliding with another meaning. */
.mic.is-near{color:var(--obsidian)}
.mic.is-near .mic-time{font-weight:700}
/* Live: the browser's own recogniser is streaming words into the box as they
   are said. It is the same stop control — pressing it again is still how you
   finish — with a halo that breathes, because on this path there is no upload
   and no receipt at the end, so the ONLY evidence it is working is the words
   appearing and this. A ring rather than another colour: the palette has one
   accent for things actively happening and this is that same thing. */
.mic.is-live .mic-stop{box-shadow:0 0 0 0 currentColor}
@media (prefers-reduced-motion:no-preference){
  .mic.is-rec .mic-stop{animation:micpulse 1.4s ease-in-out infinite}
  .mic.is-live .mic-stop{animation:miclisten 1.6s ease-out infinite}
}
@keyframes micpulse{50%{opacity:.45}}
@keyframes miclisten{
  0%{box-shadow:0 0 0 0 currentColor;opacity:1}
  70%{box-shadow:0 0 0 7px transparent;opacity:.8}
  100%{box-shadow:0 0 0 0 transparent;opacity:1}
}

/* ── Email sign-in ────────────────────────────────────────────────────────
   Sits under the Google button as the second option, not a fallback: plenty
   of people do not have a Google account, and plenty who do would rather not
   attach it to a new service. */
/* A picture the model drew, under the words that introduce it. Capped by
   height rather than width so a tall portrait and a wide banner both sit in a
   chat column without one of them filling the screen. */
.msg-imgs{display:flex;flex-wrap:wrap;gap:8px;margin-top:10px}
.msg-imgs a{display:block;line-height:0;border-radius:12px;overflow:hidden;
  box-shadow:var(--ring-card)}
.msg-imgs img{display:block;max-width:100%;max-height:340px;width:auto;height:auto}
/* One line per thing that is using space: what it is, how much, and — for the
   ones somebody can act on — the way to get rid of it. */
.stable{display:flex;flex-direction:column;gap:2px;margin:14px 0 22px}
.srow{display:grid;grid-template-columns:1fr auto auto;align-items:baseline;gap:10px;
  padding:10px 12px;border-radius:10px;background:var(--white);box-shadow:var(--ring-card)}
.srow-n{font-weight:600}
.srow-b{font-variant-numeric:tabular-nums;color:var(--stone)}
.srow-d{grid-column:1/-1;color:var(--stone);font-size:12px}
@media(max-width:520px){ .srow{grid-template-columns:1fr auto} }
.authsep{display:flex;align-items:center;gap:12px;max-width:260px;margin:20px auto 16px;
  color:var(--stone);font-size:12px}
.authsep::before,.authsep::after{content:"";flex:1;height:1px;background:var(--hairline)}
.authmail{display:flex;flex-wrap:wrap;justify-content:center;gap:8px;max-width:340px;
  margin:0 auto;min-width:0}
.authmail input{flex:1 1 140px;min-width:0;font:inherit;font-size:15px;
  background:var(--white);color:var(--obsidian);border:0;box-shadow:var(--ring-card);
  border-radius:999px;padding:11px 16px}
.authmail input:focus-visible{outline:none;box-shadow:var(--ring-card),0 0 0 4px var(--focus-halo)}
.authmail .btn{flex:none;white-space:nowrap}
/* The bot check is `interaction-only`, so most of the time it renders nothing.
   When Cloudflare does decide to ask, it paints a 300px checkbox — and as a
   flex sibling with no rule of its own it took the whole 340px row and squeezed
   the email field down to a 32px sliver with no visible placeholder. That is
   the FIRST screen of the product for anyone who does not use Google: they
   could see "Email me a link" and had nowhere to type an address. Give the
   widget its own line instead of a share of the row, and let it take up no
   space at all on the runs where it draws nothing. */
.authmail .cf-turnstile{flex:1 0 100%;display:flex;justify-content:center;margin-top:4px}
/* NOT display:none. botcheck.js renders this widget the first time an
   IntersectionObserver says the container is on screen, and an element
   with display:none has no box to intersect anything — so hiding it
   while empty is a deadlock: it stays empty because it is hidden, and
   it stays hidden because it is empty. The bot check rendered zero
   times on 5 of 5 production loads. A 1px box collapses the gap just as
   well and is still a box. */
.authmail .cf-turnstile:empty{margin:0;min-height:1px}
.authmail-msg{max-width:340px;margin:12px auto 0;font-size:13px;line-height:1.5;
  color:var(--stone);text-align:center}
.authmail-msg.is-bad{color:var(--alarm)}
@media (max-width:420px){
  /* Stack, rather than squeeze the button down to two characters. */
  .authmail{flex-wrap:wrap}
  .authmail input,.authmail .btn{flex:1 0 100%}}

/* Reachable by a screen reader, invisible to everyone else. Only for labels
   whose text appears nowhere on the page — a duplicate of visible copy here
   shows up twice in content audits. */
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
  clip:rect(0 0 0 0);white-space:nowrap;border:0}

/* ── Connected accounts ───────────────────────────────────────────────────
   One row per account, because a person can have a personal GitHub and a work
   one and needs to see — and remove — the right one. */
.dconn-accts{display:flex;flex-direction:column;gap:6px;margin:0 0 12px}
.dconn-acct{display:flex;align-items:center;gap:10px;min-width:0;
  background:var(--hairline);border-radius:var(--radius);padding:7px 9px 7px 11px}
.dconn-acct-n{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;
  white-space:nowrap;font-size:13px;color:var(--carbon)}
/* What that login actually reaches. Wraps rather than truncates: an
   organisation name cut in half is worse than two lines. */
.dconn-owners{margin:-6px 0 12px;font-size:12px;line-height:1.5;color:var(--stone);
  overflow-wrap:anywhere}
.dconn-owners:empty{display:none}
.dconn-x{flex:none;appearance:none;background:none;border:0;cursor:pointer;
  font:inherit;font-size:12px;color:var(--stone);padding:2px 4px}
.dconn-x:hover{color:var(--alarm)}

/* The records to add. Monospace and selectable — these get copied by hand into
   a registrar, so they must be unambiguous and easy to grab. */
.domrecs{display:flex;flex-direction:column;gap:10px;margin:14px 0}
.domrec{display:grid;grid-template-columns:auto minmax(0,1fr);gap:4px 10px;
  background:var(--hairline);border-radius:var(--radius);padding:10px 12px}
.domrec-t{font-size:11px;font-weight:700;letter-spacing:.06em;color:var(--stone);
  align-self:center}
.domrec-n,.domrec-v{font-family:var(--mono);font-size:12px;color:var(--carbon);
  word-break:break-all;user-select:all}
.domrec-n{font-weight:600}
/* The value gets the full width: these are long, and wrapping one inside the
   narrow first column makes a record that has to be copied exactly harder to
   read than it needs to be. */
.domrec-v{grid-column:1/-1}
/* Its own row, left-aligned under the value it copies. Floating it beside the
   value would put a button in a column sized by a string that wraps to three
   lines on a phone. */
.domrec-c{grid-column:1/-1;justify-self:start;margin-top:2px}
.domrec-w{grid-column:1/-1;font-size:11px;line-height:1.5;color:var(--stone)}

/* The four clicks that make a Cloudflare token. Numbered because they are done
   in another tab, in order, and a person coming back to this sheet needs to see
   where they were. */
.domsteps{margin:10px 0 12px;padding-left:20px;line-height:1.7}
.domsteps li{margin:2px 0}

/* Said once, while the questions are happening: nothing has started and
   nothing is being charged. Quiet, because it is reassurance rather than an
   instruction — but present, because being asked four questions by something
   that bills by the minute is a fair thing to worry about. */
.plan-note{font-family:var(--sans);font-weight:600;font-size:12px;line-height:1.6;color:var(--stone);
  margin:2px 0 10px;padding-left:2px}

/* ── Rankings, on the Models tab ────────────────────────────────────────────
   Self-contained, with its own `rk-` prefix.

   The first version reused `.dash-h3` and a grid of fixed columns. Two things
   went wrong and both are worth stating, because they are the same mistake at
   different scales.

   1. `.dash-h3` was a NEW name in an established `.dash-*` family, and the
      heading rendered clipped mid-letter — "Used most this week" came out as
      "Jsed most this". Rather than hunt the rule it was colliding with, this
      block now owns every class it uses. A name nothing else can match cannot
      be overridden by something else.

   2. The grid had fixed columns with a `@media (max-width: 34rem)` escape.
      That keys off the VIEWPORT, and this lives in a content pane beside a
      sidebar — so on a wide desktop the query never fired while the pane was
      narrow enough to overflow. A media query cannot see a container. Flexbox
      can: everything that may shrink has min-width:0, the name ellipsizes, the
      numbers keep their natural width, and there is nothing left to overflow at
      any pane width. */
.rk { min-width: 0; }
.rk-h {
  font-size: 1.05rem;
  font-weight: 600;
  margin: 1.8rem 0 .3rem;
  padding: 0;
  line-height: 1.3;
  letter-spacing: 0;
}
.rk-sub { margin: 0 0 .9rem; font-size: .85rem; opacity: .62; max-width: 44rem; }
/* Sits under the board, aligned with the rows rather than centred on the pane:
   it belongs to the list above it, not to the section. */
.rk-more { margin: .7rem 0 0; }

/* Credits, folded away.
 *
 * The licences on this data require the author be named wherever the numbers
 * are shown. They do not require it be the loudest thing under the chart, and
 * they say nothing about naming the route the data travelled here — that is a
 * vendor relationship of ours rather than the reader's business.
 *
 * One disclosure covering both boards. Closed it is a two-word line; open it is
 * every credit in full, on the same screen as the numbers. */
/* A question that has to be answered before anything can continue. Same
   scrim as the previewer, because it is the same kind of interruption. */
/* Where builds run. A setting, not a dialog: it is asked in Account and never
   on the path where somebody has just pressed send. */
/* ── Ask AI ───────────────────────────────────────────────────────────── */
/* A grid, not a column of six full-width bars. minmax(0,…) rather than a bare
   fraction — a bare 1fr is min-content-floored and a long question then pushes
   the whole page sideways, which is the third time that default has bitten
   this stylesheet. */
/* ── Chat sessions ─────────────────────────────────────────────────────────
   Replaced the .ask-* rules, which styled a screen that no longer exists. A
   row is one button rather than a card: the list is read down the titles, and
   card chrome per row turns nine conversations into nine boxes to look past. */
.sess-head{display:flex;align-items:center;gap:14px;flex-wrap:wrap;margin:18px 0 6px}
.sess-where{margin:0;font-size:13px;color:var(--stone);min-width:0}
.sess-list{display:flex;flex-direction:column;margin:10px 0 0}
.sess-row{appearance:none;display:flex;align-items:center;gap:12px;width:100%;
  text-align:left;cursor:pointer;font:inherit;min-width:0;
  background:none;border:0;border-bottom:1px solid var(--hairline);
  padding:13px 6px;color:var(--obsidian);
  transition:background .12s ease}
.sess-row:last-child{border-bottom:0}
.sess-row:hover{background:var(--hairline)}
.sess-main{display:flex;flex-direction:column;gap:3px;min-width:0}
/* The title is the only thing scanned, so it gets the one line and the
   ellipsis. Without min-width:0 on both this and .sess-main the flex child
   refuses to shrink and a long first message widens the whole page. */
.sess-title{font-size:15px;line-height:1.35;min-width:0;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.sess-meta{font-size:12.5px;color:var(--stone);min-width:0}

.wrun{margin-top:26px}
.wrun-row{display:flex;align-items:flex-start;gap:10px;cursor:pointer;
  padding:12px 0;border-top:1px solid var(--hairline)}
.wrun-row input{margin-top:3px;flex:0 0 auto}
.wrun-t{display:block;font-size:14px;color:var(--obsidian)}
.wrun-t strong{font-weight:600}
.wrun-s{display:block;margin-top:2px;font-family:var(--sans);font-weight:600;font-size:10.5px;color:var(--stone)}

/* Two cards, one choice.
   Drawn as cards rather than left as bare radios because this is a decision
   about money, and a decision about money in a stack of form rows reads as
   something already settled. The whole card is the hit area — the radio is
   3mm across and this gets used on a laptop trackpad. */
.wrun-card{display:flex;align-items:flex-start;gap:10px;cursor:pointer;
  padding:12px 14px;margin-top:10px;border:1px solid var(--hairline);
  border-radius:10px;background:var(--white)}
.wrun-card:hover{border-color:var(--ash)}
.wrun-card:has(input:checked){border-color:var(--obsidian)}
.wrun-card input{margin-top:3px;flex:0 0 auto}
.wrun-body{display:block;min-width:0}
/* The cost of each option, on the option. The two prices are the entire
   difference between these cards and neither of them was on screen. */
.wrun-tag{font-family:var(--sans);font-weight:600;font-size:10px;color:var(--stone);
  margin-left:6px;white-space:nowrap}
/* What happens if nobody touches anything. */
.wrun-def{font-family:var(--sans);font-weight:600;font-size:9.5px;text-transform:uppercase;
  letter-spacing:.06em;color:var(--white);background:var(--obsidian);
  border-radius:999px;padding:1px 6px;margin-left:6px;vertical-align:1px}
.wrun-list{display:block;margin-top:3px;font-family:var(--sans);font-weight:600;font-size:10.5px;
  color:var(--graphite)}

.rk-src { margin: 1.4rem 0 0; }
.rk-src > summary {
  cursor: pointer; list-style: none;
  font-size: .72rem; opacity: .4;
  font-family: var(--mono, monospace);
  padding: .2rem 0; width: max-content;
}
.rk-src > summary::-webkit-details-marker { display: none; }
.rk-src > summary::before { content: "\25b8  "; }
.rk-src[open] > summary::before { content: "\25be  "; }
.rk-src > summary:hover { opacity: .7; }
.rk-src > summary:focus-visible { outline: 2px solid var(--obsidian); outline-offset: 2px; }
.rk-src .rk-cite:first-of-type { margin-top: .3rem; }

.rk-cite { margin: 1rem 0 0; font-size: .72rem; opacity: .45; font-family: var(--mono, monospace); }

.rk-row {
  display: flex;
  align-items: center;
  gap: .7rem;
  padding: .62rem 0;
  min-width: 0;
  border-bottom: 1px solid var(--hairline);
}
.rk-n { flex: 0 0 auto; min-width: 1.1rem; font-size: .78rem; opacity: .45;
  font-variant-numeric: tabular-nums; }
/* The one element allowed to give up space, so nothing else has to. */
.rk-l { flex: 1 1 auto; min-width: 0; font-size: .93rem;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rk-bar { flex: 0 1 6rem; min-width: 0; height: 6px; border-radius: 999px;
  background: var(--hairline); overflow: hidden; }
.rk-bar i { display: block; height: 100%; border-radius: 999px;
  background: currentColor; opacity: .5; }
.rk-sparkwrap { flex: 0 0 auto; display: flex; align-items: center; opacity: .85; }
.rk-spark { display: block; }
.rk-p { flex: 0 0 auto; min-width: 3.2rem; text-align: right;
  font-size: .95rem; font-weight: 650; font-variant-numeric: tabular-nums; }
.rk-d { flex: 0 0 auto; min-width: 2.8rem; text-align: right;
  font-size: .78rem; opacity: .5; font-variant-numeric: tabular-nums; }
/* Direction, in weight rather than hue. These were #4a9d5f and #c4674a — a
   green and a red frozen to one theme, and the green sat a few hundred pixels
   from the green that means "online". The row already prints ▲ or ▼ (the
   comment in app.js says why: colour alone is not available to everyone), so
   the hue was carrying nothing the glyph was not already carrying. Rising gets
   full ink, falling gets muted, flat stays as it was. */
.rk-d.is-up { color: var(--obsidian); opacity: 1; }
.rk-d.is-down { color: var(--stone); opacity: 1; }

/* One board, full width.
 *
 * This was an auto-fit grid of task cards, and it outlived them: with a single
 * ranked board inside it, auto-fit put the board in the first column and left
 * the second for whatever came next — so at 1280px the rows used half the pane
 * and the legend floated in the empty half beside them. A container that still
 * describes a layout two designs ago. */
.rk-tasks { margin-top: .2rem; min-width: 0; }
.rk-task { border: 1px solid var(--hairline);
  border-radius: 12px; padding: .8rem .9rem; min-width: 0; }
.rk-task-h { display: flex; align-items: baseline; gap: .6rem; min-width: 0; }
.rk-task-l { flex: 1 1 auto; min-width: 0; font-size: .92rem; font-weight: 550;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rk-task-p { flex: 0 0 auto; font-size: .88rem; font-weight: 650; opacity: .8;
  font-variant-numeric: tabular-nums; }
.rk-bar-wide { flex: none; display: block; width: 100%; margin-top: .55rem; }
.rk-task-who { margin: .55rem 0 0; font-size: .76rem; opacity: .5;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ── Vendor colour, the macro bar, and the task leaders ─────────────────────
   Colour does the scanning. A ranked list of names and percentages is readable
   but not scannable — you have to read every row to notice that one company
   owns most of the code work. A colour per vendor makes the shape of the
   market visible before a word has been read.

   Carried on a dot and a bar, never on the text: colouring the label itself is
   what makes a chart pretty and hard to read. And every colour is paired with
   something non-colour — an arrow on the delta, a percentage on every segment
   — because colour alone is not available to every reader. */
.rk-dot { flex: 0 0 auto; width: 8px; height: 8px; border-radius: 999px; }
.rk-namewrap { flex: 1 1 auto; min-width: 0; display: flex; align-items: baseline; gap: .45rem; }
.rk-vendor { flex: 0 0 auto; font-size: .68rem; opacity: .38; text-transform: lowercase;
  letter-spacing: .02em; white-space: nowrap; }

/* The macro bar: where all the work goes, in one row. */
.rk-macrowrap { margin: 1.1rem 0 .2rem; }
.rk-macro { display: flex; width: 100%; height: 10px; border-radius: 999px;
  overflow: hidden; background: var(--hairline); }
.rk-macroseg { display: block; height: 100%; }
.rk-macrokey { display: flex; flex-wrap: wrap; gap: .45rem 1rem; margin-top: .5rem; }
.rk-macroitem { display: inline-flex; align-items: center; gap: .35rem;
  font-size: .78rem; opacity: .72; }
.rk-macroitem i { width: 8px; height: 8px; border-radius: 2px; display: block; }

/* Task cards: a colour RAIL, not a coloured card, so the text keeps full
   contrast on either ground. */
.rk-task { border-left: 3px solid var(--slate); }
.rk-task-win { display: flex; align-items: center; gap: .45rem; margin-top: .6rem; min-width: 0; }
.rk-task-win i { flex: 0 0 auto; width: 7px; height: 7px; border-radius: 999px; display: block; }
.rk-task-who1 { flex: 1 1 auto; min-width: 0; font-size: .86rem; font-weight: 550;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rk-task-w1p { flex: 0 0 auto; font-size: .82rem; opacity: .7;
  font-variant-numeric: tabular-nums; }
.rk-task-rest { margin: .4rem 0 0; font-size: .74rem; opacity: .45;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ── One row shape for every leaderboard on this screen ─────────────────────
 *
 * Two sections rank models and they are asking different questions — "best at
 * coding", from tests somebody ran, and "most used for debugging", from
 * traffic — but the row is the same four facts every time: where it placed,
 * what it is, how far along the scale, and the number. One shape, so the eye
 * does not have to relearn the layout halfway down the pane, and one place to
 * fix when it breaks.
 *
 * ── What this replaces, twice ───────────────────────────────────────────────
 * First a treemap: nine categories, the smallest 1.3% of the whole, in a pane a
 * few hundred pixels wide. No threshold or fitting pass makes that hold its
 * labels — live it rendered "Classificati…", cut "Q&A & Knowledge" in half, and
 * dropped the leading model, which was the point of the section, from six rows
 * out of nine. Then a ranked list that showed nine winners and never a second
 * place, which cannot answer "what else should I consider".
 *
 * A rank, a name and a bar on a shared baseline answers both, fits every label
 * at every width, and needs no measurement code at all. */
.rk-board { display: flex; flex-direction: column; margin: .3rem 0 .6rem; }
/* The explanation that now leads the models screen. */
.how .how-why{margin:0 0 14px;padding:0 0 0 18px;font-size:13.5px;line-height:1.5;color:var(--charcoal)}
.how .how-why li{margin:0 0 5px}
.how .how-why b{font-weight:600;color:var(--obsidian)}
.how .tune-lbl{margin-top:4px}
/* Four cards in a row where there is room, a column on a phone. The sheet
   under the composer keeps its column; this is a page, not a sheet. */
.how-postures{margin-bottom:8px;display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:8px}
.how-postures .tune-posture{height:100%;box-sizing:border-box}
.rk-fine{font-size:12.5px;color:var(--stone);margin:0 0 4px}
.rk-brow {
  display: grid;
  grid-template-columns: 1.4rem 3px minmax(6rem, 1fr) minmax(3rem, 7rem) 3.6rem 4.6rem;
  align-items: center;
  gap: .7rem;
  padding: .42rem 0;
  border-bottom: 1px solid var(--hairline);
  min-width: 0;
}
.rk-brow:last-child { border-bottom: 0; }
/* The place, stated. A ranked list whose rows are not numbered makes the reader
   count, and they will only count as far as they care about. */
.rk-bn { font-size: .78rem; text-align: right; opacity: .5;
  font-variant-numeric: tabular-nums; }
/* Colour as a rail, not a fill: the text keeps full contrast on either theme,
   which a coloured cell with text on it never quite does. */
.rk-brail { display: block; width: 3px; height: 1.15rem; border-radius: 2px; }
.rk-bl { font-size: .88rem; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rk-bbar { display: block; height: 6px; border-radius: 999px;
  background: var(--hairline); overflow: hidden; min-width: 0; }
.rk-bbar i { display: block; height: 100%; border-radius: 999px; }
.rk-bv { font-size: .82rem; text-align: right;
  font-variant-numeric: tabular-nums; color: var(--charcoal); }
/* What a turn costs, beside the score. Richy, 2026-09-02: "it should also show
   price so that people get an idea". A number and a hair of a bar under it —
   the bar is on a linear scale on purpose, so a model a hundred times cheaper
   shows as a hundredth of the width, which is the fact. */
.rk-bp { font-size: .74rem; text-align: right; white-space: nowrap;
  font-variant-numeric: tabular-nums; color: var(--stone); line-height: 1.2; }
.rk-bp i { display: block; height: 3px; border-radius: 999px; margin: 3px 0 0 auto;
  background: var(--stone); opacity: .55; }
.rk-brow:hover .rk-bp { color: var(--charcoal); }

/* Under a narrow pane it becomes two lines rather than five squeezed columns.
 *
 * Every child is placed EXPLICITLY. Moving only two of them and letting the
 * other three auto-place put the number into the 3px rail column — 37px of text
 * in 3px, on every row, clipped exactly the way the treemap used to clip.
 * Auto-placement after a partial override is how you get a layout that is right
 * in the two places you looked at.
 *
 *    1 [rail] Claude Opus 5 ............. 78.0
 *      [rail]                   [=========]
 */
/* 40rem, from 30: the price column took the room the longest names needed
   in a 560px pane ("Nemotron 3 Ultra 550b Instruct" — check-leaders), and a
   two-line row is the honest answer well before a phone. */
@media (max-width: 40rem) {
  .rk-brow {
    grid-template-columns: 1.4rem 3px minmax(0, 1fr) auto auto;
    row-gap: .22rem;
    column-gap: .5rem;
  }
  .rk-bn    { grid-area: 1 / 1 / 3 / 2; align-self: center; }
  .rk-brail { grid-area: 1 / 2 / 3 / 3; height: 100%; }
  .rk-bl    { grid-area: 1 / 3 / 2 / 4; }
  .rk-bv    { grid-area: 1 / 4 / 2 / 5; }
  .rk-bp    { grid-area: 1 / 5 / 2 / 6; }
  .rk-bbar  { grid-area: 2 / 3 / 3 / 6; }
}

/* Two columns where there is room, one where there is not — auto-fit, so no
   breakpoint has to guess the pane width. */
.rk-cols { display: grid; gap: 0 1.6rem;
  grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr)); }
.rk-col { min-width: 0; }

/* ── The marketing stylesheet's section rhythm does not belong in the app ────
   paper.css sets `section{padding:96px 0}` and, above 900px,
   `section{padding:128px 0}`. That is the landing page's vertical rhythm, and
   it is right there — but this app loads paper.css too, and #rankBlock is a
   bare <section>, so the dashboard inherited 128px of blank above its first
   heading and 128px below.

   Measured on production before this rule: 344px of vertical padding across the
   two visible sections of the Explore tab at 1440px, and a 157px void between
   the tab row and "Used most this week". It tracked the 900px breakpoint
   exactly — 128px above it, 96px below — which is how the rule was identified.

   Same shape as the earlier .dash-h3 collision: an app and a marketing site
   sharing one document, where a bare ELEMENT selector in one silently restyles
   the other. Scoped by the app shell rather than by !important, so the landing
   pages keep their rhythm. */
.shell section { padding: 0; }

/* ── paper.css styles every <nav> as the marketing site's top bar ────────────
   paper.css:230
     nav{...height:64px;position:sticky;top:0;z-index:20;
         background:var(--nav-bg);backdrop-filter:blur(20px);
         margin:0 -24px;padding:0 24px}

   That is right for the landing page's header and wrong for this app's
   sidebar, which is also a <nav>. Three separate consequences, all measured on
   production:

     * `margin:0 -24px` pushed the sidebar's box 11px INTO the content pane at
       every width from 950px to 1440px.
     * `z-index:20` plus an opaque `background` and a backdrop blur meant it
       PAINTED over that 11px — covering the first glyph of every line of body
       text. "Used most this week" rendered as "Jsed most this week" and
       "Share of every token" as "hare of every token". Reported as a UI bug
       twice; it survived because the DOM is perfectly healthy — correct left
       edge, no transform, no overflow, no animation — so every layout
       assertion passes. Only elementFromPoint at the text's first pixel finds
       it, by returning NAV instead of the heading.
     * `position:sticky` made the whole sidebar sticky as a side effect.

   Third instance of this family in this codebase, after `.dash-h3` colliding
   with the dash-* namespace and `section{padding:128px 0}`. The cause is
   structural: an app and a marketing site share one document, and paper.css
   sets bare ELEMENT selectors. The ones that reach into the shell are
   a, h1, h2, h3, main, nav, p, section. */
.shell nav {
  height: auto;
  position: static;
  z-index: auto;
  background: none;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  margin: 0;
  padding: 0;
}

/* ── What we recommend ───────────────────────────────────────────────────────
   Ours, so it leads the pane. Deliberately quieter than the treemap below it:
   this is a list somebody READS one row at a time to make a decision, and the
   charts underneath are for scanning. Colour would compete with them for the
   same attention and win nothing. */
.rec-controls { display: flex; flex-direction: column; gap: 6px; margin: .7rem 0 .2rem; }
/* Each row scrolls on its own rather than wrapping: a wrapped pill row grows
   the header downward on the screen with the least room, which is the bug this
   app already fixed once on the phone. */
.rec-pills { display: flex; gap: 6px; overflow-x: auto; padding-bottom: 2px; scrollbar-width: none; }
.rec-pills::-webkit-scrollbar { display: none; }
.rec-pill {
  flex: 0 0 auto; border: 1px solid var(--hairline);
  background: transparent; color: inherit; opacity: .72;
  border-radius: var(--radius-pill, 999px); padding: .3rem .7rem;
  font-size: .8rem; font-weight: 600; cursor: pointer; white-space: nowrap;
}
/* --obsidian/--paper, the tokens this stylesheet actually defines and flips per
   theme. The first version invented --ink/--bg, which exist nowhere, so both
   fell back: background:currentColor resolved against the color set on the SAME
   rule, and the pill rendered white text on a white pill. It passed every DOM
   assertion, because the text was there — only the screenshot showed it. */
.rec-pill.is-on { background: var(--obsidian); color: var(--paper); opacity: 1; border-color: var(--obsidian); }
.rec-job { margin: 1.1rem 0 .1rem; font-size: .7rem; font-weight: 700;
  letter-spacing: .08em; opacity: .45; }
.rec-note { margin: 0 0 .5rem; font-size: .8rem; opacity: .55; }
.rec-card {
  border: 1px solid var(--hairline);
  border-radius: 12px; padding: .7rem .85rem; margin-bottom: .5rem; min-width: 0;
}
/* Too few runs to rank on. Dimmed rather than hidden, so a job with one strong
   model does not look like a job with one option — and never mistakable for a
   recommendation. */
.rec-card.is-thin { opacity: .5; }
.rec-top { display: flex; align-items: center; gap: .6rem; min-width: 0; }
.rec-name { flex: 1 1 auto; min-width: 0; font-size: .95rem; font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rec-badge {
  flex: 0 0 auto; background: var(--obsidian); color: var(--paper);
  border-radius: var(--radius-pill, 999px); padding: .15rem .5rem;
  font-size: .68rem; font-weight: 700; white-space: nowrap;
}
.rec-why { margin: .35rem 0 0; font-size: .84rem; opacity: .72; line-height: 1.45; }

/* ── Your data ──────────────────────────────────────────────────────────────
   The panel behind the Data button on a project. Built to answer one question
   — "is anything actually being saved" — so the row count is the loudest thing
   in it and an empty table gets a sentence rather than an empty box. */
.data-t{display:flex;align-items:baseline;justify-content:space-between;gap:12px;
  width:100%;font:inherit;text-align:left;background:var(--paper);
  border:1px solid var(--hairline);border-radius:10px;padding:11px 14px;margin-bottom:8px;
  cursor:pointer;min-height:44px}
.data-t:hover{border-color:var(--stone)}
.data-t-n{font-family:var(--mono);font-size:13px;color:var(--charcoal)}
.data-t-c{font-size:12.5px;color:var(--stone)}
.data-head{display:flex;align-items:center;justify-content:space-between;gap:14px;
  flex-wrap:wrap;margin-bottom:10px}
.data-h-n{font-family:var(--sans);font-weight:600;font-size:12.5px;color:var(--charcoal);margin:0}
/* The table scrolls inside its own box. A form with eight columns must not make
   the whole sheet scroll sideways. */
.data-scroll{overflow-x:auto;border:1px solid var(--hairline);border-radius:10px;
  background:var(--paper)}
.data-tbl{border-collapse:collapse;width:100%;min-width:420px}
.data-tbl th,.data-tbl td{text-align:left;padding:9px 13px;border-bottom:1px solid var(--hairline);
  font-size:13.5px;line-height:1.45;vertical-align:top;
  /* Long messages wrap instead of forcing a horizontal scroll nobody wants. */
  max-width:34ch;overflow-wrap:anywhere}
.data-tbl thead th{font-family:var(--sans);font-weight:600;font-size:10.5px;letter-spacing:.07em;
  text-transform:uppercase;color:var(--stone);font-weight:600;white-space:nowrap}
.data-tbl tbody tr:last-child td{border-bottom:0}
.data-when{color:var(--stone);white-space:nowrap;font-size:13px}
.data-nil{color:var(--smoke)}

/* ── What you have shipped ──────────────────────────────────────────────────
   A year of days, one column per week. Hidden by the markup until there is
   something in it — an empty grid is a picture of having done nothing, shown
   to somebody who has just started. */
.act{margin:0 0 22px}
.act-h{font-family:var(--sans);font-weight:600;font-size:11.5px;letter-spacing:.06em;
  text-transform:uppercase;color:var(--stone);margin:0 0 9px}
/* Columns of 7, filling downward, so each column is a week and the shape reads
   the way people already know how to read it. Scrolls in its own box: 53 weeks
   does not fit a phone and must not make the page scroll sideways. */
.act-grid{display:grid;grid-auto-flow:column;grid-template-rows:repeat(7,1fr);
  gap:3px;overflow-x:auto;padding-bottom:4px}
.act-c{width:11px;height:11px;border-radius:2px;background:var(--sunk);
  display:block}
/* Four steps against the busiest day. Deliberately the brand green rather than
   a neutral: this is the one place in the app that shows accumulated effort. */
.act-1{background:color-mix(in oklab,var(--live) 30%,var(--sunk))}
.act-2{background:color-mix(in oklab,var(--live) 55%,var(--sunk))}
.act-3{background:color-mix(in oklab,var(--live) 78%,var(--sunk))}
.act-4{background:var(--live)}
/* Days after today: present so the grid keeps its shape, invisible so they do
   not read as "nothing happened" for time that has not occurred. */
.act-x{background:transparent}
@media(prefers-reduced-motion:no-preference){
  .act-c{transition:background .12s ease}
}

/* ── Your code, in our portal ───────────────────────────────────────────────
   Replaces a link that sent people to a Forgejo login they do not have. */
.code-clone{margin:0 0 12px}
.code-clone code{display:inline-block;margin-top:4px;font-size:12px;
  background:var(--sunk);border:1px solid var(--hairline);border-radius:6px;
  padding:5px 8px;overflow-wrap:anywhere}
.code-acts{display:flex;gap:8px;flex-wrap:wrap}
/* Source scrolls in its own box at a readable size. Long lines wrap rather
   than forcing a horizontal scroll on a phone; code that has to be scrolled
   sideways to read is code nobody reads. */
.code-src{margin:0;max-height:60vh;overflow:auto;background:var(--sunk);
  border:1px solid var(--hairline);border-radius:10px;padding:12px 14px}
.code-src code{font-family:var(--mono);font-size:12.5px;line-height:1.65;
  color:var(--charcoal);white-space:pre-wrap;overflow-wrap:anywhere}
/* The rendered page. White ground on purpose: it is showing somebody's own
   site, which has its own colours, and tinting it would misrepresent what
   they built. */
.code-frame{width:100%;height:60vh;border:1px solid var(--hairline);
  border-radius:10px;background:#fff}

/* Taking your work with you. Download first, git second — "git clone" is a
   fine answer for somebody who knows what git is and no answer at all for
   everybody else, and the promise is made to everybody. */
.code-take{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin:0 0 14px}
.code-adv,.built-adv{font-size:12.5px;color:var(--stone)}
.code-adv summary,.built-adv summary{cursor:pointer;min-height:24px;display:inline-flex;align-items:center}
.code-adv code{display:block;margin-top:6px;font-size:12px;background:var(--sunk);
  border:1px solid var(--hairline);border-radius:6px;padding:5px 8px;overflow-wrap:anywhere}

/* Dropping a file. The whole window is the target — somebody dragging a photo
   is not aiming carefully, and a drop zone you can miss is one that loses work.
   Pointer-events none so the overlay never eats the drop it is advertising. */
/* "Picked up where you left off" — a self-dismissing note that recovery
   happened, so a person who saw the page reload knows the work is theirs. */
.recovered-toast{position:fixed;left:50%;top:14px;z-index:9998;
  transform:translate(-50%,-12px);background:var(--obsidian);color:var(--paper);
  font:600 13px/1.35 var(--sans);padding:9px 15px;border-radius:999px;max-width:90vw;
  box-shadow:0 6px 24px rgba(0,0,0,.22);opacity:0;pointer-events:none;
  transition:opacity .25s ease,transform .25s ease}
.recovered-toast.show{opacity:1;transform:translate(-50%,0)}
@media(prefers-reduced-motion:reduce){.recovered-toast{transition:opacity .25s ease}}

body.is-dropping::after{content:'Drop it anywhere — photos and files';
  position:fixed;inset:12px;z-index:9999;pointer-events:none;
  border:2px dashed var(--live);border-radius:16px;background:var(--scrim);
  color:var(--paper);font:600 15px/1 var(--sans);letter-spacing:.01em;
  display:grid;place-items:center;text-align:center;padding:20px}
@media(prefers-reduced-motion:no-preference){
  body.is-dropping::after{animation:dropin .12s ease-out}
  @keyframes dropin{from{opacity:0}to{opacity:1}}
}

/* ── Checking a live site ───────────────────────────────────────────────────
   The panel behind "Check this site". Its job is to be read top to bottom and
   acted on, so the ordering is the fixing order: high first, then the button
   that hands the whole list back to the agent.

   Deliberately plain. Findings about somebody's live website are alarming
   enough as a list of sentences; colour-coding them red would make a page with
   four small SEO notes look like an outage. Severity is carried by the heading
   and the order, and only the alarm rule uses the alarm colour. */
.ck-top{display:flex;align-items:center;justify-content:space-between;gap:14px;
  flex-wrap:wrap;margin-bottom:10px}
.ck-allow{margin:0}
.ck-target{margin:0 0 14px;word-break:break-all}
.ck-verdict{font-family:var(--sans);font-weight:600;font-size:12.5px;color:var(--charcoal);
  margin:0 0 14px}
.ck-sev{font-size:.92rem;font-weight:600;color:var(--charcoal);
  margin:18px 0 8px;letter-spacing:.01em}
.ck-sev-high{color:var(--alarm)}
.ck-list{list-style:none;margin:0;padding:0}
/* Each finding is a block, not a bullet: the sentence is the content and it
   wraps to two or three lines, which a bullet list makes hard to scan. */
.ck-item{display:flex;flex-direction:column;gap:4px;background:var(--paper);
  border:1px solid var(--hairline);border-radius:10px;padding:11px 14px;
  margin-bottom:8px}
.ck-area{font-family:var(--sans);font-weight:600;font-size:10.5px;letter-spacing:.04em;
  text-transform:uppercase;color:var(--stone)}
.ck-says{font-size:.92rem;line-height:1.5;color:var(--charcoal)}
.ck-page{font-family:var(--mono);font-size:11px;color:var(--stone);
  word-break:break-all;align-self:flex-start}
.ck-fix{margin-top:20px;padding-top:16px;border-top:1px solid var(--hairline)}
.ck-fix .app-fine{margin:8px 0 0}
.ck-hist{margin-top:22px}
.ck-old{font:inherit;font-family:var(--mono);font-size:11px;color:var(--stone);
  background:none;border:none;padding:2px 0;margin-right:12px;cursor:pointer;
  text-decoration:underline}
.ck-old:hover{color:var(--charcoal)}

/* ── Waiting on you ─────────────────────────────────────────────────────────
   The blanks only the owner can fill. Written to read as a question somebody
   can answer, not as an error report about their own website — marking a gap
   honestly was the right call, and the tone has to say so. Hence a quiet
   prompt on the card rather than a warning badge. */
.repo-need{margin:6px 0 0}
.need-btn{font:inherit;font-family:var(--sans);font-weight:600;font-size:11px;letter-spacing:.02em;
  color:var(--charcoal);background:var(--sunk);border:1px solid var(--hairline);
  border-radius:var(--radius-pill);padding:4px 11px;cursor:pointer;min-height:26px}
.need-btn:hover{border-color:var(--stone)}
.need-q{padding:14px 0;border-top:1px solid var(--hairline)}
.need-q.is-first{border-top:none;padding-top:4px}
.need-ask{margin:0;font-size:1rem;line-height:1.45;color:var(--charcoal);font-weight:600}
.need-why{margin:6px 0 0;font-size:.9rem;line-height:1.5;color:var(--stone)}
.need-where{margin:6px 0 0}
/* One field per question, all of them in one form. It was a field-and-button
   per question, which meant answering the first closed the panel and the other
   five had to be reopened one at a time. */
.need-in{display:block;width:100%;margin-top:10px;min-width:0;font:inherit;
  font-size:.92rem;padding:9px 11px;border:1px solid var(--hairline);
  border-radius:10px;background:var(--paper);color:var(--charcoal);min-height:40px}
.need-in:focus-visible{outline:none;border-color:var(--brand);
  box-shadow:0 0 0 3px var(--brand-halo)}
.need-send{margin-top:20px;padding-top:16px;border-top:1px solid var(--hairline)}
.need-send .app-fine{margin:8px 0 0}
.need-foot{margin-top:18px;padding-top:14px;border-top:1px solid var(--hairline)}

/* ── The account page ───────────────────────────────────────────────────────
   Every limit with its fill, so the one that is nearly full does the selling.
   Deliberately quiet: this page is read when somebody is deciding whether to
   trust the bill, and a page that shouts about upgrading at every visit is a
   page they stop reading. Only .is-near carries any colour at all. */
.acct-plan{margin:0 0 22px;font-size:1.05rem;color:var(--charcoal)}
.acct-status,.acct-renew{font-family:var(--sans);font-weight:600;font-size:11px;letter-spacing:.02em;
  color:var(--stone);text-transform:uppercase;margin-left:8px}
/* The meters read as designed things, not printed rules: the fill is the
   brand until it is a warning, the label carries weight, and each row has
   air. Grey-on-grey graphite bars were part of the "everything is text"
   verdict this screen earned. The numbers stay mono — they are data. */
.al-row{display:grid;grid-template-columns:minmax(8rem,14rem) minmax(4rem,1fr) auto;
  align-items:center;gap:14px;padding:10px 0}
.al-k{font-size:.94rem;font-weight:600;color:var(--charcoal)}
.al-bar{display:block;height:10px;border-radius:99px;background:var(--sunk);
  box-shadow:inset 0 0 0 1px var(--hairline);overflow:hidden}
.al-bar i{display:block;height:100%;background:var(--brand);border-radius:99px;min-width:2px}
/* The month's money is ONE bar: the track is the spending limit, the shaded
   part is how much of it the plan already covers, the fill is what has been
   spent. Both live on top of each other, so they are positioned rather than
   stacked — a second block child would render UNDER the bar. */
.al-spend .al-bar{position:relative}
.al-spend .al-bar>u,.al-spend .al-bar>i{position:absolute;left:0;top:0;height:100%;border-radius:99px}
.al-spend .al-bar>u{background:color-mix(in oklab,var(--brand) 22%,transparent);text-decoration:none}
.al-spend .al-bar>i{min-width:2px}
.al-row.is-near .al-bar i{background:var(--alarm)}
.al-v{font-family:var(--mono);font-size:11.5px;color:var(--stone);white-space:nowrap}
.al-near{font-style:normal;color:var(--alarm);margin-left:6px}
.al-note{margin:2px 0 18px}
.al-edit{font:inherit;font-size:12px;font-weight:600;color:var(--brand-ink);
  background:none;border:none;padding:0;cursor:pointer;text-decoration:underline}
.acct-up{margin-top:24px;padding:16px 18px;border:1px solid var(--hairline);
  border-radius:var(--radius);background:var(--sunk)}
.acct-up-t{margin:0;font-weight:600;color:var(--charcoal)}
.acct-up-b{margin:6px 0 0;font-size:.94rem;line-height:1.5;color:var(--stone)}
.acct-room{margin-top:24px}
/* One column on a phone: three columns at 320px puts the numbers off the edge. */
@media (max-width:34rem){
  .al-row{grid-template-columns:1fr auto;gap:6px 12px}
  .al-bar{grid-column:1 / -1;order:3}
  .al-v{text-align:right}
}

/* ── The next useful thing ──────────────────────────────────────────────────
   One card, at the top of the overview, or nothing at all. Sized to be read
   before the numbers below it and quiet enough not to feel like an advert —
   it is advice about their own account, and it earns attention by being right
   rather than by being loud. */
/* The ONE card. It sat on --sunk — a RECESSED surface — while the stat
   cards below it were elevated white, so the single most considered element
   on the overview read quieter than its own furniture. Elevated like its
   siblings; the blue button is applied where it renders. */
#nextCard{margin:0 0 26px;padding:18px 20px;border:1px solid var(--hairline);
  border-radius:var(--radius);background:var(--white);box-shadow:var(--ring-card)}
.nx-t{margin:0;font-size:1.08rem;line-height:1.35;font-weight:600;color:var(--charcoal)}
.nx-b{margin:8px 0 0;font-size:.95rem;line-height:1.55;color:var(--stone);max-width:46rem}
.nx-chips{display:flex;flex-wrap:wrap;gap:8px;margin-top:14px}
.nx-go{margin-top:14px}

/* ── Your public page ───────────────────────────────────────────────────────
   Off by default and it has to LOOK off — a control that reads as already-on
   is how somebody discovers their work is public by finding it. Hence the
   fields staying hidden until the box is ticked, rather than sitting there
   filled in and ambiguous. */
.pfset{margin-top:36px;padding-top:26px;border-top:1px solid var(--hairline)}
.pfset-row{display:flex;align-items:center;gap:10px;margin:14px 0 0;cursor:pointer;
  min-height:44px}
.pfset-t{font-size:.95rem;color:var(--charcoal)}
.pfset-fields{margin-top:16px;display:grid;gap:12px;max-width:34rem}
.pfset-f{display:grid;gap:5px}
.pfset-f>span:first-child{font-family:var(--sans);font-weight:600;font-size:10.5px;letter-spacing:.04em;
  text-transform:uppercase;color:var(--stone)}
.pfset-f input{font:inherit;font-size:.94rem;padding:8px 11px;border:1px solid var(--hairline);
  border-radius:10px;background:var(--paper);color:var(--charcoal);min-height:38px;
  min-width:0;width:100%}
.pfset-f input:focus-visible{outline:none;border-color:var(--brand);
  box-shadow:0 0 0 3px var(--brand-halo)}
/* The address shown as what it will be, so nobody types the whole URL in. */
.pfset-at{display:flex;align-items:center;gap:2px;flex-wrap:wrap;
  font-family:var(--mono);font-size:12px;color:var(--stone)}
.pfset-at input{flex:1 1 8rem;font-family:var(--mono)}
.pfset-go{display:flex;gap:10px;align-items:center;flex-wrap:wrap}

/* ── The public page itself ─────────────────────────────────────────────────
   Its own document: this is the one page here read by people with no account,
   and loading the signed-in application to render a name and six links would
   be slow for exactly the audience with least reason to wait.

   Two columns, identity beside work, which is the shape every profile of this
   kind uses because it is the one that answers "who is this" and "what have
   they made" without scrolling between the two. */
.pf{min-height:100vh;display:flex;flex-direction:column;background:var(--paper)}
/* paper.css carries `section{padding:96px 0}` for the marketing pages, and this
   document loads paper.css for its tokens. The contribution calendar is a
   <section>, so it inherited 96px of dead space above and below — a gap the
   size of the calendar itself, between it and the projects under it.
   Fourth instance in this codebase of a BARE ELEMENT SELECTOR in the shared
   marketing stylesheet reaching an app surface; the previous one made the
   sidebar paint over the page content. The app shell already neutralises it,
   which is why the identical graph on the dashboard is fine and only this
   page — which deliberately does not load the app bundle — was wrong.
   Neutralised for this document rather than changed in paper.css, where the
   96px is correct and every marketing page depends on it. */
.pf section{padding:0;position:static}
.pf-top{display:flex;align-items:center;justify-content:space-between;gap:16px;
  padding:16px 20px;border-bottom:1px solid var(--hairline)}
.pf-main{flex:1;width:100%;max-width:68rem;margin:0 auto;padding:36px 20px 60px}
.pf-cols{display:grid;grid-template-columns:16rem minmax(0,1fr);gap:40px;align-items:start}
.pf-body{min-width:0}

/* Identity */
.pf-av{width:88px;height:88px;border-radius:50%;display:grid;place-items:center;
  font-family:var(--mono);font-size:28px;letter-spacing:.02em;color:var(--white);
  margin-bottom:16px;user-select:none}
.pf-av-0{background:var(--project-0)}.pf-av-1{background:var(--project-1)}
.pf-av-2{background:var(--project-2)}.pf-av-3{background:var(--project-3)}
.pf-av-4{background:var(--project-4)}.pf-av-5{background:var(--project-5)}
.pf-name{margin:0;font-size:1.5rem;line-height:1.2;color:var(--charcoal);
  overflow-wrap:anywhere}
.pf-handle{margin:2px 0 0;font-family:var(--mono);font-size:13px;color:var(--stone);
  overflow-wrap:anywhere}
.pf-bio{margin:14px 0 0;font-size:.95rem;line-height:1.55;color:var(--charcoal)}
.pf-meta{margin:18px 0 0;display:grid;gap:8px}
.pf-meta div{display:flex;justify-content:space-between;gap:12px;align-items:baseline}
.pf-meta dt{font-family:var(--sans);font-weight:600;font-size:10.5px;letter-spacing:.04em;
  text-transform:uppercase;color:var(--stone)}
.pf-meta dd{margin:0;font-size:.9rem;color:var(--charcoal);text-align:right;
  overflow-wrap:anywhere}

/* The contribution calendar. The labels are the feature: a bare grid is a
   texture, the months and weekdays are what make it legible. */
.cal{margin:0 0 32px}
.cal-scroll{overflow-x:auto;padding-bottom:4px}
.cal-scroll:focus-visible{outline:2px solid var(--brand);outline-offset:3px;border-radius:6px}
.cal-months{display:grid;grid-template-columns:repeat(53,11px);gap:3px;
  margin-left:30px;margin-bottom:4px;min-width:min-content}
.cal-m{font-size:10px;color:var(--stone);white-space:nowrap}
.cal-body{display:flex;gap:6px;min-width:min-content}
/* Sticky, because the scroller opens at the RECENT end: scrolled right, a
   column that travels with the grid is off-canvas, and Mon/Wed/Fri rendered
   as three clipped slivers of glyph on the live page. The background masks
   the squares passing underneath. */
.cal-days{display:grid;grid-template-rows:repeat(7,11px);gap:3px;width:24px;
  font-size:10px;color:var(--stone);line-height:11px;
  position:sticky;left:0;z-index:1;background:var(--paper)}
/* Mon, Wed, Fri sit on rows 2, 4 and 6 — the same three GitHub labels, in the
   same places, so the column reads as a week rather than as three loose words. */
.cal-days span:nth-child(1){grid-row:2}
.cal-days span:nth-child(2){grid-row:4}
.cal-days span:nth-child(3){grid-row:6}
.cal-grid{display:grid;grid-template-rows:repeat(7,11px);grid-auto-flow:column;
  grid-auto-columns:11px;gap:3px}
.cal-key{display:flex;align-items:center;gap:4px;margin-top:10px;
  font-size:10.5px;color:var(--stone)}
.cal-key .act-c{width:11px;height:11px}

/* Repository cards */
.pf-list-head{display:flex;align-items:center;justify-content:space-between;
  gap:16px;flex-wrap:wrap;margin-bottom:12px}
.pf-list-head .dash-h2{margin:0}
#pfFind{font:inherit;font-size:.9rem;padding:7px 11px;border:1px solid var(--hairline);
  border-radius:10px;background:var(--paper);color:var(--charcoal);min-height:36px;
  min-width:0;flex:0 1 15rem}
#pfFind:focus-visible{outline:none;border-color:var(--brand);
  box-shadow:0 0 0 3px var(--brand-halo)}
.pf-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(17rem,1fr));gap:14px}
.pf-card{border:1px solid var(--hairline);border-radius:var(--radius);
  padding:16px 18px;background:var(--paper);min-width:0;display:flex;
  flex-direction:column;gap:8px}
.pf-card-n{margin:0;display:flex;align-items:baseline;gap:8px;flex-wrap:wrap;
  font-weight:600;color:var(--charcoal);overflow-wrap:anywhere}
.pf-vis{font-family:var(--sans);font-weight:600;font-size:9.5px;letter-spacing:.05em;
  text-transform:uppercase;color:var(--stone);border:1px solid var(--hairline);
  border-radius:var(--radius-pill);padding:1px 7px}
.pf-card-d{margin:0;font-size:.88rem;line-height:1.5;color:var(--stone);
  overflow-wrap:anywhere}
/* Was opacity:.6 over --stone, which composites to 2.47:1 in light and 3.45:1
   in dark — both under the 4.5:1 bar, and a static opacity rather than an
   animation, so it was never going to settle into passing. It is placeholder
   text and should read quieter than a real description, but "quieter" has a
   floor. --stone already passes on both grounds; italic carries the meaning
   that the transparency was trying to. */
.pf-card-d-none{font-style:italic}
.pf-card-m{margin:auto 0 0;display:flex;align-items:center;gap:14px;flex-wrap:wrap;
  font-size:11.5px;color:var(--stone);font-family:var(--sans);font-weight:600}
.pf-lang{display:inline-flex;align-items:center;gap:6px}
.pf-dot{width:9px;height:9px;border-radius:50%;display:inline-block}
.pf-dot-0{background:var(--project-0)}.pf-dot-1{background:var(--project-1)}
.pf-dot-2{background:var(--project-2)}.pf-dot-3{background:var(--project-3)}
.pf-dot-4{background:var(--project-4)}.pf-dot-5{background:var(--project-5)}
.pf-live{color:var(--stone)}
.pf-card-none{opacity:.7}
.pf-when{margin-left:auto}
.pf-none{margin-top:6px}
.pf-cta{margin-top:40px}
.pf-foot{padding:20px;border-top:1px solid var(--hairline);text-align:center}

/* One column on a narrow screen: a 16rem sidebar beside content does not fit
   a phone, and the identity belongs above the work rather than squeezed. */
@media (max-width:52rem){
  .pf-cols{grid-template-columns:1fr;gap:28px}
  .pf-av{width:64px;height:64px;font-size:21px}
  .pf-meta div{justify-content:flex-start;gap:10px}
  .pf-meta dd{text-align:left}
}

/* Archived projects. Quiet on purpose — this is a way back, not a feature. */
#archWrap{margin-top:34px;padding-top:22px;border-top:1px solid var(--hairline)}
.arch-list{margin-top:12px;display:grid;gap:8px;max-width:34rem}
.arch-row{display:flex;align-items:center;justify-content:space-between;gap:14px;
  padding:10px 14px;border:1px solid var(--hairline);border-radius:10px;
  background:var(--sunk);font-size:.94rem;color:var(--charcoal)}

/* ── Embedded in a customer's own website ───────────────────────────────────
   The published site's "Edit now" badge opens this app in an iframe. Somebody
   who pressed a button on their own website wants to say what to change, not
   to be handed a dashboard — so the navigation, the header and the account
   furniture are all gone and only the conversation is left. */
html[data-embed] .app-top,
html[data-embed] .side,
html[data-embed] #projWrap,
html[data-embed] .starters { display: none !important; }
html[data-embed] .shell { grid-template-columns: 1fr; }
html[data-embed] .main { padding-left: 0; padding-right: 0; }
html[data-embed] body, html[data-embed] .app { min-height: 100vh; }

/* A year of grey squares with one green one is a picture of having done almost
   nothing, printed larger than the projects underneath it. Collapsed until the
   grid has a shape worth reading — the headline number is always there, and
   the control gives it back to anybody who wants it. */
.act[data-sparse="1"] .act-grid{display:none}
.act-toggle{font:inherit;font-family:var(--sans);font-weight:600;font-size:10.5px;letter-spacing:.04em;
  text-transform:uppercase;color:var(--stone);background:none;border:0;padding:2px 0;
  cursor:pointer;text-decoration:underline;margin-bottom:10px}
.act-toggle:hover{color:var(--charcoal)}

/* One primary action per project, and everything else behind one control.
   The row was eight links of identical weight — Preview, Your files, Form
   entries, Secrets, Archive, Use your own domain, Check this site — which is
   a row nobody reads and a beginner cannot rank. */
/* "More" is a button like its neighbours — it was bare grey text, and when
   opened its full-width panel shoved the label into a floating orphan above
   the extra pills ("the project look so bad lol"). Open, it takes the whole
   row so the extra actions read as a tidy second shelf under the first. */
.repo-more{display:inline-block}
.repo-more[open]{flex:1 0 100%}
.repo-more>summary{list-style:none;cursor:pointer;font:inherit;font-size:12.5px;font-weight:600;
  color:var(--obsidian);background:var(--paper);border:1px solid var(--hairline);
  border-radius:9999px;padding:7px 14px;display:inline-flex;align-items:center}
.repo-more>summary:hover{border-color:var(--obsidian)}
.repo-more>summary::-webkit-details-marker{display:none}
.repo-more>summary::after{content:" ▾";font-size:10px;margin-left:5px}
.repo-more[open]>summary::after{content:" ▴"}
.repo-more-in{display:flex;flex-wrap:wrap;gap:8px;padding:10px 0 2px;width:100%}
/* The one thing on a card that is actually asking something of them. */
.need-btn{background:var(--brand);color:var(--brand-on);border-color:transparent;font-weight:600}
.need-btn:hover{background:var(--brand);border-color:transparent;opacity:.92}

/* Is it live, and has anybody written to me. The two things a business owner
   opens this screen for, both of which used to require a click to find out. */
.repo-d.is-live{display:flex;align-items:center;gap:7px;color:var(--charcoal)}
.repo-dot{width:7px;height:7px;border-radius:50%;background:var(--live);flex:0 0 auto}
.repo-leads{margin:8px 0 0}
.leads-btn{font:inherit;font-family:var(--sans);font-weight:600;font-size:11px;letter-spacing:.02em;
  color:var(--charcoal);background:var(--sunk);border:1px solid var(--hairline);
  border-radius:var(--radius-pill);padding:4px 11px;cursor:pointer;min-height:26px}
.leads-btn:hover{border-color:var(--stone)}

/* The private-activity choice sits under the fields it qualifies. */
.pfset-sub{align-items:flex-start;margin-top:4px}
.pfset-note{display:block;margin-top:3px;font-size:.82rem;line-height:1.45;color:var(--stone)}

/* The live transcript while somebody is still talking.
   Muted and italic because it is provisional — it will be replaced by the
   clean pass the moment they stop, and it should not read as the answer. */
.miclive{margin:6px 2px 0;font-style:italic;color:var(--stone);
  max-height:4.5em;overflow:hidden}

/* Why the microphone did not start, and what to press about it.
   The heading carries the alarm colour; the steps are ordinary text, because
   they are instructions rather than more bad news. */
.micerr{margin:6px 2px 0}
.micerr-t{color:var(--alarm);margin:0 0 4px;font-weight:600}
.micerr-s{margin:0;padding-left:20px;line-height:1.7;color:var(--stone)}
.micerr-s li{margin:1px 0}
.micerr-s b{color:var(--carbon);font-weight:600}

/* ── Automations ──────────────────────────────────────────────────────────
   A card per job. The price sits beside the title rather than under the
   description, because it is the second thing anybody looks at and the whole
   argument of this product is that it is not hidden. */
.autos{display:grid;gap:12px;margin:18px 0 26px}
.auto{border:1px solid var(--hairline);border-radius:var(--radius);padding:14px 16px}
.auto-top{display:flex;align-items:baseline;gap:10px;flex-wrap:wrap}
.auto-t{font-weight:600;color:var(--obsidian);margin:0}
.auto-badge{font:600 10px/1 var(--mono);letter-spacing:.08em;text-transform:uppercase;
  color:var(--stone);border:1px solid var(--hairline);border-radius:var(--radius-pill);
  padding:4px 7px}
.auto-cost{margin-left:auto;font:600 12px/1 var(--mono);color:var(--carbon);white-space:nowrap}
.auto-cost.is-unknown{color:var(--stone);font-weight:500}
.auto-s{margin:8px 0 0;color:var(--stone);line-height:1.6}
.auto-foot{display:flex;align-items:center;gap:12px;margin-top:12px;flex-wrap:wrap}
.auto-by{font-size:12px;color:var(--stone)}
.auto-by a{color:var(--brand-ink)}
.auto-votes{display:flex;align-items:center;gap:4px;margin-left:auto}
.auto-vote{appearance:none;background:none;border:0;cursor:pointer;color:var(--stone);
  font:600 13px/1 var(--mono);padding:4px 6px;border-radius:6px}
.auto-vote:hover{color:var(--carbon);background:var(--hairline)}
.auto-vote.on{color:var(--brand-ink)}
.auto-score{min-width:1.5em;text-align:center;font:600 12px/1 var(--mono);color:var(--carbon)}
.auto-locked{font-size:12px;color:var(--stone)}

/* The one suggestion. Given weight, because it is the only thing on this page
   that claims to be about YOUR project rather than about the catalogue. */
.autosuggest{display:flex;align-items:center;gap:14px;flex-wrap:wrap;
  border:1px solid var(--brand-ink);border-radius:var(--radius);padding:14px 16px;margin:18px 0 0}
.autosuggest-b{margin:0;color:var(--obsidian);flex:1 1 16rem}
.autoauth{display:grid;gap:8px;margin-top:12px}
.autoauth-r{display:flex;align-items:baseline;gap:10px;font-size:13px}
.autoauth-r a{color:var(--brand-ink);font-weight:600}
.autoauth-n{margin-left:auto;font:600 12px/1 var(--mono);color:var(--stone)}

/* ── Visitors ─────────────────────────────────────────────────────────────
   Two numbers, a shape, and two short lists. Anything more is a dashboard
   somebody has to learn, and the question here is only ever "is anyone
   coming, and from where". */
.vis-nums{display:flex;gap:28px;margin:4px 0 18px}
.vis-n{font:600 28px/1 var(--sans);color:var(--obsidian)}
.vis-l{display:block;margin-top:4px;font:600 10px/1 var(--mono);letter-spacing:.08em;
  text-transform:uppercase;color:var(--stone)}
/* Bars, not a line: a line implies a trend between two points that a day of
   traffic does not have. */
.vis-bars{display:flex;align-items:flex-end;gap:2px;height:64px;margin:0 0 20px}
.vis-bar{flex:1 1 0;min-width:2px;background:var(--brand-ink);border-radius:2px 2px 0 0;
  opacity:.85}
.vis-bar.is-zero{background:var(--hairline);opacity:1}
/* Search Console sits below the first-party counter and is ruled off from it,
   because they are different measurements from different sources and reading
   them as one row of numbers would invite adding clicks to views. */
/* Three questions, not one pile: which accounts Amelia acts on, what the
   finished site can do, and what data it may read. Spaced so the headings read
   as headings without a rule that would fight the cards' own borders. */
.dconn-grp{margin:0 0 34px}
.dconn-grp-t{font-size:15px;font-weight:600;margin:0 0 4px}
.dconn-grp-b{font-size:13px;color:var(--stone);margin:0 0 14px;max-width:56ch}
/* Bot Teams' Tools catalogue is this same Connect page — search + a "how
   many already" count, above the Featured shelf and the server's own
   group shelves (see app.js's renderConnections). */
.dconn-toolbar{display:flex;align-items:center;gap:10px;margin:0 0 18px;flex-wrap:wrap}
.dconn-search{flex:1 1 240px;min-width:0;border:1px solid var(--hairline);border-radius:999px;
  padding:9px 14px;font:inherit;font-size:13.5px;background:var(--white);color:var(--carbon)}
.dconn-count{display:inline-flex;align-items:center;gap:6px;font-size:12.5px;font-weight:600;
  color:var(--stone);background:var(--sunk,rgba(0,0,0,.05));border-radius:999px;padding:5px 12px;
  flex:0 0 auto;white-space:nowrap}
.vis-sc{margin-top:22px;padding-top:18px;border-top:1px solid var(--hairline)}
.vis-cols{display:grid;grid-template-columns:1fr 1fr;gap:22px}
@media(max-width:34rem){.vis-cols{grid-template-columns:1fr}}
.vis-row{display:flex;gap:10px;font-size:13px;padding:5px 0;border-bottom:1px solid var(--hairline)}
.vis-row span:first-child{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  color:var(--carbon)}
.vis-row span:last-child{margin-left:auto;font:600 12px/1.5 var(--mono);color:var(--stone)}

/* Inline code inside a chat bubble — md() emits these. Neutral translucent
   ground so it reads on both themes without its own token pair. */
.msg code{font-family:var(--mono,ui-monospace,Menlo,monospace);font-size:.92em;
  background:rgba(127,127,127,.16);padding:1px 5px;border-radius:5px}

/* On a phone the fact line was three lines deep under the composer. The
   essential part is the model, the price and "stops when you're done"; the
   lead-in phrase is the part a small screen can spare. */
/* ── The footer, on a phone ───────────────────────────────────────────────
   It had grown to four lines under the composer — the machine, the model, the
   price, what the price includes, when it stops, and why this model — and on a
   small screen that is a quarter of the conversation spent on a caption.
   Richy's brother, 2026-08-31: "at the bottom the ai is taking up too much
   space".

   Nothing is deleted, because every part of it is true and somebody chose to
   say it. The parts that are reference rather than news are folded away below
   480px; all of them are one tap into the sheet this line opens, and the two
   that matter on every send — which model, what a turn costs — stay. */
@media (max-width: 480px){
  .chatfine-lead{display:none}
  /* Except when it names their own computer. "Private cloud machine" was
     furniture — the same six words under every turn for everybody. The name of
     the laptop the build is running on is the opposite: it is the one part of
     this line that is not the default, and dropping it on a phone would leave
     the price of a free build reading as a cloud rate. */
  .chatfine.is-own .chatfine-lead{display:inline}
  .chatfine-more{display:none}
  .chatfine{padding:3px 8px;line-height:1.3;font-size:12px}
  /* The pill stops being a pill's worth of padding at this width. */
  .chatfine #chatModel{padding:0 5px 0 7px}
  /* "Best on this kind of work across 82 recent runs here." is worth saying
     and is not worth a fifth of a phone screen under every single turn. It is
     the first thing in the sheet this line opens. */
  .chatfine-why{display:none}
}

/* ── The workspace switcher ────────────────────────────────────────────────
   Sits directly under the account block, because "who am I" and "whose work is
   this" are one question asked twice, and answering them in two different
   corners is how somebody answers only the first.

   Working in a company changes the BLOCK — a brand edge, a brand label and the
   name in heading ink — rather than adding a badge inside it. A small pill
   beside a name stops being read within a day, and this control exists for
   exactly one purpose: that nobody puts company work in their own space, or
   their own work on the company's bill. No new colour is invented for it; it
   spends the brand hue, which is what --brand is for. */
.side-ws{display:flex;flex-direction:column;gap:3px;margin:0 0 10px;padding:8px 10px;
  border:1px solid var(--hairline);border-left:3px solid var(--hairline);
  border-radius:var(--radius);background:var(--white)}
.side-ws-l{color:var(--stone)}
.side-ws-s{width:100%;min-width:0;font:inherit;font-size:13px;color:var(--obsidian);
  background:transparent;border:0;padding:0;cursor:pointer}
.side-ws-s:focus-visible{outline:2px solid var(--brand);outline-offset:2px;
  border-radius:var(--radius-nav)}
.side-ws-a{font-size:12px;color:var(--stone);text-decoration:none}
.side-ws-a:hover{color:var(--obsidian);text-decoration:underline}
.side-ws.is-org{border-left-color:var(--brand)}
.side-ws.is-org .side-ws-l{color:var(--brand-ink)}
.side-ws.is-org .side-ws-s{font-weight:600}

/* ── The company console (/org) ────────────────────────────────────────────
   Its own page, so its own block of rules rather than reusing the builder's
   layout — this is a table-and-form screen for somebody doing administration,
   not the chat surface.

   Everything here spends weight and space rather than colour. There are three
   semantic hues in the whole system and an admin screen full of numbers is
   exactly where a fourth gets invented, so the only colour used is --alarm on
   a refusal and --brand on the current tab. */
.org-body{background:var(--paper);color:var(--charcoal);
  font-family:var(--sans);margin:0;padding:0 16px 64px}
.org-top{display:flex;align-items:baseline;justify-content:space-between;gap:16px;
  flex-wrap:wrap;max-width:1040px;margin:0 auto;padding:20px 0 12px;
  border-bottom:1px solid var(--hairline)}
.org-back{color:var(--stone);text-decoration:none;font-size:13px}
.org-back:hover{color:var(--obsidian)}
.org-who{margin:0;display:flex;align-items:baseline;gap:8px}
.org-name{font-weight:600;color:var(--obsidian)}
.org-role{color:var(--stone)}
.org-shell,.org-empty{max-width:1040px;margin:0 auto}
.org-empty{padding:48px 0;max-width:56ch}
.org-empty p{color:var(--stone)}

.org-tabs{display:flex;gap:2px;flex-wrap:wrap;margin:14px 0 0;
  overflow-x:auto;scrollbar-width:none}
.org-tab{font:inherit;font-size:13px;color:var(--stone);background:transparent;
  border:0;border-bottom:2px solid transparent;padding:8px 10px;cursor:pointer;
  white-space:nowrap}
.org-tab:hover{color:var(--obsidian)}
.org-tab.is-on{color:var(--obsidian);border-bottom-color:var(--brand)}
.org-tab:focus-visible{outline:2px solid var(--brand);outline-offset:-2px}

.org-pane{padding:20px 0}
.org-note{margin:12px 0 0;padding:10px 12px;border-radius:var(--radius);
  background:var(--sunk);color:var(--obsidian);font-size:14px}
.org-note.is-bad{color:var(--alarm);box-shadow:var(--ring-ghost)}
/* --slate, not --smoke. smoke is defined as "placeholders — 2.3:1, never
   content", and "Nothing yet." IS content: it is the whole answer to what is
   on the screen. axe flagged it, and the token's own note had said so first. */
.org-wait,.org-none{color:var(--slate);font-size:14px}
.org-intro{max-width:68ch;color:var(--stone);font-size:14px;margin:0 0 16px}
.org-fine{max-width:68ch;color:var(--slate);font-size:12px;line-height:1.5}
.org-h3{font-size:14px;color:var(--obsidian);margin:24px 0 8px}

.org-cards{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(170px,1fr))}
.org-card{border:1px solid var(--hairline);border-radius:var(--radius);
  background:var(--white);padding:12px 14px}
.org-card-l{margin:0;color:var(--stone)}
.org-card-v{margin:2px 0 0;font-size:24px;font-weight:600;color:var(--obsidian);
  font-variant-numeric:tabular-nums}
.org-card-n{margin:2px 0 0;font-size:12px;color:var(--slate)}
.org-total{font-size:28px;font-weight:600;color:var(--obsidian);margin:0 0 8px;
  font-variant-numeric:tabular-nums}
.org-total span{font-size:13px;font-weight:400;color:var(--stone);margin-left:8px}
.org-asks{margin:20px 0;padding:12px 14px;border:1px solid var(--hairline);
  border-left:3px solid var(--brand);border-radius:var(--radius);background:var(--white)}
.org-ask{margin:4px 0;font-size:14px;color:var(--obsidian)}

/* A wide table scrolls inside its own box. The page never scrolls sideways. */
.org-scroll{overflow-x:auto}
.org-t{width:100%;border-collapse:collapse;font-size:14px}
.org-t th{text-align:left;font-weight:500;color:var(--stone);font-size:12px;
  letter-spacing:.05em;text-transform:uppercase;padding:6px 10px 6px 0;
  border-bottom:1px solid var(--hairline);white-space:nowrap}
.org-t td{padding:8px 10px 8px 0;border-bottom:1px solid var(--hairline);
  color:var(--charcoal);font-variant-numeric:tabular-nums}
/* Suspended, not decorative — still has to be readable. */
.org-t tr.is-off td{color:var(--slate)}
.org-t tr.is-deny td{color:var(--alarm)}
.org-act{text-align:right}

.org-form{display:flex;flex-direction:column;gap:4px;margin:24px 0 0;max-width:60ch}
.org-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}
.org-tools{margin:0 0 16px}
.org-form textarea{font:inherit;font-family:var(--mono);font-size:12px;
  color:var(--obsidian);background:var(--white);border:1px solid var(--hairline);
  border-radius:var(--radius);padding:7px 9px;width:100%;resize:vertical}
.org-form textarea:focus-visible{outline:2px solid var(--brand);outline-offset:1px}
.org-form input,.org-form select,.org-tools select,.org-t select{
  font:inherit;font-size:14px;color:var(--obsidian);background:var(--white);
  border:1px solid var(--hairline);border-radius:var(--radius);padding:7px 9px;min-width:0}
.org-form input{flex:1 1 14rem}
.org-form input:focus-visible,.org-form select:focus-visible,
.org-tools select:focus-visible,.org-t select:focus-visible{
  outline:2px solid var(--brand);outline-offset:1px}
.org-check{display:flex;align-items:center;gap:8px;font-size:14px;
  color:var(--charcoal);margin:8px 0}
.org-b,.org-b2{font:inherit;font-size:14px;border-radius:var(--radius);
  padding:7px 14px;cursor:pointer;border:1px solid transparent;
  text-decoration:none;display:inline-block}
.org-b{background:var(--brand);color:var(--brand-on);align-self:flex-start;margin-top:8px}
.org-b2{background:var(--white);color:var(--obsidian);border-color:var(--hairline)}
/* ── The roles beyond owner/admin/member ─────────────────────────────────────
   Shown as what somebody HOLDS, under the one coarse role they are — they are
   added on top of it, not instead of it, which is what the permission model
   actually does. */
.org-extra{display:flex;flex-wrap:wrap;gap:4px;margin-top:5px}
.org-rolepill{display:inline-flex;align-items:center;gap:4px;
  font-family:var(--sans);font-size:11px;letter-spacing:.02em;
  padding:2px 4px 2px 8px;border-radius:999px;
  background:var(--sunk);color:var(--charcoal)}
.org-rolex{font:inherit;font-size:13px;line-height:1;cursor:pointer;border:0;
  background:none;color:var(--stone);padding:0 3px}
.org-rolex:hover{color:var(--alarm)}
.org-addrole{font:inherit;font-size:12px;margin-top:5px;max-width:190px;
  padding:3px 6px;border-radius:var(--radius-sm);
  border:1px solid var(--hairline);background:var(--paper);color:var(--stone)}
.org-b:hover,.org-b2:hover{filter:brightness(.96)}
.org-b:focus-visible,.org-b2:focus-visible{outline:2px solid var(--brand);outline-offset:2px}

/* Named apart from .org-role in the header. One class doing two jobs on two
   different elements is how a margin fix in one place silently moves the
   other. */
details.org-rolecard{border-bottom:1px solid var(--hairline);padding:10px 0}
details.org-rolecard summary{cursor:pointer;color:var(--obsidian);font-size:14px}
.org-perm{margin:6px 0 0;font-size:12px;color:var(--slate);line-height:1.6;
  font-family:var(--sans);font-weight:600}
.org-perm.is-deny{color:var(--alarm)}
.org-form code,.org-fine code{font-family:var(--mono);font-size:12px;
  background:var(--sunk);padding:2px 5px;border-radius:var(--radius-nav);
  word-break:break-all}
/* Visually hidden, for a column whose header is only there for a screen reader. */
.vh{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);
  white-space:nowrap}

/* The two blocks on the console that are about ending something.
   Weight and a rule, not a colour: --alarm means "this failed", and spending it
   on a button that has not failed yet is how a palette starts lying. The one
   place alarm IS right is the banner that says a company is already going. */
.org-danger{margin-top:32px;padding-top:20px;border-top:1px solid var(--hairline)}
.org-danger .org-h3{margin-top:0}
.org-doom{margin:0 0 20px;padding:14px 16px;border:1px solid var(--alarm);
  border-radius:var(--radius);background:var(--white)}
.org-doom .org-h3{margin:0 0 6px;color:var(--alarm)}
.org-doom p{margin:0 0 8px;font-size:14px;color:var(--charcoal)}

/* ── Your computers ─────────────────────────────────────────────────────────
   Deliberately borrows .dconn for the rows: a computer on the account and an
   account Amelia can reach are the same kind of object to the reader — a thing
   that is connected or is not — and giving them two visual languages inside
   one section makes the page look like two pages. Only the parts with no
   equivalent above are new. */
.mach-h2{margin-top:34px}
.mach-add-h{margin-top:0}
.mach-door{margin:0 0 18px}
.mach-door:last-child{margin-bottom:0}
.mach-door-t{margin:0 0 4px;font-size:14px;font-weight:500;color:var(--obsidian)}
.mach-door .app-fine{margin:0 0 10px;max-width:44rem}
/* The install line. pre-wrap, not a sideways scroller: a command you cannot
   see the end of is one people paste half of. */
.mach-cmd{margin:12px 0 8px;padding:12px 14px;background:var(--sunk);
  border-radius:var(--radius);font-family:var(--mono);font-size:12px;line-height:1.6;
  color:var(--charcoal);white-space:pre-wrap;overflow-wrap:break-word}
.mach-cmd-acts{display:flex;align-items:center;gap:12px;flex-wrap:wrap}
.mach-exp,.mach-wait{color:var(--stone)}
.mach-wait{margin:10px 0 0}
.mach-err{margin:10px 0 0;color:var(--alarm)}
/* ── A company with no plan ───────────────────────────────────────────────
   Deliberately not styled as an alarm. Nothing is broken and nobody has done
   anything wrong: the company was made a moment ago and has not been paid for
   yet. It is the first thing on the page because being refused mid-build is
   the worst way to learn this, but it reads as the next step rather than as a
   failure. */
.org-buy{margin:0 0 20px;padding:14px 16px;border:1px solid var(--hairline);
  border-radius:var(--radius);background:var(--white)}
.org-buy .org-h3{margin:0 0 6px}
.org-buy p{margin:0 0 8px;font-size:14px;color:var(--charcoal)}
.org-plans{display:flex;flex-wrap:wrap;gap:8px;margin:0}
.org-plans .org-b{margin:0}

/* ── Sign-in settings ─────────────────────────────────────────────────────
   Two visual states and no third. A method is either on — one green dot, the
   word "On", nothing else to read — or it is off and owes the owner a
   sentence. There is deliberately no "configured but failing" state here,
   because that is the state that shipped a dead Google button. */
.si-row{display:flex;align-items:center;gap:10px;flex-wrap:wrap;padding:11px 0;
  border-bottom:1px solid var(--hairline)}
.si-dot{flex:none;width:7px;height:7px;border-radius:50%;background:var(--stone)}
.si-on .si-dot{background:var(--live)}
.si-name{font-size:14px;color:var(--obsidian)}
.si-state{flex:1;font-family:var(--sans);font-weight:600;font-size:10.5px;letter-spacing:.06em;
  text-transform:uppercase;color:var(--stone);text-align:right}
.si-on .si-state{color:var(--live)}
/* The sentence gets the whole row under the name, because it is the part
   somebody has to act on and a truncated one is useless. */
.si-says{flex-basis:100%;margin:6px 0 0;padding-left:17px}
.si-more{margin-top:16px;border-top:1px solid var(--hairline);padding-top:12px}
.si-more>summary{cursor:pointer;font-size:14px;color:var(--obsidian);
  padding:4px 0;list-style:none}
.si-more>summary::-webkit-details-marker{display:none}
.si-more>summary::before{content:"+ ";font-family:var(--sans);font-weight:600;color:var(--stone)}
.si-more[open]>summary::before{content:"– "}
.si-steps{margin:10px 0 4px;padding-left:20px;color:var(--charcoal);font-size:14px}
.si-steps li{margin-bottom:12px;line-height:1.5}
/* The redirect address. Tap-to-copy, because it is the one value that breaks
   the whole flow when it is retyped with a character wrong. */
.si-copy{display:inline-block;margin-top:6px;font-family:var(--sans);font-weight:600;font-size:11.5px;
  background:var(--sunk);border:1px solid var(--hairline);border-radius:8px;
  padding:6px 9px;cursor:pointer;overflow-wrap:anywhere;color:var(--obsidian)}
.si-copy:hover{border-color:var(--stone)}
.si-copy:focus-visible{outline:2px solid var(--obsidian);outline-offset:2px}

/* ── A live site that is not the current work ─────────────────────────────
   Amber, not green and not red. It IS live — saying otherwise would be the
   opposite lie to the one this fixes — but what is live is not what they are
   looking at, and the sentence has to sit next to the tap that resolves it. */
.repo-d.is-behind{display:flex;align-items:center;gap:7px;color:var(--charcoal)}
.repo-d.is-behind .repo-dot{background:#c98a12}
.repo-behind{font-size:12px;color:var(--stone);margin-right:8px}

/* ── Payments panel ───────────────────────────────────────────────────────
   Reuses the sign-in rows on purpose: "is this capability on, and if not what
   do I do" is one question with one answer shape, and giving it two different
   looks would make it read as two different kinds of thing. */
.py-price{display:flex;gap:8px}
.py-price .sheet-input{margin-bottom:12px}
.py-price #pyAmount{flex:1 1 auto}
.py-price #pyCadence{flex:0 0 auto;width:auto}
.py-name{font-size:13px;color:var(--obsidian);overflow-wrap:anywhere}
.py-amt{font-family:var(--mono);font-size:12px;color:var(--charcoal);margin-left:auto}
.py-state{font-family:var(--sans);font-weight:600;font-size:10px;letter-spacing:.06em;
  text-transform:uppercase;color:var(--stone)}
.py-state.is-paid{color:var(--live)}
.py-state.is-failed{color:var(--alarm)}
/* Everything above needs a decision. This block needs only to be known, so it
   is set apart rather than sitting in the same list as the things to act on. */
.py-also{margin-top:18px;border-top:1px solid var(--hairline);padding-top:14px}

/* ── Screens & flow ──────────────────────────────────────────────────────── */
.sheet-flows{width:min(1400px,96vw);max-width:96vw}
.flows-mount{height:min(72vh,900px);min-height:420px;border:1px solid var(--hairline);
  border-radius:12px;overflow:hidden;background:var(--paper);position:relative}
.flows-mount .flows-note,.flows-empty{padding:24px;margin:0}
.flows-empty p{margin:0 0 8px}
.flows-tabs{display:flex;gap:6px;margin:0 0 10px;flex-wrap:wrap}
.flows-tabs button{font:inherit;font-size:13px;padding:6px 12px;border-radius:999px;
  border:1px solid var(--hairline);background:transparent;color:inherit;cursor:pointer}
.flows-tabs button.is-on{background:var(--brand);color:#fff;border-color:var(--brand)}
.pv-jump{font:inherit;font-size:13px;padding:6px 10px;border-radius:8px;border:1px solid var(--hairline);
  background:var(--paper);color:inherit;max-width:220px}

/* The sketch face for Screens & flow, self-hosted: the CSP allows fonts from
   this origin only, so the renderer's Google Fonts @import is stripped from the
   bundle and the same face is served from /vendor instead. */
@font-face{font-family:'Kalam';font-style:normal;font-weight:400;font-display:swap;src:url(/vendor/kalam-400.woff2) format('woff2')}
@font-face{font-family:'Kalam';font-style:normal;font-weight:700;font-display:swap;src:url(/vendor/kalam-700.woff2) format('woff2')}

/* ── New project: the second door ─────────────────────────────────────────── */
.np-mode{display:flex;gap:6px;margin:4px 0 14px}
.np-tab{appearance:none;font:inherit;font-size:14px;cursor:pointer;padding:7px 12px;
  border:1px solid var(--hairline);border-radius:var(--radius);background:none;color:var(--stone)}
.np-tab.is-on{background:var(--obsidian);color:var(--white);border-color:var(--obsidian)}
.np-lbl{display:block;font-size:13px;font-weight:600;margin:14px 0 6px}
.np-git{margin-top:18px}
.np-git-head{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:6px}
.np-filter{width:100%;font:inherit;font-size:14px;padding:8px 10px;border:1px solid var(--hairline);
  border-radius:var(--radius);background:var(--white);color:var(--obsidian);margin-bottom:6px}
.np-repos{display:flex;flex-direction:column;max-height:220px;overflow:auto;border:1px solid var(--hairline);border-radius:var(--radius)}
.np-repo{appearance:none;font:inherit;text-align:left;cursor:pointer;display:flex;justify-content:space-between;gap:10px;
  padding:9px 11px;border:0;border-bottom:1px solid var(--hairline);background:none;color:var(--obsidian);min-width:0}
.np-repo:last-child{border-bottom:0}
.np-repo:hover{background:var(--hairline)}
.np-repo .n{font-size:14px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}
.np-repo .m{font-size:12px;color:var(--stone);flex:none}
.np-dev{margin-top:18px;font-size:14px}
.np-dev summary{cursor:pointer;font-weight:600}
.np-msg{margin-top:8px;font-size:14px;line-height:1.45;resize:vertical}
.np-err{color:var(--alarm);margin-top:10px}

/* ── Bring your site ───────────────────────────────────────────────────────
   The preview card. Every colour is a token, because a literal here is one
   theme's value frozen into a card that the other theme cannot reach — which
   is what check-tokens.mjs exists to stop. */
.np-site-card{margin-top:16px;padding:14px;border:1px solid var(--hairline);
  border-radius:var(--radius);background:var(--sunk)}
.np-site-top{display:flex;align-items:center;gap:10px;min-width:0}
.np-site-icon{width:32px;height:32px;border-radius:6px;flex:none;object-fit:contain;background:var(--white)}
.np-site-id{min-width:0}
.np-site-title{font-size:15px;font-weight:600;margin:0;overflow:hidden;
  text-overflow:ellipsis;white-space:nowrap}
.np-site-host{font-size:13px;color:var(--stone);margin:2px 0 0}
.np-site-facts{font-size:14px;margin:10px 0 0}
.np-site-card .btn{margin-top:12px}

/* The door on the first screen. A row, not a card: it sits between the
   greeting and the starter chips and must not compete with either. */
.bring{display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap;
  margin:26px 0 6px;padding:14px 16px;border:1px solid var(--hairline);
  border-radius:var(--radius);background:var(--sunk)}
.bring-h{font-size:15px;font-weight:600;margin:0}
.bring-b{font-size:14px;color:var(--stone);margin:4px 0 0;max-width:52ch}
.bring .btn{flex:none}

.dash-head-acts{display:flex;align-items:center;gap:8px;flex-wrap:wrap}

.ho-row{display:flex;gap:8px;justify-content:flex-end;margin-top:10px}

.jb-row{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:11px 0;border-bottom:1px solid var(--hairline)}
.jb-row:last-child{border-bottom:0}
.jb-main{display:flex;flex-direction:column;gap:2px;min-width:0}
.jb-name{font-size:15px;font-weight:600}
.jb-meta{font-size:12.5px;color:var(--stone);font-family:var(--sans);font-weight:600}
.jb-state{font-size:13px;color:var(--stone)}
.jb-row.is-bad .jb-state{color:var(--alarm)}

/* The device sign-in code, shown in the desktop app while the browser has it */
.desk-code{font-family:var(--mono,ui-monospace,SFMono-Regular,Menlo,monospace);font-size:34px;letter-spacing:.14em;
  font-weight:500;margin:6px 0 8px;color:var(--obsidian);user-select:all}

/* ── The owner's admin (admin.html) ─────────────────────────────────────── */
.owner-admin-body{background:var(--paper,var(--white));color:var(--obsidian);margin:0}
/* paper.css styles bare header/section/main for the marketing pages (a fixed
   header, section{padding:96px 0}); on this page those read as the header
   covering the first row of tiles and a screen of white between sections.
   Class rules beat the element rules; position and padding are set here on
   purpose, not by omission. */
/* body > header.owner-admin-head, not .owner-admin-head: paper.css's `body > header:not(.site)`
   is (0,1,2) and zeroed this header's top padding at (0,1,0). */
body > header.owner-admin-head{display:flex;align-items:flex-end;justify-content:space-between;gap:16px;padding:22px 24px 12px;border-bottom:1px solid var(--hairline);max-width:1100px;margin:0 auto;position:static;height:auto;background:none;backdrop-filter:none}
.owner-admin-sec{padding:0;position:static;min-width:0}
.owner-admin-main{position:static}
.owner-admin-cap{display:block;font-size:12px;letter-spacing:.06em;text-transform:uppercase;color:var(--stone);font-weight:600}
.owner-admin-title{font-size:26px;margin:2px 0 0}
.owner-admin-links{display:flex;gap:8px}
.owner-admin-auth{max-width:520px;margin:64px auto;padding:0 24px}
.owner-admin-err{max-width:1100px;margin:12px auto;padding:0 24px;color:var(--alarm)}
.owner-admin-main{max-width:1100px;margin:0 auto;padding:16px 24px 48px}
.owner-admin-tiles{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:10px}
.owner-admin-tile{border-top:2px solid var(--obsidian);padding-top:8px}
.owner-admin-k{font-size:12px;letter-spacing:.06em;text-transform:uppercase;color:var(--stone);font-weight:600}
.owner-admin-v{font-size:28px;line-height:1.1;margin-top:4px;font-variant-numeric:tabular-nums}
.owner-admin-s{font-size:12.5px;color:var(--stone);margin-top:2px}
.owner-admin-cols{display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:22px;margin-top:26px}
.owner-admin-wide{grid-column:1 / -1}
.owner-admin-h2{font-size:15px;font-weight:600;margin:0 0 6px}
.owner-admin-row{display:flex;justify-content:space-between;gap:12px;padding:8px 0;border-bottom:1px solid var(--hairline);font-size:14px;min-width:0}
.owner-admin-row span:first-child{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}
.owner-admin-row .owner-admin-m{color:var(--stone);flex:none;font-size:13px}
.owner-admin-row.is-bad .owner-admin-m{color:var(--alarm)}

/* Stop, beside the composer, only while a turn runs. */
.composer .stop{border:1px solid var(--alarm,#b91c1c);color:var(--alarm,#b91c1c);background:transparent;border-radius:8px;padding:.45rem .7rem;font:inherit;font-size:13px;cursor:pointer}
.composer .stop:disabled{opacity:.6;cursor:default}


/* ═══ ROOMS (2026-09-05) ═══════════════════════════════════════════════════
   Colour means place. Every room owns a fill, a tint and an ink (tokens.json),
   and the three custom properties below carry them to whatever sits inside a
   [data-room] element: the rail button, the room header, a Map stop. State
   stays a dot. */
[data-room="map"]{--rc:var(--map);--rt:var(--map-t);--ri:var(--map-ink)}
[data-room="studio"],[data-room="builds"]{--rc:var(--studio);--rt:var(--studio-t);--ri:var(--studio-ink)}
[data-room="chats"]{--rc:var(--chats);--rt:var(--chats-t);--ri:var(--chats-ink)}
[data-room="projects"]{--rc:var(--projects);--rt:var(--projects-t);--ri:var(--projects-ink)}
[data-room="library"]{--rc:var(--library);--rt:var(--library-t);--ri:var(--library-ink)}
[data-room="memory"]{--rc:var(--memory);--rt:var(--memory-t);--ri:var(--memory-ink)}
/* Computers is its own room as of 2026-09-07. It was the second TAB of Memory,
   which is how the Map's "Build on your own computer for $0" card sent a
   beginner to a room called Memory to plug in a laptop (Richy: "it takes me to
   /app/connect. Double check all."). Its own hue, because colour means place. */
[data-room="computers"]{--rc:var(--computers);--rt:var(--computers-t);--ri:var(--computers-ink)}
[data-room="account"]{--rc:var(--account);--rt:var(--account-t);--ri:var(--account-ink)}

/* ── the rail ── */
@media(min-width:821px){.side{flex-basis:232px;width:232px}}
.snav{gap:11px;padding:6px 9px;font-size:14px;font-weight:500;color:var(--charcoal);border-radius:var(--radius-nav)}
.snav-tile{flex:0 0 auto;width:28px;height:28px;border-radius:8px;display:inline-grid;place-items:center;
  background:var(--rt);color:var(--rc)}
.snav-tile .ic{display:block}
.snav:hover{background:var(--sunk);color:var(--obsidian)}
.snav.is-on{background:var(--rt);color:var(--obsidian);font-weight:600}
.snav.is-on .snav-tile{background:var(--rc);color:var(--brand-on)}
.side-grp{font-size:11px;font-weight:600;letter-spacing:.08em;text-transform:uppercase;color:var(--slate);margin:14px 0 4px}
.side-plan{font-size:10px;font-weight:600;letter-spacing:.04em}

/* ── the room header: tile, name, one sentence, tabs ── */
.roomhead{display:flex;flex-direction:column;gap:14px;margin:0 0 20px}
.roomhead[hidden]{display:none}
.rh-top{display:flex;align-items:center;gap:14px;min-width:0}
.rh-tile{flex:0 0 auto;width:48px;height:48px;border-radius:13px;display:inline-grid;place-items:center;
  background:var(--rt);color:var(--rc)}
.rh-t{min-width:0}
.rh-name{margin:0;font-family:var(--display);font-size:24px;font-weight:700;letter-spacing:-.015em;color:var(--obsidian);line-height:1.1}
.rh-blurb{margin:3px 0 0;font-size:14px;color:var(--stone)}
.rtabs{display:flex;gap:4px;padding:4px;background:var(--sunk);border-radius:var(--radius-pill);
  width:max-content;max-width:100%;overflow-x:auto;scrollbar-width:none}
.rtabs::-webkit-scrollbar{display:none}
.rtab{appearance:none;border:0;background:none;cursor:pointer;padding:8px 15px;border-radius:var(--radius-pill);
  font-family:var(--sans);font-size:13.5px;font-weight:600;color:var(--stone);white-space:nowrap;line-height:1.2}
.rtab:hover{color:var(--obsidian)}
.rtab[aria-selected="true"]{background:var(--white);color:var(--obsidian);box-shadow:0 1px 2px var(--edge)}
.roomhead-studio{margin:8px 0 4px;flex:0 0 auto}
.roomhead-studio .rh-tile{width:36px;height:36px;border-radius:10px}
.roomhead-studio .rh-name{font-size:19px}
.roomhead-studio .rh-blurb{font-size:13px}
.app.is-reading .roomhead-studio{display:none}
/* The room header carries the eyebrow and the sub-tabs now. */
.dsec>.cap,.dash-head .cap{display:none}
.xtabs{display:none}
.dsec .app-h1{font-size:1.25rem;line-height:1.2}

/* ── the Map ── */
.map-stops{margin:0 0 24px}
.map-prog{display:flex;align-items:center;gap:14px;background:var(--map-t);border:1px solid var(--hairline);
  border-radius:var(--radius);padding:12px 16px}
.map-prog b{font-family:var(--display);font-size:15px;font-weight:600;color:var(--obsidian);white-space:nowrap}
.map-meter{flex:1;height:6px;border-radius:3px;background:var(--white);overflow:hidden;min-width:60px}
.map-meter i{display:block;height:100%;background:var(--map);border-radius:3px}
.map-pct{font-family:var(--mono);font-size:12px;color:var(--map-ink)}
.map-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:12px;margin-top:12px}
/* One of the two `.stop` classes: this one is a card on the Map's checklist,
   the other is the Stop button in the composer (`.composer .stop`). They were
   both plain `.stop` and only specificity kept them apart, so an edit to
   either could break the other silently (breakit2, finding 24). */
.map-stop{background:var(--white);border:1px solid var(--hairline);border-radius:var(--radius);padding:14px;
  display:flex;flex-direction:column;gap:8px}
.stop-h{display:flex;align-items:center;gap:9px;min-width:0}
.stop-h b{font-family:var(--display);font-size:15px;font-weight:600;color:var(--obsidian);flex:1;min-width:0;line-height:1.2}
.stop-tile{flex:0 0 auto;width:26px;height:26px;border-radius:7px;display:inline-grid;place-items:center;
  background:var(--rt);color:var(--rc)}
.stop-ck{flex:0 0 auto;width:20px;height:20px;border-radius:50%;border:2px solid var(--hairline);
  display:inline-grid;place-items:center;color:var(--brand-on)}
.map-stop.is-done .stop-ck{background:var(--live);border-color:var(--live)}
.map-stop p{margin:0;font-size:13px;color:var(--stone);flex:1}
.stop-go{appearance:none;border:0;background:none;padding:0;text-align:left;cursor:pointer;
  font-family:var(--sans);font-size:13px;font-weight:600;color:var(--ri)}
.stop-go:hover{text-decoration:underline}

/* ── a project's drawers ── */
.repo-more>summary{font-size:12.5px}
.repo-more-in{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:12px;width:100%;margin-top:12px}
.drawer{background:var(--paper);border:1px solid var(--hairline);border-radius:var(--radius-sm);padding:12px;min-width:0}
.drawer-h{margin:0 0 1px;font-family:var(--display);font-size:14px;font-weight:600;color:var(--obsidian)}
.drawer-s{margin:0 0 8px;font-size:12px;color:var(--slate)}
.drawer-in{display:flex;flex-direction:column;gap:2px}
.repo-acts .repo-tool{display:flex;flex-direction:column;align-items:flex-start;gap:1px;width:100%;
  text-align:left;padding:7px 9px;border:1px solid transparent;border-radius:var(--radius-sm);background:none;
  font-family:var(--sans);color:var(--obsidian);cursor:pointer;text-decoration:none;box-sizing:border-box;min-width:0}
.repo-acts .repo-tool:hover{background:var(--white);border-color:var(--hairline)}
.repo-acts .repo-tool b{font-size:13px;font-weight:600;display:flex;min-width:0;max-width:100%}
.repo-acts .repo-tool small{font-size:11.5px;color:var(--slate);font-weight:400;line-height:1.3}
.repo-acts .repo-acct{padding:6px 9px}
@media(max-width:820px){
  .roomhead{margin-top:12px}
  .snav{font-size:13px;gap:8px;padding:6px 8px}
  .snav-tile{width:24px;height:24px;border-radius:7px}
  .rh-tile{width:40px;height:40px;border-radius:11px}
  .rh-name{font-size:20px}
  .map-prog b{white-space:normal}
}


/* ═══ THE PHONE IS A CHAT (2026-09-06) ═══════════════════════════════════════
   Richy: "the chat screen is way too cluttered on top… organise the chat to be
   more full screen like Claude and ChatGPT do it and there can be a menu to
   open all the other items." Under 820px the header is ☰ · title · +, the
   rail becomes a drawer, and the conversation gets the screen. */
.menubtn,.newtop{display:none}
.scrim{position:fixed;inset:0;background:var(--scrim);z-index:55}
.scrim[hidden]{display:none}
.side-phone{display:none}
@media(max-width:820px){
  .menubtn,.newtop{display:inline-grid;place-items:center;width:40px;height:40px;border-radius:var(--radius-nav);
    border:0;background:none;color:var(--obsidian);cursor:pointer;flex:0 0 auto;padding:0}
  .menubtn:hover,.newtop:hover{background:var(--sunk)}
  /* One row: ☰, the title (the project picker when a project is open, else
     the brand), +. Everything the wide header carries lives in the drawer. */
  .app-top{height:56px;padding:0;flex-wrap:nowrap;gap:6px;align-items:center}
  .app-top::after{display:none}
  .app-top .brand{order:0;flex:1 1 auto;min-width:0;justify-content:center;font-family:var(--display);font-size:15px;font-weight:600}
  /* Whichever switcher is up IS the title on a phone, so the brand steps
     aside for either of them. */
  .app-top:has(.projwrap:not([hidden])) .brand{display:none}
  .toptitle{display:none;flex:1 1 auto;min-width:0;text-align:center;font-family:var(--display);font-size:16px;font-weight:600;color:var(--obsidian);
    overflow:hidden;text-overflow:ellipsis;white-space:nowrap;order:1}
  body.is-dash .toptitle:not([hidden]),body.is-chat-only .toptitle:not([hidden]){display:block}
  /* A chat has no project to switch — but it does have other chats, and
     that switcher is the phone's title bar (Richy, 2026-09-06). */
  body.is-chat-only .projwrap:not(.chatwrap){display:none}
  .chatwrap .projbtn{font-family:var(--display);font-size:15px;font-weight:600;max-width:100%}
  body.is-dash .projwrap,body.is-dash .app-top .brand{display:none}
  body.is-dash .newtop{visibility:hidden}
  .app-top-right{display:contents}
  /* Report a problem STAYS on the brand row here. It used to be in this hide
     list, which left the button nowhere on screen at all below 821px: the
     header copy was display:none and the rail's copy — the one the wide
     header defers to — is translated off-canvas at this width, because the
     rail IS the drawer here. So the one control somebody needs while
     something else is going wrong was reachable only by first opening a menu,
     on exactly the devices where the greeting screen is the whole viewport
     (Maria, beginner review, 2026-09-06). The 560px rule above has styled it
     for this row since 2026-09-02; that styling had simply stopped applying.
     Above 821px the rail is always on screen and keeps it (see the
     min-width:821px rule). */
  .app-top-tail,#allowance,#machineState,#modelState,#projHistBtn{display:none!important}
  .projwrap{order:1;flex:1 1 auto;min-width:0;display:flex;justify-content:center}
  .projbtn{max-width:100%;font-family:var(--display);font-size:15px;font-weight:600;text-transform:none;letter-spacing:0;
    background:none;border:0;padding:6px 8px}
  .newtop{order:2}
  /* The rail as a drawer. Its own scroller, the whole height, the account
     block, find and the footer back on, rooms stacked. */
  .shell{flex-direction:column}
  .side{position:fixed;top:0;left:0;bottom:0;width:min(84vw,340px);z-index:60;
    display:flex;flex-direction:column;gap:2px;padding:14px 14px calc(14px + env(safe-area-inset-bottom));
    background:var(--white);border-right:1px solid var(--hairline);border-bottom:0;
    overflow-y:auto;transform:translateX(-102%);transition:transform .22s ease;box-shadow:none}
  body.side-open .side{transform:none;box-shadow:var(--drop-lg)}
  .side-acct,.side-find{display:flex}
  .side-grp{display:block}
  .side-foot{display:flex}
  .side-nav{position:static;flex-direction:column;align-items:stretch;overflow:visible;gap:2px;padding-right:0;
    -webkit-mask-image:none;mask-image:none;flex:0 0 auto}
  .side{align-items:stretch}
  .snav{width:100%;flex:0 0 auto;white-space:normal;font-size:15px;padding:9px 10px;gap:12px}
  .snav-tile{width:30px;height:30px;border-radius:9px}
  .side-phone{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin-top:auto;padding-top:14px;border-top:1px solid var(--hairline)}
  .side-spend{flex:0 0 100%;margin:0 0 4px;font-family:var(--mono);font-size:12px;color:var(--stone)}
  .side-spend:empty{display:none}
  .side-foot{margin-top:10px}
  .main{padding-left:0}
  /* The Studio header shrinks to its tabs; the rooms header stays on the
     other screens, where it is the only title there is. */
  .roomhead-studio .rh-top{display:none}
  .roomhead-studio{margin:6px 0 4px}
  .app.is-reading .side{display:flex}
  @media(prefers-reduced-motion:reduce){.side{transition:none}}
}


/* ═══ CHATS AND BUILDS, APART (2026-09-06) ═══════════════════════════════════
   A chat runs no machine; a build does. Each is a screen, and a project card
   carries its own of each in two colour-coded columns. */
.sess-proj{font-family:var(--sans);font-weight:600;color:var(--projects-ink)}
.sess-row[data-kind="build"] .sess-title::before,.sess-row[data-kind="chat"] .sess-title::before{
  content:"";display:inline-block;width:8px;height:8px;border-radius:2px;margin:0 8px 1px 0;vertical-align:middle}
.sess-row[data-kind="build"] .sess-title::before{background:var(--studio)}
.sess-row[data-kind="chat"] .sess-title::before{background:var(--chats)}
.repo-sess{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin:12px 0 2px}
@media(max-width:700px){.repo-sess{grid-template-columns:1fr}}
.repo-col{background:var(--rt);border:1px solid var(--hairline);border-radius:var(--radius-sm);padding:10px 12px 8px;min-width:0}
.repo-col-h{margin:0 0 6px;display:flex;align-items:center;gap:8px;font-family:var(--display);font-size:13.5px;font-weight:600;color:var(--obsidian)}
.repo-col-dot{width:10px;height:10px;border-radius:3px;background:var(--rc);flex:0 0 auto}
.repo-col-n{font-family:var(--mono);font-size:11px;font-weight:500;color:var(--ri)}
.repo-col .sess-row{padding:6px 6px;border-radius:7px;background:transparent}
.repo-col .sess-row:hover{background:var(--white)}
.repo-col .sess-row .sess-title::before{display:none}
.repo-col .sess-title{font-size:13px}
.repo-col .sess-meta{font-size:11.5px}
.repo-col-empty{margin:2px 6px 6px;font-size:12.5px;color:var(--slate)}
.repo-col-go{appearance:none;border:0;background:none;padding:6px 6px 2px;cursor:pointer;font-family:var(--sans);font-size:12.5px;font-weight:600;color:var(--ri);text-align:left}
.repo-col-go:hover{text-decoration:underline}
/* The room header already names and explains Chats and Builds; the section's
   own heading said it twice. */
.dsec[data-for="sessions"]>.app-h1,.dsec[data-for="sessions"]>.app-sub,
.dsec[data-for="builds"]>.app-h1,.dsec[data-for="builds"]>.app-sub{display:none}
.dsec[data-for="sessions"] .sess-head,.dsec[data-for="builds"] .sess-head{margin-top:4px}


/* ═══ THE CHAT IS THE SCREEN, ON A DESKTOP TOO (2026-09-06) ══════════════════
   Richy: "the chat is the main feature for both chat and build and it should
   not be cluttered — Amelia's Agent seems so cluttered vs Claude clean." One
   quiet bar, a reading column, plain answers, the options inside the
   composer, and everything else in the rail. */

/* The reading column: the log scrolls full width (scrollbar at the edge) and
   pads its content into a column; the strips below match its width. */
.log{padding-left:max(0px,calc((100% - 860px) / 2));padding-right:max(0px,calc((100% - 860px) / 2))}
.starters,.sitebar,.rec,.atts,.atts-hint,.mentions,.composer,.chatfine,.chatswap,#chatFine{
  width:100%;max-width:860px;margin-left:auto;margin-right:auto;box-sizing:border-box}
.chatfine,.chatswap{display:block;text-align:center}

/* A person's line is a quiet grey block; an answer is plain text with the
   mark beside it. The blue bubble made every exchange look like an alert. */
.msg{font-size:15px;line-height:1.55}
.msg.me{background:var(--sunk);color:var(--obsidian);max-width:min(76%,34rem);border-bottom-right-radius:7px}
.msg.ai{background:none;box-shadow:none;padding:6px 0 6px 0;max-width:calc(100% - 40px)}
.msg.ai.err{box-shadow:none;color:var(--alarm)}
.msg.ai::before{bottom:auto;top:4px}

/* The "earlier here" strip is gone; the rail's Recent list holds those. */
.convbar{display:none!important}

/* The conversation header is one slim line: which room, the way back, a new one. */
.roomhead-studio{flex-direction:row;align-items:center;justify-content:space-between;gap:12px;margin:2px 0 4px;
  width:100%;max-width:860px;margin-left:auto;margin-right:auto}
.roomhead-studio .rh-top{gap:8px}
.roomhead-studio .rh-tile{width:24px;height:24px;border-radius:7px}
.roomhead-studio .rh-tile .ic{width:13px;height:13px}
.roomhead-studio .rh-name{font-size:14px;font-weight:600}
.roomhead-studio .rh-blurb{display:none}
.roomhead-studio .rtabs{background:none;padding:0;gap:2px}
.roomhead-studio .rtab{padding:5px 9px;font-size:12.5px;color:var(--stone)}
.roomhead-studio .rtab[aria-selected="true"]{background:none;box-shadow:none;color:var(--obsidian)}
.roomhead-studio .rtab:hover{background:var(--sunk);color:var(--obsidian)}

/* The composer carries its options on its bottom edge. */
.composer.has-modes{display:grid;grid-template-columns:1fr auto auto auto auto;grid-template-areas:"t t t t t" "m a s c d";
  align-items:center;column-gap:6px;row-gap:4px;padding:10px 10px 8px 16px;border-radius:16px}
.composer.has-modes textarea{grid-area:t;padding:.4rem 0 .2rem}
.composer.has-modes .attach{grid-area:a}
.composer.has-modes .stop{grid-area:s}
.composer.has-modes .mic{grid-area:c}
.composer.has-modes .send{grid-area:d}
.composer.has-modes .modebar{grid-area:m;margin:0;min-width:0;flex-wrap:wrap;overflow:visible}
.composer.has-modes .modebar-b{font-size:11px;padding:4px 9px;letter-spacing:.03em}
.composer.has-modes .modebar-hint{font-weight:500;font-size:11px;white-space:normal;overflow:visible;text-overflow:clip;min-width:0}
.composer.has-modes .modebar-b.modebar-new{margin-left:auto;flex:0 0 auto}

/* The caption under the composer is one quiet centred line, like "Claude can
   make mistakes": the model is still the control, the swap is inline. */
.chatfine{padding:4px 8px;font-size:12px;color:var(--slate)}
.chatswap{display:inline-block;width:auto;margin:0 auto;font-size:12px;color:var(--slate);text-decoration:underline;text-underline-offset:2px;padding:0 0 6px}

/* The rail keeps the last conversations; the chat does not. */
.side-recent{margin-top:8px;min-width:0}
.side-recent-list{display:flex;flex-direction:column;gap:1px}
.side .side-sess{display:block;width:100%;text-align:left;appearance:none;border:0;background:none;cursor:pointer;
  padding:6px 10px 6px 30px;border-radius:var(--radius-nav);font-family:var(--sans);font-size:13px;color:var(--charcoal);
  position:relative;min-width:0}
.side .side-sess .sess-title{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.side .side-sess::before{content:"";position:absolute;left:13px;top:11px;width:8px;height:8px;border-radius:2px;background:var(--studio)}
.side .side-sess[data-kind="chat"]::before{background:var(--chats)}
.side .side-sess:hover{background:var(--sunk);color:var(--obsidian)}
.side .side-sess.is-on{background:var(--sunk);color:var(--obsidian);font-weight:600}

/* The wide header keeps the project and the spend; Report, theme and Sign out
   live in the rail's footer with the address, as they do on a phone. */
@media(min-width:821px){
  .app-top-right>.reportbtn,#projHistBtn,.app-top-tail{display:none!important}
  .side-phone{display:flex;flex-wrap:wrap;gap:8px;margin-top:auto;padding-top:12px;border-top:1px solid var(--hairline)}
  .side-phone .side-spend{display:none}
  .side-phone .btn{font-size:12px;padding:5px 10px}
  .side-foot{margin-top:8px;border-top:0;padding-top:4px}
}
@media(max-width:820px){
  .side-recent{margin-top:4px}
  .roomhead-studio{margin:6px 0 4px}
}
/* One kind dot per recent row, and the phone's title bar text stays a phone thing. */
.toptitle{display:none}
/* On a phone the hint takes its own line under the pills, and "+ New chat"
   stays on the pill line — the composer is narrower than the old strip was. */
@media(max-width:820px){.composer.has-modes .modebar-hint{order:3;flex:1 0 100%}}
/* On a phone the box takes the top row and everything else shares the bottom
   one: attach on the left, the pills with their hint in the middle, mic and
   send on the right. The icons used to sit on a third row under the hint,
   which left an empty band across the composer (Richy, 2026-09-06: "below
   'I ask first' there is unnecessary spacing… the plus sign, audio and enter
   can be on the same row as Plan Build Overnight"). */
@media(max-width:820px){
  .composer.has-modes{grid-template-columns:auto 1fr auto auto;grid-template-areas:"t t t t" "a m c d";row-gap:2px}
  .composer.has-modes .stop{grid-area:c}
  .composer.has-modes .mic{grid-area:c}
  .composer.has-modes .send{grid-area:d}
  .composer.has-modes .attach{grid-area:a}
  .composer.has-modes .modebar{row-gap:3px}
  .composer.has-modes .modebar-hint{line-height:1.3}
  .composer.has-modes .attach,.composer.has-modes .mic,.composer.has-modes .send,.composer.has-modes .stop{align-self:center}
  /* The phone's title bar already carries "+" for a new conversation, the
     way Claude's does, so the pill row does not repeat it — there is no room
     for it now the icons share the row. The editor sheet on a customer's
     site has no title bar, so it keeps the button. */
  html:not([data-embed]) .composer.has-modes .modebar-b.modebar-new{display:none}
}
/* Narrow phones: the pills lose a little padding so all three still stand on
   one line between the attach and the mic. */
@media(max-width:430px){
  .composer.has-modes .modebar-b{padding:4px 7px;font-size:10.5px;letter-spacing:.02em}
  .composer.has-modes{column-gap:4px}
}
@media(max-width:820px){body.is-chat-only .app-top .brand{display:none}}

/* A heading inside an answer. */
.msg .md-h{display:block;font-family:var(--display);font-size:1.06em;font-weight:600;margin:.5em 0 .15em}


/* ═══ SIDE BY SIDE WITH CLAUDE (2026-09-06, round four) ═══════════════════════
   The rail carries the identity and the account; the header is one line —
   the room and the conversation's name, the site's state, the project, the
   spend; the reading face is bigger; the composer carries only the mode as
   quiet words; the caption is one line. */
.side-brand{display:flex;align-items:center;justify-content:space-between;padding:4px 8px 10px}
.side-brand-l{display:flex;align-items:center;gap:8px;text-decoration:none;color:var(--obsidian);
  font-family:var(--display);font-size:16px;font-weight:700;letter-spacing:-.01em}
.side-brand-m{width:22px;height:22px;border-radius:6px}
.rh-conv{font-weight:500;color:var(--stone);font-size:14px;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.rh-sep{margin:0 8px;color:var(--ash)}
.chatcap{display:flex;flex-wrap:wrap;align-items:center;justify-content:center;gap:2px 10px;
  width:100%;max-width:820px;margin:2px auto 0;font-size:12px;color:var(--slate)}
.chatcap .chatfine,.chatcap .chatswap,.chatcap .modebar-hint{width:auto;max-width:none;margin:0;padding:4px 0;font-size:12px;color:var(--slate)}
.chatcap .modebar-hint{font-weight:500;letter-spacing:0;text-transform:none}

@media(min-width:821px){
  .side{flex-basis:248px;width:248px;padding:12px 12px 12px 0;gap:1px}
  .side-acct{order:97;margin:auto 0 0;padding:8px 8px}
  .side-phone{order:98;margin-top:0;border-top:0;padding-top:6px}
  .side-foot{order:99}
  .side-find{margin-bottom:8px;padding:6px 9px;font-size:12.5px}
  .side-grp{display:none}
  .side-recent .side-grp{display:block;margin:10px 0 2px}
  .snav{padding:5px 8px;font-size:13.5px;gap:9px;font-weight:500}
  .snav-tile{width:24px;height:24px;border-radius:7px}
  .snav-tile .ic{width:14px;height:14px}
  .side .side-sess{padding:5px 8px 5px 26px;font-size:13px}
  .side .side-sess::before{left:10px;top:10px}

  .app-top{height:48px;gap:14px;padding:0 0 0 4px}
  .app-top .brand{display:none}
  .app-top .roomhead-studio{flex:1 1 auto;min-width:0;margin:0;max-width:none;justify-content:flex-start;gap:14px}
  .app-top .roomhead-studio .rh-top{min-width:0;flex:0 1 auto}
  .app-top .roomhead-studio .rh-name{display:flex;align-items:center;min-width:0}
  .app-top .roomhead-studio .rtabs{flex:0 0 auto}
  .app-top-right{gap:14px;flex:0 0 auto}
  .app-top .sitebar{margin:0;font-size:12px;gap:6px;max-width:34vw;width:auto}
  .app-top .sitebar-go{font-size:11.5px;padding:4px 10px}
  .app-top .sitebar-t{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .app-top .sitebar-t strong{font-weight:600}

  .view.chat{gap:8px;padding-top:4px}
  .log{padding-top:12px;gap:14px}
  .msg{font-size:16px;line-height:1.6}
  .msg.ai{max-width:calc(100% - 44px)}
  .msg.me{max-width:min(72%,32rem);padding:11px 16px;font-size:15.5px}

  .composer.has-modes{padding:12px 12px 10px 18px;border-radius:18px}
  .composer.has-modes .modebar-b{background:none;border:0;box-shadow:none;padding:3px 9px;font-size:12.5px;
    font-weight:600;letter-spacing:0;text-transform:none;color:var(--stone);border-radius:var(--radius-pill)}
  .composer.has-modes .modebar-b:hover{background:var(--sunk);color:var(--obsidian)}
  .composer.has-modes .modebar-b.is-on{background:var(--sunk);color:var(--obsidian)}
  .composer.has-modes .modebar-b.modebar-new{color:var(--brand-ink)}
}
/* One caption line: the model first, then the mode's hint, then the swap. */
.chatcap .modebar-hint{flex:0 0 auto;order:2}
.chatcap .chatfine{order:1}
.chatcap .chatswap{order:3}
.chatcap .modebar-hint::before{content:"·";margin-right:10px;color:var(--ash)}


/* ═══ EDGE TO EDGE (2026-09-06) ═══════════════════════════════════════════════
   Richy: "too much padding on the sides — it should match Claude." The app
   fills the window: the rail sits against the left edge with its border,
   the content takes the rest, and the conversation column centres inside
   it. Dashboard sections keep a readable width of their own. */
@media(min-width:821px){
  .app{max-width:none;margin:0;padding:0}
  .app-top{padding:0 22px 0 18px;border-bottom:1px solid var(--hairline);height:52px}
  .side{padding:14px 12px 14px 14px;flex-basis:264px;width:264px}
  .main{padding-left:0}
  .view.chat{padding-left:28px;padding-right:28px;margin:0}
  .log{padding-left:max(0px,calc((100% - 860px) / 2));padding-right:max(0px,calc((100% - 860px) / 2))}
  .dash-pane{max-width:1160px;margin:0 auto;padding:0 32px;width:100%;box-sizing:border-box}
  .view.dash{padding-left:0;padding-right:0}
}
/* The 1280px cap on the signed-in shell is the padding Richy saw: the rail
   started a fifth of the way across a wide screen. Off, on a desktop. The
   dashboard's own title sits where the conversation's does. */
@media(min-width:821px){
  .app:has(.side:not([hidden])){max-width:none}
  body.is-dash .toptitle:not([hidden]){display:block;flex:1 1 auto;text-align:left;font-family:var(--display);font-size:15px;font-weight:600;color:var(--obsidian)}
}

/* Waiting: three dots that breathe, the shape every chat uses. Arriving: a
   caret at the end of the text while it types. */
.work-dots{display:inline-flex;gap:4px;align-items:center;height:14px}
.work-dots i{width:6px;height:6px;border-radius:50%;background:var(--stone);animation:work-dot 1.1s ease-in-out infinite}
.work-dots i:nth-child(2){animation-delay:.18s}
.work-dots i:nth-child(3){animation-delay:.36s}
@keyframes work-dot{0%,80%,100%{opacity:.3;transform:translateY(0)}40%{opacity:1;transform:translateY(-3px)}}
.msg.is-typing::after{content:"";display:inline-block;width:2px;height:1em;margin-left:2px;vertical-align:-.15em;background:var(--obsidian);animation:caret .8s steps(2) infinite}
@keyframes caret{50%{opacity:0}}
@media(prefers-reduced-motion:reduce){.work-dots i{animation:none;opacity:.7}.msg.is-typing::after{display:none}}

/* ═══ THE CONVERSATION GETS THE HEIGHT (2026-09-06) ═══════════════════════════
   Richy: "look how compressed the top and bottom are [on Claude] to allow the
   chat to be as big as possible." On a desktop the composer is one row —
   attach, the box, the mode as small words, mic, send — and the caption is
   one line; the log runs up to the header. */
@media(min-width:821px){
  .view.chat{padding-top:0;gap:6px;padding-bottom:10px}
  .log{padding-top:6px;padding-bottom:4px}
  .composer.has-modes{grid-template-columns:auto 1fr auto auto auto auto;grid-template-areas:"a t m s c d";
    row-gap:0;column-gap:6px;padding:5px 8px 5px 8px;border-radius:16px;align-items:center}
  .composer.has-modes textarea{padding:.55rem 4px;font-size:15px}
  .composer.has-modes .modebar{gap:2px;flex-wrap:nowrap;overflow:visible}
  .composer.has-modes .modebar-b{padding:4px 8px;font-size:12px}
  .composer.has-modes .modebar-b.modebar-new{margin-left:2px}
  .chatcap{flex-wrap:nowrap;gap:0 8px;font-size:11.5px;margin-top:0;max-width:none}
  .chatcap>*{white-space:nowrap;flex:0 0 auto}
  .chatcap .chatfine,.chatcap .chatswap,.chatcap .modebar-hint{font-size:11.5px;padding:2px 0}
}
@media(min-width:821px){
  .chatcap{max-width:860px;justify-content:center}
  .chatcap .modebar-hint{margin:0;flex:0 0 auto;text-align:left}
}
/* The model line is one item of the caption row, not the whole row: an
   id-level width rule above was stretching it to 860px and shoving the hint
   and the switch to the far edge. */
.chatcap #chatFine,.chatcap .chatfine{width:auto!important;max-width:none!important;margin:0!important;flex:0 0 auto}
.chatcap{justify-content:center}
/* Local model names can be much longer than a hosted model's short label.
   Keep the last-answer receipt readable inside a phone's caption row. */
.is-chat-only .chatcap #chatFine{min-width:0;max-width:100%!important;flex:0 1 auto;flex-wrap:wrap;gap:4px}
.is-chat-only .chatcap #chatModel{min-width:0;max-width:100%;overflow-wrap:anywhere}

/* On a phone the conversation starts right under the title bar: the second
   row of "All chats / New chat" is gone — the title goes back to the list
   and the plus makes a new one (Richy, 2026-09-06: "the top gets cut off
   with mostly empty space"). */
@media(max-width:820px){
  .roomhead-studio{display:none}
  .view.chat{padding-top:0}
  .log{padding-top:6px}
  .toptitle{cursor:pointer}
}


/* ═══ SHARE A CONVERSATION (2026-09-06) ═══════════════════════════════════════ */
.sharetop{display:none}
@media(max-width:820px){
  .sharetop{display:inline-grid;place-items:center;width:40px;height:40px;border-radius:var(--radius-nav);border:0;background:none;color:var(--obsidian);cursor:pointer;flex:0 0 auto;padding:0;order:2}
  .sharetop[hidden]{display:none}
  .newtop{order:3}
  /* An empty chat: the greeting and four questions, nothing about building. */
  body.is-chat-only .hello-facts,body.is-chat-only .starters-cats,body.is-chat-only .kits,body.is-chat-only .starters-q{display:none}
}
body.is-chat-only .hello-facts,body.is-chat-only .starters-cats,body.is-chat-only .kits,body.is-chat-only .starters-q{display:none}
.sheet-share{width:min(560px,100%)}

/* ── Building is not on this plan ──────────────────────────────────────────
   The refusal sheet, and the empty-state explainer that says the same thing
   on the Builds and Projects screens. Both are answers to "what is a build?"
   rather than error surfaces, so neither of them borrows the error colour. */
.sheet-bw{width:min(520px,100%)}
.sheet-bw .up-actions{justify-content:flex-start;margin-top:18px}
.bxpl{display:flex;flex-direction:column;align-items:flex-start;gap:14px;
      background:var(--white);border-radius:var(--radius);box-shadow:var(--ring-card);
      padding:26px 22px}
.bxpl-tile{display:inline-flex;align-items:center;justify-content:center;
           width:40px;height:40px;border-radius:12px;background:var(--rt);color:var(--rc)}
.bxpl-h{margin:0;font-size:19px;font-weight:600;color:var(--obsidian);letter-spacing:-.01em}
.bxpl-s{margin:0;font-size:14px;line-height:1.6;color:var(--stone);max-width:60ch}
.bxpl-g{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px;width:100%}
.bxpl-c,.bxpl-c:hover,.bxpl-c *{text-decoration:none}
.bxpl-c{display:flex;flex-direction:column;gap:6px;min-width:0}
.bxpl-shot{width:100%;height:auto;border-radius:10px;box-shadow:var(--ring-card);display:block}
.bxpl-t{font-size:14px;font-weight:500;color:var(--obsidian)}
.bxpl-m{font-family:var(--sans);font-size:11px;color:var(--smoke)}
@media (min-width:900px){.bxpl-g{grid-template-columns:repeat(4,minmax(0,1fr))}}

.share-row{display:flex;gap:8px;margin:14px 0 18px}
.share-row .sheet-input{flex:1;min-width:0;margin:0}
.share-cap{margin:0 0 8px;font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:var(--slate);font-weight:600}
.share-access{display:block;margin:0 0 12px}
.share-access select{width:100%;font:inherit;font-size:14.5px;font-weight:600;color:var(--obsidian);background:var(--sunk);border:1px solid var(--hairline);border-radius:var(--radius-sm);padding:10px 12px}
.share-people{display:flex;flex-direction:column;gap:2px;margin:0 0 12px}
.share-person{display:flex;align-items:center;gap:12px;padding:8px 4px;border-top:1px solid var(--hairline)}
.share-person:first-child{border-top:0}
.share-av{width:36px;height:36px;border-radius:50%;display:inline-grid;place-items:center;background:var(--sunk);color:var(--obsidian);font-weight:600;flex:0 0 auto}
.share-who{flex:1;min-width:0;display:flex;flex-direction:column;line-height:1.25}
.share-who b{font-weight:600;font-size:14.5px;color:var(--obsidian);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.share-who small{font-size:12px;color:var(--slate)}
.share-role{font-size:13px;color:var(--slate)}
.share-x{appearance:none;border:0;background:none;color:var(--slate);cursor:pointer;font-size:14px;padding:6px 8px;border-radius:8px}
.share-x:hover{background:var(--sunk);color:var(--obsidian)}
.share-fine{margin:0 0 4px}
.share-err{color:var(--alarm);margin:6px 0 0}
.share-foot{display:flex;align-items:center;gap:10px;margin-top:16px;padding-top:14px;border-top:1px solid var(--hairline)}
.share-foot-gap{flex:1}

/* ═══ THE SHARED PAGE (/s/<token>) ═══════════════════════════════════════════ */
body.shared{background:var(--paper);color:var(--charcoal);font-family:var(--sans);margin:0}
.sh-head{display:flex;align-items:center;gap:14px;padding:12px 22px;border-bottom:1px solid var(--hairline);background:var(--white)}
.sh-brand{display:flex;align-items:center;gap:8px;text-decoration:none;color:var(--obsidian);font-family:var(--display);font-weight:700;font-size:16px}
.sh-brand img{border-radius:6px}
.sh-kind{flex:1;font-size:13px;color:var(--slate)}
.sh-main{max-width:860px;margin:0 auto;padding:28px 22px 40px}
.sh-title{font-family:var(--display);font-size:clamp(24px,4vw,34px);font-weight:700;letter-spacing:-.015em;color:var(--obsidian);margin:0 0 6px;text-wrap:balance}
.sh-when{margin:0 0 22px;font-size:13px;color:var(--slate)}
.sh-log{overflow:visible;padding:0}
.sh-log .msg{font-size:16px;line-height:1.6}
.sh-log .msg.me{background:var(--sunk);color:var(--obsidian);align-self:flex-end;max-width:min(76%,34rem)}
.sh-log .msg.ai{background:none;box-shadow:none;padding:6px 0;max-width:calc(100% - 44px)}
.sh-state{padding:28px 0}
.sh-state-b{color:var(--stone);margin:6px 0 16px}
.sh-foot{max-width:860px;margin:0 auto;padding:18px 22px 40px;border-top:1px solid var(--hairline);font-size:13px;color:var(--slate)}
.sh-foot a{color:var(--brand-ink)}
.sh-head{flex-wrap:wrap}
.sh-head .btn{flex:0 0 auto;margin-left:auto}
@media(max-width:420px){.sh-head{padding:10px 14px;gap:8px}.sh-main,.sh-foot{padding-left:14px;padding-right:14px}}

/* ── THE CONVERSATION'S ACTIONS, FAR RIGHT AND IN COLOUR ──────────────────
   "+ New chat" and "Share" used to be two grey words in the tab strip beside
   "← All chats". Richy, 2026-09-06: "move + New chat and share to the far
   right and with more color so it's easy visible and a share icon too on
   share." They are the last thing in the header now, brand blue, each with
   its icon. The project switcher moves the other way, next to the name. */
.roomacts{display:none}
@media(min-width:821px){
  .roomacts:not([hidden]){display:flex;align-items:center;gap:6px;flex:0 0 auto}
  .rtab-act{display:inline-flex;align-items:center;gap:6px;padding:6px 13px 6px 10px;border-radius:var(--radius-pill);
    background:var(--brand);color:var(--brand-on);font-family:var(--sans);font-weight:600;font-size:12.5px;letter-spacing:0;
    box-shadow:none;transition:filter .12s}
  .rtab-act:hover{background:var(--brand);color:var(--brand-on);filter:brightness(.9)}
  .rtab-act:focus-visible{outline:2px solid var(--obsidian);outline-offset:2px}
  .rtab-act .ic{width:14px;height:14px;display:block}
  .rtab-act-share{background:var(--studio-t);color:var(--studio-ink)}
  .rtab-act-share:hover{background:var(--studio-t);color:var(--studio-ink);filter:brightness(.94)}
  .app-top .roomhead-studio{width:auto;margin-left:0;margin-right:0}
  .app-top>.projwrap{flex:0 1 auto;min-width:0;max-width:22vw}
  .app-top>.projhist{flex:0 0 auto}
  .app-top .roomhead-studio{flex:0 1 auto}
  .app-top-right{margin-left:auto}
}
.apane[hidden]{display:none}

/* ═══ CHATS IS NOT A BUILD TOOL (2026-09-06) ═══════════════════════════════
   Richy, on the phone build and then on this one: "it doesn't make sense for
   chat, it shows things related to building — improve all the flow and
   design." The composer carried Plan / Build / Overnight and "I ask first,
   you approve before anything runs" in BOTH rooms: three gears for a machine
   and a promise about approving a run, over a conversation that runs nothing.

   Hidden by the body class rather than by [hidden] on the bar, because
   placeChrome() moves #modeHint between the modebar and the caption row by
   viewport width — a display rule follows it to both homes, an element flag
   on its old parent does not. Builds is untouched: everything it had, it
   keeps. */
body.is-chat-only .modebar,
body.is-chat-only .modebar-hint,
body.is-chat-only .chatcap .modebar-hint{display:none!important}

/* The "+" is a menu in Chats. It anchors to the composer, so the composer has
   to be the positioned ancestor — it had no position at all, being a plain
   flex row until today. */
.composer{position:relative}
.plusmenu{position:absolute;left:8px;bottom:calc(100% + 8px);z-index:40;
  min-width:250px;max-width:calc(100% - 16px);padding:5px;background:var(--white);
  border-radius:var(--radius);box-shadow:var(--ring-card)}
.plusmenu[hidden]{display:none}
.plusmenu-b{display:flex;align-items:center;gap:10px;width:100%;appearance:none;border:0;
  background:none;cursor:pointer;text-align:left;padding:9px 10px;border-radius:var(--radius-nav);
  font-family:var(--sans);font-size:13.5px;color:var(--obsidian)}
.plusmenu-b:hover{background:var(--sunk)}
.plusmenu-b:focus-visible{outline:2px solid var(--obsidian);outline-offset:-2px}
.plusmenu-i{display:inline-flex;align-items:center;justify-content:center;flex:none;
  width:26px;height:26px;border-radius:8px;background:var(--sunk);color:var(--stone);
  font-size:15px;line-height:1}
/* The destination wears its own room's colour, so the door is recognisable
   before it is read — the same tile the rail gives Builds. */
#plusBuildI{background:var(--studio-t);color:var(--studio-ink)}
#plusBuildI .ic{width:15px;height:15px}

/* "Want it built?" — the one card that turns a finished plan into a build.
   Quiet, one line and one button: it is an offer, not the end of the chat. */
.buildthis{display:flex;flex-wrap:wrap;align-items:center;gap:6px 12px;
  width:100%;max-width:860px;margin:8px auto 2px;padding:12px 14px;
  background:var(--white);border-radius:var(--radius);box-shadow:var(--ring-card)}
.buildthis-t{flex:1 1 auto;min-width:0;margin:0;font-family:var(--sans);font-size:13.5px;
  color:var(--charcoal)}
.buildthis-go{flex:0 0 auto;display:inline-flex;align-items:center;gap:7px;appearance:none;
  border:0;cursor:pointer;padding:8px 14px;border-radius:var(--radius-nav);
  font-family:var(--sans);font-size:13px;font-weight:600;
  background:var(--studio);color:var(--white)}
.buildthis-go .ic{width:15px;height:15px}
.buildthis-go:hover{background:var(--studio-ink)}
.buildthis-go:focus-visible{outline:2px solid var(--obsidian);outline-offset:2px}
@media(max-width:430px){
  .buildthis{padding:11px 12px}
  .buildthis-go{width:100%;justify-content:center}
}

/* ── the waiting card ─────────────────────────────────────────────────────
   Richy, 2026-09-06: "Make it very nice and appealing so that people don't get
   super bored just staring at 'thinking'." Drawn by waiting.js, which mounts
   the same card into every wait — a plan turn, a build turn, and the seconds a
   machine takes to start.

   It keeps the .work-* class names the live progress card already had, so the
   stylesheet above, the problem reporter's .work-trail read, and check-publish
   all keep working. Everything below is what is new: the surface it sits on,
   the breathing glow, the fact underneath and the progress view.

   The glow is the Studio room's tint, because a wait in the conversation is
   Studio — the same rule that gives every room one hue. It BREATHES rather
   than spins: a spinner says "wait", a slow pulse says "alive", and the whole
   point is that the seconds should not feel spent. */
.msg.ai.work.wait{position:relative;overflow:hidden;width:100%;
  max-width:calc(100% - 40px);padding:14px 16px;border-radius:var(--radius);
  background:var(--white);box-shadow:var(--ring-card);
  animation:wait-breathe 4.6s ease-in-out infinite}
@keyframes wait-breathe{0%,100%{box-shadow:var(--ring-card)}50%{box-shadow:var(--ring-card-hover)}}

/* A soft light in the corner the status sits in. Behind everything, and
   pointer-transparent, so it can never eat a click meant for the header. */
.wait-glow{position:absolute;inset:0;border-radius:inherit;pointer-events:none;z-index:0;
  background:radial-gradient(120% 150% at 8% 0%, var(--studio-t), transparent 62%);
  opacity:.5;animation:wait-glow 4.6s ease-in-out infinite}
@keyframes wait-glow{0%,100%{opacity:.24}50%{opacity:.72}}
/* Above the glow. The screen-reader line is excluded on purpose: .sr-only
   pins itself off-screen with position:absolute, and re-declaring position
   here put the status text back on the page — a second, visible copy of the
   line already in the header. */
.msg.ai.work.wait > :not(.wait-glow):not(.sr-only){position:relative;z-index:1}

/* The header is the whole control: status on the left, the way in on the
   right. A real <button>, so it takes focus and announces its own state. */
.wait-head{align-items:flex-start}
.wait-status{font-family:var(--sans);font-weight:600;font-size:14.5px;
  color:var(--obsidian);line-height:1.35}
.wait-see{display:inline-flex;align-items:center;gap:6px;flex:0 0 auto;
  white-space:nowrap;font-family:var(--sans);font-size:12px;font-weight:600;
  letter-spacing:.01em;color:var(--slate)}
/* How many steps are behind the chevron, beside it rather than inside it —
   the chevron rotates when the card opens, and a rotated number reads as a
   rendering bug. */
.wait-count{font-family:var(--mono);font-size:11px;color:var(--stone);
  font-variant-numeric:tabular-nums}
.wait-count:empty{display:none}
.wait-head:hover .wait-see{color:var(--obsidian)}
/* The label stays at every width. Hidden under 430px, the tap target's whole
   accessible name collapsed to the status line and the only hint that the card
   opens was an 8px chevron — on the card whose own body says "open it to see
   what it is doing" (breakit2, finding 16). It fits: the head is 353px on a
   393px phone and the status beside it is short, so the type is a shade
   smaller rather than the label being removed. */
@media(max-width:430px){.wait-see{font-size:11.5px;gap:4px}}

/* The fact. Crossfades rather than cuts, because a cut reads as a glitch and
   a fade reads as a page turning. */
.wait-fact{margin:9px 0 0;padding:0;font-family:var(--sans);font-size:13.5px;
  line-height:1.55;color:var(--slate);max-width:46rem;
  transition:opacity .26s ease, transform .26s ease}
.wait-fact.is-out{opacity:0;transform:translateY(3px)}
.wait-fact[hidden]{display:none}

/* The clock and the meter. Hidden for the first fifteen seconds: a timer on a
   two-second wait invents an anxiety that was not there. */
.wait-meta{margin-top:10px}

/* The progress view, in place of the fact. */
.wait-prog{margin-top:11px;padding-top:10px;border-top:1px solid var(--hairline)}
.wait-prog .work-trail{margin-top:0;padding-top:0;border-top:0}
/* The step it is on right now — the only pulsing thing in the list. */
.wait-now{display:flex;align-items:baseline;gap:8px;font-family:var(--sans);
  font-weight:600;font-size:12px;letter-spacing:.02em;line-height:1.65;
  color:var(--obsidian);white-space:pre-wrap}
.wait-now::before{content:"";flex:0 0 auto;width:6px;height:6px;align-self:center;
  border-radius:var(--radius-pill);background:var(--studio);
  animation:work-pulse 1.25s ease-in-out infinite}
/* Where a wait genuinely has no steps, it says so rather than inventing one. */
.wait-honest{margin:9px 0 0;font-family:var(--sans);font-size:12.5px;
  line-height:1.5;color:var(--stone)}
.wait-honest[hidden]{display:none}

@media(prefers-reduced-motion:reduce){
  .msg.ai.work.wait{animation:none;box-shadow:var(--ring-card)}
  .wait-glow{animation:none;opacity:.4}
  .wait-fact{transition:none}
  .wait-now::before{animation:none;opacity:.85}
}

/* ═══ THE PHONE, MEASURED (2026-09-06) ═══════════════════════════════════════
   A mobile-browser sweep of every room in both themes (report: contrast on 39
   nodes across six screens, two tab strips hiding real tabs, and the only door
   to the model chooser sitting on the home-indicator). Everything here is one
   of those findings; check-a11y.mjs fails if any of them comes back.

   One block at the end of the file on purpose: each fix is a small override of
   a rule written elsewhere for a different reason, and scattering them through
   the file would have made every one of them a merge conflict. */

/* ── contrast ───────────────────────────────────────────────────────────────
   --smoke and --graphite were darkened at the token level, which fixes every
   plain text use at once. These two are the exception the token cannot reach:
   the ground is --hairline, not --paper, and even the corrected --smoke reads
   3.77:1 on it. --stone is 5.10:1 and is what every other chip already uses. */
.xtab{color:var(--stone)}
.dconn-state{color:var(--stone)}
/* Ten pixels was the smallest text anywhere in the product, on a label that is
   also one of the contrast failures. Twelve, uppercase, still a badge. */
.dmodel-t{font-size:12px}

/* ── the scroll strips say they scroll ──────────────────────────────────────
   .rtabs and .rec-pills hide their scrollbar and clipped tabs mid-word with no
   cue at all: on a 360px phone Library's "Automations" and Account's "Keys"
   were invisible unless somebody swiped by accident.

   Two gradients per side, and no JavaScript. The `local` pair scrolls WITH the
   content and paints the strip's own ground; the `scroll` pair is fixed to the
   box and paints a shadow. At rest the ground covers the shadow; the moment
   there is content off that edge the ground has scrolled away and the shadow
   shows. So the hint appears only on the side that is actually hiding
   something — which a plain mask cannot do, and which was the whole complaint. */
.rtabs{
  background:
    linear-gradient(to right, var(--sunk) 45%, rgba(0,0,0,0)) left center / 26px 100% no-repeat local,
    linear-gradient(to left,  var(--sunk) 45%, rgba(0,0,0,0)) right center / 26px 100% no-repeat local,
    radial-gradient(farthest-side at 0 50%, var(--edge-hint), rgba(0,0,0,0)) left center / 13px 100% no-repeat scroll,
    radial-gradient(farthest-side at 100% 50%, var(--edge-hint), rgba(0,0,0,0)) right center / 13px 100% no-repeat scroll,
    var(--sunk);
  scroll-snap-type:x proximity;
  scroll-padding:0 26px;
}
.rec-pills{
  background:
    linear-gradient(to right, var(--paper) 45%, rgba(0,0,0,0)) left center / 24px 100% no-repeat local,
    linear-gradient(to left,  var(--paper) 45%, rgba(0,0,0,0)) right center / 24px 100% no-repeat local,
    radial-gradient(farthest-side at 0 50%, var(--edge-hint), rgba(0,0,0,0)) left center / 12px 100% no-repeat scroll,
    radial-gradient(farthest-side at 100% 50%, var(--edge-hint), rgba(0,0,0,0)) right center / 12px 100% no-repeat scroll;
  scroll-snap-type:x proximity;
  scroll-padding:0 24px;
}
/* Proximity, not mandatory: a stopped scroll still never lands mid-label, and
   app.js's scrollIntoView for the selected tab is not fought by the snap. */
.rtab{scroll-snap-align:center}
.rec-pill{scroll-snap-align:center}
/* Both strips are rows of real <button>s, so every tab is already a tab stop —
   what was missing is seeing where the focus went inside a clipped box. */
.rtab:focus-visible,.rec-pill:focus-visible{outline:2px solid var(--obsidian);outline-offset:-1px}

/* ── the finger ─────────────────────────────────────────────────────────────
   Measured on an iPhone 15 Pro: .rtab 32px tall, .rec-pill 26.6, the top-bar
   icons 40x40, the composer's attach 18x27 and mic 27x24, the mode pills 21
   tall. Padding and min-height only — nothing here shrinks to fit, and no
   visible font size changes except where it was under the 14px floor. */
@media(pointer:coarse){
  .rtab{min-height:44px;display:inline-flex;align-items:center}
  .rec-pill{min-height:44px;display:inline-flex;align-items:center;font-size:14px}
  .menubtn,.newtop,.sharetop{width:44px;height:44px}
  .composer .attach,.composer .mic,.composer .send,.composer .stop{
    min-width:44px;min-height:44px;display:inline-grid;place-items:center}
  /* The mode pills get a 44px TARGET without a 44px pill.
     Growing the box was the first attempt and it was wrong: at 44x44 a
     three-letter uppercase label becomes a circle, the row wraps to two lines,
     and the composer — the one surface Richy asked to be less cluttered —
     gained 40px of height to fix an accessibility number. What the criterion
     actually asks about is the area that RESPONDS to a tap, so that is what
     grows. The pseudo-element is a child of the button, so a press anywhere in
     it is a press on the button, and the pill is drawn exactly as designed.

     24px, not 44, and that is the honest number. The mode row WRAPS on a
     393px phone — Plan and Build on one line, Overnight under them, 3px
     apart — so a 44px area would reach into the pill below it and the row
     below would win the tap. 24x24 is WCAG 2.2 AA's SC 2.5.8 floor, it is
     what fits between two wrapped rows, and it is what these three get; the
     tab strips and the composer's icon buttons are at the AAA 44. Raising
     these to 44 needs the mode row to stop wrapping, which is a composer
     redesign and not an accessibility fix. */
  .composer.has-modes .modebar-b{position:relative}
  .composer.has-modes .modebar-b::after{content:"";position:absolute;
    left:-2px;right:-2px;top:50%;height:24px;transform:translateY(-50%)}
}

/* ── the line above the home indicator ──────────────────────────────────────
   #chatFine — the only way into Model & cost from an open conversation —
   measured 261x26 at top 822 of an 852pt viewport: four pixels of clearance,
   under the gesture band, and 18px under the tap minimum on its own.

   The safe-area inset was already asked for here and was always zero, because
   the page never said viewport-fit=cover. app.html says it now. */
@media(max-width:820px){
  .view.chat{padding-bottom:calc(12px + env(safe-area-inset-bottom,0px))}
  /* 14px is the floor for secondary copy. It wraps on a narrow phone, which is
     what a block-level centred line is for. */
  .app-fine{font-size:14px}
  /* .chatcap .chatfine, not .chatfine: the caption row sets the size at two
     classes deep, which outranks a single class however late it appears. A
     one-class rule here looked right and did nothing. */
  .chatcap .chatfine,.chatfine{font-size:14px;line-height:1.3;padding:6px 10px;min-height:44px;
    display:flex;align-items:center;justify-content:center;box-sizing:border-box}
  .chatcap .chatswap,.chatswap{font-size:14px;min-height:44px;padding:6px 10px}
}

/* Same as paper.css's block: --brand as a text colour is 3.53:1 on the dark
   ground. --brand-ink is the token whose whole job is this. */
.modebar-b.modebar-new{color:var(--brand-ink)}
.bestcard-use.is-on{color:var(--brand-ink)}
.tune-more .tune-row-n{color:var(--brand-ink)}

/* ═══ FINISHED, AND FRIENDLY TO A BEGINNER (2026-09-06) ══════════════════════
   Richy, on a 1999px desktop screenshot of /app/sessions: "the Chats screen
   along with all the others have the title basically touching the top, which
   is weird. Fix any UI issues also. Check the full platform and fix all UI
   issues you can find; it should be super user friendly for beginners."

   One block, in one place, so the whole answer to that sentence can be read —
   and reverted — together. Every rule here is scoped to a breakpoint where it
   has to be, because appending to this file means winning every earlier
   media query by declaration order alone. ─────────────────────────────────── */

/* 1 ── Air under the top bar, for every room at once.
   MEASURED cause, not guessed: `.shell section{padding:0}` — added upstream to
   cancel paper.css's `section{padding:96px 0}` inside the app — also cancels
   `.dash`'s own `padding:24px 0 64px`, because `.shell section` (0,1,1)
   outweighs `.dash` (0,1,0) and comes later. So EVERY dashboard room lost its
   top and bottom padding in one stroke, which is why the fix belongs on the
   scroller and not on each room's header. */
.shell .view.dash{padding-top:26px;padding-bottom:64px}
@media(max-width:820px){
  /* The phone header already carries 12px of its own (.roomhead margin-top). */
  .shell .view.dash{padding-top:6px}
}
/* The conversation room keeps its header inside .view.chat, which the
   edge-to-edge rules had pinned to padding-top:0 — so the first line of a
   transcript sat against the bar for exactly the same reason. */
@media(min-width:821px){
  .shell .view.chat{padding-top:8px}
}

/* 2 ── Read, not shouted.
   The header pill said "38¢ SPENT THIS MONTH · $11.62 LEFT" — a sentence in
   11px small-caps with letter-spacing, which is a label's treatment applied to
   something a beginner has to actually read. Sentence case here, and the same
   for the readouts beside it: those hold proper names ("GPT-5.6 Luna"), which
   capitalising mangles. */
.mstate{text-transform:none;letter-spacing:0}
@media(min-width:821px){
  #allowance.mstate{font-size:12px}
}
/* The project switcher and the chat switcher are one control in two rooms, and
   only the chat one had dropped its caps. Two shapes for one action IS the
   defect. */
.projbtn{text-transform:none;letter-spacing:0}
@media(min-width:821px){
  .projbtn{font-size:12.5px}
}
/* The activity strip's caption, and its toggle — which was the only underlined
   caps text link left in the app, next to a screen full of pill buttons. */
.act-h{text-transform:none;letter-spacing:0;font-size:13px}
.act-toggle{text-transform:none;letter-spacing:0;font-size:12.5px;text-decoration:none;
  box-shadow:var(--ring-ghost);border-radius:var(--radius-pill);padding:4px 11px}
.act-toggle:hover{box-shadow:var(--ring-card-hover);color:var(--obsidian)}
.act-toggle:focus-visible{outline:2px solid var(--obsidian);outline-offset:2px}

/* 3 ── One measure for every room's opening sentence.
   .dash-sub was capped at 44rem and .dash-who was not, so Memory's intro ran
   the full 1096px column while the card directly beneath it stopped at 704 —
   two different measures, one under the other. */
.dash-who{max-width:44rem}

/* 4 ── An empty panel is not as tall as a full one.
   A project card lays Chats and Builds side by side in a grid, and grid items
   stretch: a project with five builds and no chats drew a 300px slab of
   colour holding one sentence. */
.repo-sess{align-items:start}

/* 5 ── Every row that opens something says so under the keyboard too.
   These already had :hover and cursor:pointer; none of them had a focus ring,
   so the whole app was unusable by keyboard without guessing. */
.sess-row:focus-visible,.tune-row:focus-visible,.side-sess:focus-visible,
.repo-col-go:focus-visible,.mem-x:focus-visible,.act-c:focus-visible{
  outline:2px solid var(--obsidian);outline-offset:-2px;border-radius:7px}
/* A scroller clips an outline drawn outside the box, so the rings above are
   inset. The list itself needs the room to draw them. */
.sess-list{padding:0 1px}

/* 6 ── The lead sentence of the Save money pane had no gap under it, so it sat
   on top of the first group's heading; and its button now sits beside it
   rather than three thousand pixels below the sentence naming it. */
.dash-instead > .app-fine:first-child{margin:0 0 10px}
.instead-act{margin:0 0 22px}

/* 7 ── The drawer's two-line cards were cut off mid-word, with no ellipsis.
   `.repo-acts a,.repo-acts button` sets overflow:hidden + text-overflow:ellipsis
   + white-space:nowrap, which is right for the PILL row it was written for.
   `.repo-tool` is a two-line CARD inside the same container, so it inherited
   both: its caption could not wrap, and the parent's overflow cut the sentence
   in the middle of a word — "invite by email — they need no plan of thei".
   (text-overflow only ellipsises a box's own inline content, never an
   overflowing child, which is why there was not even a "…" to warn you.) */
.repo-acts .repo-tool,
.repo-acts .repo-tool b,
.repo-acts .repo-tool small{white-space:normal;overflow:visible;text-overflow:clip}

/* 8 ── The same label, in the same words, in two registers.
   The Map's recommendation card said "A WEBSITE" and the Library's board said
   "A website" — one string, from one endpoint, shouted in one room and spoken
   in the other. Spoken in both, at the size that already made it an eyebrow. */
.drec-j{text-transform:none;letter-spacing:.02em;font-size:11.5px}

/* 9 ── A label between two card grids needs the gap under it, not over it.
   The marketplace's "Published by people here" was borrowing .lib-s2, whose
   margin-top:-6px exists to tuck a second LEAD line under the first — so the
   label sat three pixels below the grid above it and twenty-two above the one
   it was naming. */
.lib-mid{margin:22px 0 10px;font-weight:600;color:var(--charcoal)}

/* The project named in the address — /app/projects/<slug>. Scrolled to and
   ringed, so a link that names one project does not land on a list of thirty
   with nothing saying which one it meant. */
.repo.is-deep{box-shadow:0 0 0 2px var(--brand);}

/* Why a connection cannot be made — beside the button that will not work.
   A disabled control with no reason on it gets answered by guessing, and
   people guess in whichever direction costs them most. */
.dconn-why{margin:10px 0 0;font-size:12px;line-height:1.5;color:var(--stone)}

.owner-admin-loading{max-width:1100px;margin:24px auto;padding:0 24px}
.owner-admin-links{flex-wrap:wrap;align-items:center}
@media(max-width:600px){body > header.owner-admin-head{align-items:flex-start;flex-direction:column}.owner-admin-cols{grid-template-columns:minmax(0,1fr)}.owner-admin-row{flex-wrap:wrap}.owner-admin-row .owner-admin-m{flex:auto;overflow-wrap:anywhere}}

.flows-list{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,240px),1fr));gap:12px;margin:16px 0 24px}.flows-screen{border:1px solid var(--hairline);border-radius:12px;padding:16px;min-width:0}.flows-screen h3{font-size:17px;margin:0 0 8px}.flows-screen p{overflow-wrap:anywhere}.flows-screen .btn{margin:8px 8px 0 0;min-height:44px}.flows-search{display:grid;gap:8px;max-width:480px}.flows-search input{min-height:44px;padding:10px 12px;border:1px solid var(--hairline);border-radius:8px;background:var(--paper);color:var(--obsidian)}.flows-connections summary{cursor:pointer;min-height:44px;padding:12px 0}.flows-graph{height:650px;max-height:75vh}#flowsMount{overflow:auto}
/* ── Get the phone app — the sidebar card (2026-09-11) ─────────────────────
   Shown by get-app.js on desktop browsers and in the desktop app only. Sits
   just above the pinned foot; a remembered "Hide" takes it away for good. */
.side-getapp{margin:10px 0 4px;padding:10px 10px 6px;border:1px solid var(--hairline);border-radius:12px;
  display:flex;flex-direction:column;align-items:center;gap:6px;text-align:center}
.side-getapp[hidden]{display:none}
.side-getapp-t{margin:0;font-size:12.5px;font-weight:600;color:var(--obsidian)}
.side-getapp-qr{display:block;border-radius:8px;background:#fff}
.side-getapp-b{display:block;line-height:0}
.side-getapp-b img{display:block;height:auto}
.side-getapp-x{background:none;border:0;padding:4px 8px;font:inherit;font-size:12px;color:var(--stone);cursor:pointer}
.side-getapp-x:hover{color:var(--obsidian)}
