/* Krate Studio -- the same language as krate.tech.
 *
 * Read from the site's own stylesheet rather than invented. What it
 * actually does, and what this file therefore does:
 *
 *   FLAT. The entire site uses two box-shadows. Surfaces are a panel
 *   colour and a 1px border -- no elevation ladder, no layered shadows, no
 *   gradients on cards. An earlier pass here built four elevations with
 *   contact shadows; that is a different product's design language.
 *
 *   MOTION CARRIES THE PERSONALITY. Everything arrives with
 *   translateY(14px) + opacity over 0.5s ease, staggered. Hovers are
 *   0.15s on colour or border only. The site has exactly one decorative
 *   animation -- a gradient bar sweeping under the rotating hero word --
 *   and it earns its place by being the only one.
 *
 *   TYPE IS THE HIERARCHY. Geist 500, tight negative tracking, big jumps
 *   between display and body. Not weight-and-colour mush.
 *
 * Every value below is the site's, matched deliberately.
 */

@font-face { font-family: "Geist"; src: url("fonts/geist-400.woff2") format("woff2"); font-weight: 400; font-display: swap; }
@font-face { font-family: "Geist"; src: url("fonts/geist-500.woff2") format("woff2"); font-weight: 500; font-display: swap; }

:root {
  /* The site's own tokens, unchanged. */
  --bg: #0a0a0a;
  --panel: #0f1012;
  --panel-raised: #16171b;
  --line: #1f2228;
  --line-strong: #2e323a;
  --text: #ffffff;
  /* Each step still reads. Auditing the light theme turned up the same
     problem here, which is easy to miss on a good screen in a dark room and
     obvious on a laptop in daylight: --faint measured 2.8:1 and --ghost
     1.6:1 against the panels they sit on, under the 3:1 floor for text.
     Both carry real words -- hints, placeholders, capability labels, the
     sub-line under every settings row. */
  --muted: rgba(255, 255, 255, 0.62);
  --faint: rgba(255, 255, 255, 0.42);
  --ghost: rgba(255, 255, 255, 0.36);
  /* Krate's own replies: the main content, so nearly full strength. A
     hardcoded white here was invisible on the light theme (K-822). */
  --reply: rgba(255, 255, 255, 0.92);
  /* Eight rules say `var(--quiet, rgba(255,255,255,0.35))` and the token was
     never actually defined, so all eight took the white fallback -- correct
     in the dark and invisible on paper. Defining it here and in the light
     palette fixes every one of them at once. */
  --quiet: rgba(255, 255, 255, 0.42);
  --blue: #6291ff;
  --blue-soft: #a9c2ff;
  --cyan: #6cf4d7;
  --yellow: #f4dc74;
  --danger: #ff7f95;

  --sans: "Geist", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  --mono: "SFMono-Regular", "Cascadia Code", Menlo, Consolas, monospace;

  --radius: 12px;
  --radius-card: 14px;
  --radius-pill: 999px;

  /* ---- glass ----------------------------------------------------------
   * One material, defined once, worn by the rail, the composer, the dock
   * and the cards. Four properties make it read as glass rather than as a
   * grey box with see-through turned on:
   *
   *   tint       barely there. Glass is mostly what is behind it.
   *   blur       enough to smear text underneath into colour, not shapes.
   *   saturate   what a real lens does: colour behind it comes back richer,
   *              and without this the blur greys everything and looks like
   *              fog on a window rather than glass.
   *   highlight  a 1px inner line along the TOP edge only, which is the
   *              lit rim of a pane catching light from above. This is the
   *              single detail that separates Apple's material from a flat
   *              translucent panel, and it is why their inputs look
   *              physical at any size.
   *
   * Two strengths, because chrome and content want different things. The
   * chrome (rail, dock, composer) is thinner and blurs harder -- it floats
   * over the work. Cards are denser: they hold text that has to stay
   * crisply readable, and a card you can see through is a card with no
   * hierarchy. That is why the references reserve heavy glass for chrome.
   */
  --glass-chrome: rgba(255, 255, 255, 0.045);
  --glass-card: rgba(255, 255, 255, 0.028);
  --glass-blur: blur(24px) saturate(1.7);
  --glass-blur-soft: blur(16px) saturate(1.4);
  --glass-edge: rgba(255, 255, 255, 0.085);
  --glass-edge-soft: rgba(255, 255, 255, 0.055);
  --glass-lit: rgba(255, 255, 255, 0.09);

  /* macOS overlays its traffic lights on our content; a fixed strip height
     and a reserved run mean the top of the window is aligned by
     construction, not by a per-screen padding guess. */
  --titlebar: 60px;
  /* The traffic lights are placed at x:20 vertically centred in the bar
     (tauri.conf trafficLightPosition), so the brand clears them by
     construction and sits on the SAME row -- not diagonally under them. */
  --lights: 78px;
  --rail: 320px;
  /* The dock's own height plus the air under and over it. Every scrolling
     page reserves exactly this, so content stops in the same place on all
     of them -- three hand-picked paddings is why they did not. */
  --dock-clear: 108px;
}

* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { height: 100%; }
body {
  background: var(--bg);
  color: var(--text);
  font-family: var(--sans);
  font-size: 14px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  overflow: hidden;
  user-select: none;
}

/* The mark, as the light behind the thing you came to use.
 *
 * krate-logo.png itself, scaled up and blurred. Not a redrawing of it.
 *
 * This was drawn by hand in SVG five times and it was wrong every time --
 * slabs pointing the wrong way, no thickness on their sides, the roof
 * floating off the stack. Blur hid the faults from me and not from anyone
 * looking at it. The logo is a rendered 3D object with real shading, and
 * reproducing that in flat vector paths is a worse job than simply using
 * the file: this way the shape, the depth and the colour are exactly the
 * brand's, because they ARE the brand's.
 *
 * The cost is that a bitmap softens when scaled. At this blur that is not
 * a cost at all -- the whole point is that it is soft. */
.home-hero { position: relative; }
.home-hero::before {
  content: "";
  position: absolute;
  width: min(74vh, 74vw);
  aspect-ratio: 1;
  left: 50%;
  top: 50%;
  /* Lifted, because the mark's weight is low: its roof is mostly empty
     space, so centring the box leaves the slabs sitting near the floor. */
  transform: translate(-50%, -60%);
  z-index: -1;
  pointer-events: none;
  background-image: url("krate-logo.png");
  background-repeat: no-repeat;
  background-position: 50% 50%;
  background-size: contain;
  /* Soft enough to be lighting, sharp enough to still be the mark. */
  filter: blur(30px) saturate(1.25);
  opacity: 0.16;
}
body.light .home-hero::before { opacity: 0.10; }

/* Everything the app draws sits above the lighting. */
.view, .win-controls { position: relative; z-index: 1; }
/* Buttons inherit nothing by default -- not colour, not font. That is how
   card titles ended up black on near-black. */
button { font: inherit; color: inherit; letter-spacing: inherit; }

.view { height: 100vh; display: flex; flex-direction: column; }
.hidden { display: none !important; }

/* ---- reveal: the site's one motion idiom ------------------------------ */

/* Reveal is driven by inline styles from JS, not by a class pair.
   `.reveal.in { opacity: 1 }` failed to win the cascade here and left the
   headline permanently invisible -- and a blank hero is a far worse
   outcome than a missing animation, so the state is set where nothing can
   override it. The class only marks WHICH elements take part. */
.reveal { transition: opacity 0.5s ease, transform 0.5s ease; }
@media (prefers-reduced-motion: reduce) {
  .reveal { transition: none; }
}

/* ---- type ------------------------------------------------------------- */

.display {
  font-size: clamp(34px, 4.4vw, 52px);
  font-weight: 500;
  letter-spacing: -0.028em;
  line-height: 1.04;
}
.eyebrow {
  font-size: 11px; font-weight: 500;
  letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--cyan);
}
.label { font-size: 11px; font-weight: 500; letter-spacing: 0.12em; text-transform: uppercase; color: var(--faint); }

/* ---- buttons: flat, border-only, colour on hover ---------------------- */

.btn {
  white-space: nowrap;
  font-size: 13.5px; font-weight: 500;
  color: var(--text);
  background: transparent;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-pill);
  padding: 9px 20px;
  cursor: pointer;
  transition: border-color 0.15s, background 0.15s, color 0.15s;
}
.btn:hover { border-color: rgba(255,255,255,0.4); }

.btn-primary { background: var(--text); color: #0a0a0a; border-color: var(--text); }
.btn-primary:hover { background: var(--blue-soft); border-color: var(--blue-soft); }

.btn-ghost { border-color: transparent; color: var(--muted); }
.btn-ghost:hover { color: var(--text); border-color: transparent; background: var(--film); }
.btn-big { padding: 12px 28px; font-size: 14.5px; }

.icon-btn {
  display: grid; place-items: center;
  width: 30px; height: 30px;
  background: none; border: none;
  border-radius: 8px;
  color: var(--faint);
  cursor: pointer;
  transition: color 0.15s, background 0.15s;
}
.icon-btn:hover { color: var(--text); background: var(--film); }

/* ---- titlebar --------------------------------------------------------- */

.titlebar {
  position: relative;
  height: var(--titlebar); flex: none;
  display: flex; align-items: center;
  gap: 12px;
  /* The corner belongs to us on every platform. */
  padding: 0 16px 0 12px;
  /* No rule under the bar. The window is one surface: a line here draws a
     header that is not really there, and every app worth copying (Codex,
     ChatGPT) lets the content start without announcing itself. */
  -webkit-app-region: drag;
}
/* A drag layer covering the whole strip, behind the controls. Relying on
   the bar's own background left only the gaps between elements grabbable,
   so the window was effectively immovable by mouse. */
.titlebar::before {
  content: "";
  position: absolute; inset: 0;
  -webkit-app-region: drag;
  z-index: 0;
}
.titlebar > * { position: relative; z-index: 1; }
/* Only the CONTROLS opt out of dragging. The blanket `.titlebar > *` rule
   also un-dragged the brand and the title text, which left barely any
   grabbable strip -- the window could not be moved with the mouse. */
.titlebar > button, .titlebar > .agent-chip { -webkit-app-region: no-drag; }
.titlebar .spacer { flex: 1; height: 100%; -webkit-app-region: drag; }

/* Our own minimize / maximize / close, shown only where the native frame is
   removed (Windows). Fixed to the window's top-right corner, above every
   view, sized to the Windows caption-button convention. */
.win-controls {
  position: fixed; top: 0; right: 0; z-index: 60;
  display: flex;
  -webkit-app-region: no-drag;
}
.win-controls button {
  width: 46px; height: 34px;
  display: flex; align-items: center; justify-content: center;
  border: none; background: transparent; color: var(--muted);
  cursor: default;
}
.win-controls button:hover { background: var(--panel-raised); color: var(--text); }
#winClose:hover { background: #e81123; color: #fff; }
.win-controls.hidden { display: none; }

/* No traffic lights on Windows or Linux: the reserved run would just be a
   hole, and it is what made the bar's content look shoved diagonally away
   from the corner. The brand starts at normal padding instead, and on
   Windows the right side clears our own window buttons. */
/* macOS draws its traffic lights over our content. The bar keeps its
   normal height and normal centring -- growing it to 74px and bottom
   aligning everything bought a taller header and a row that sat low, for
   no gain: the lights are moved out of the way in tauri.conf instead
   (trafficLightPosition), so nothing has to dodge them. */
/* The lights occupy x:20 to about x:74. Our row starts after them, on the
   bar's own centre line -- no taller header, no bottom alignment. The
   drawer is a separate surface with no lights over it, so it keeps the
   corner. */
/* The drawer owns the corner when it is open, so the room's bar starts
   after it; when it is closed the room's own bar clears the lights. */
/* The room's title bar rides 8px below the traffic lights, and the
   drawer's strip does not -- both are --titlebar tall and both centre, so
   the difference comes from what wraps them, not from the rows.
   
   Measured off screen captures of the running window, twice, stable:
     drawer toggle  centre 122.0 vs dots 171.5 -> +0.25 CSS px  (aligned)
     room toggle    centre 187.5 vs dots 171.5 -> +8.00 CSS px  (low)
   
   So only the room's bar is corrected, by exactly what was measured. The
   bar keeps its height -- the room below depends on it -- and only its
   controls move onto the lights' line. */
body.macos .titlebar { padding-left: 84px; }
body.macos .titlebar > * { position: relative; top: -6px; }
body.macos.side-open .titlebar { padding-left: 12px; }
/* The drawer's top strip clears the traffic lights the same way. */
/* macOS only: the traffic lights are drawn over the drawer's first row.
   That row is left EMPTY for them -- the panel toggle sits at its right,
   on the lights' own line -- and the Krate mark moves to its own row
   underneath, where nothing is drawn over it. Trying to fit the mark
   beside the lights on one row is what kept it looking half a line off:
   a 22px logo and three 12px dots do not share a baseline. */
/* The lights row, sized from a measurement rather than a guess.
 *
 * Probed in the running window: outerHeight - innerHeight shows the
 * webview fills the whole window, so there is NO frame inset above us and
 * trafficLightPosition is already in our coordinates. The lights' top is
 * y:24 and they are 12px tall, so their centre is y:30. A row that centres
 * its content puts that content at height/2, so the row must be 60.
 *
 * (This was briefly 48, from misreading a screenshot; that put our glyph
 * at y:24 -- level with the TOP of the lights instead of their middle.) */
body.macos .side-top {
  height: 60px; padding-left: 78px;
  align-items: center;
}
/* The panel toggle sits on the same line as the traffic lights -- measured
   from a screen capture, the two centres are 0.4 CSS px apart, so this is
   not a position problem.
   
   It LOOKED low because of weight, not placement: three solid saturated
   circles beside a 1.35px grey stroke at 32% opacity, and the eye reads
   the lighter mark as smaller and lower. Brought up to a legible weight
   so it holds the line as their peer. */
body.macos .side-top .side-toggle,
body.macos .titlebar .side-toggle { color: rgba(255,255,255,0.62); }
body.macos .side-top .side-toggle:hover,
body.macos .titlebar .side-toggle:hover { color: #fff; }
/* The same weight in ink. Without this the rule above wins in light mode
   too and the drawer button is white on paper -- measured 1.02:1, which is
   invisible, in five different views. */
body.macos.light .side-top .side-toggle,
body.macos.light .titlebar .side-toggle { color: rgba(13, 16, 21, 0.70); }
body.macos.light .side-top .side-toggle:hover,
body.macos.light .titlebar .side-toggle:hover { color: #0d1015; }
body.macos .side-mark {
  padding-left: 10px;
}
body.windows .titlebar { padding-right: 150px; }

/* Windows: our controls share the caption buttons' line.
 *
 * The caption buttons are 34px tall pinned at top:0, so their centre is at
 * y=17. The titlebar is 60px and centres its own children, putting them at
 * y=30 -- thirteen pixels low, which reads as two rows that were meant to be
 * one. Every app people compare this to (ChatGPT, VS Code, Explorer) keeps
 * the window's own buttons and the app's chrome on a single line.
 *
 * Aligning to the top and pushing down by (34-30)/2 lands our 30px buttons
 * on exactly the caption buttons' centre line. */
/* Centre everything in a SHORTER bar rather than top-aligning it.
 *
 * The first version used flex-start with a 2px nudge on each child, which
 * lines up things that are all the same height and skews everything else:
 * a 30px button landed on centre 17 while the "Shared" text, being 20px
 * tall, landed on 12 -- five pixels high, and visible.
 *
 * The caption buttons are 34px at top:0, so their centre is 17. A 34px row
 * that centres its children puts EVERY child on 17 whatever its height. */
body.windows .titlebar {
  align-items: center;
  /* The row is 34px so its children centre on the caption buttons' line,
     and the rest of the 60px is padding so the content below still starts
     where it did. Shrinking the whole bar moved every page up by 26px. */
  height: var(--titlebar);
  padding-bottom: calc(var(--titlebar) - 34px);
}
body.windows .titlebar .spacer { height: 34px; }

/* And they have to be VISIBLE.
 *
 * --faint is deliberately quiet, which works beside a macOS traffic light
 * but not beside Windows' own caption glyphs: those are drawn at full
 * contrast by the system, so a 1.35px stroke at --faint next to them looks
 * like a rendering fault rather than a button. macOS already got this
 * treatment (the .side-toggle rules above); Windows never did. */
body.windows .titlebar .icon-btn,
body.windows .titlebar .side-toggle { color: rgba(255, 255, 255, 0.72); }
body.windows .titlebar .icon-btn:hover,
body.windows .titlebar .side-toggle:hover { color: #fff; }
body.windows.light .titlebar .icon-btn,
body.windows.light .titlebar .side-toggle { color: rgba(12, 16, 23, 0.75); }
body.windows.light .titlebar .icon-btn:hover,
body.windows.light .titlebar .side-toggle:hover { color: #000; }

.brand {
  display: flex; align-items: center; gap: 9px;
  /* Optical, not mathematical: the wordmark's letter-spacing adds a trailing
     gap of its own, so matching the lights' spacing exactly reads as too far
     right. */
  margin-left: -6px;
  font-size: 12.5px; font-weight: 500; letter-spacing: 0.15em;
  color: var(--text);
  -webkit-app-region: drag;
}
/* The wordmark is uppercase, so its optical centre is its cap-height -- above
   the line box's true middle. `align-items: center` therefore leaves the glyph
   looking low next to it. The nudge is optical, which is why it is not a round
   number. */
/* Full opacity and bigger: at 17px and 0.9 the mark was a grey smudge
   next to the wordmark rather than a logo anyone could read. */
/* No optical nudge: the drawer's mark has none, and a 1px lift here is
   exactly 1px of disagreement across the seam. */
.brand img { width: 22px; height: 22px; opacity: 1; }
.crumb {
  font-size: 13px; color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 42ch;
}
.avatar { width: 24px; height: 24px; border-radius: 50%; display: block; }
#accountBtn { padding: 0; overflow: hidden; }

.agent-chip {
  display: flex; align-items: center; gap: 8px;
  height: 30px; padding: 0 13px;
  background: transparent;
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  color: var(--muted);
  font-size: 12px; font-weight: 500;
  cursor: pointer;
  transition: border-color 0.15s, color 0.15s;
  max-width: 210px;
}
.agent-chip:hover { border-color: var(--line-strong); color: var(--text); }
.agent-chip span:last-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.dot { width: 6px; height: 6px; flex: none; border-radius: 50%; background: var(--ghost); }
.dot.ok { background: var(--cyan); }
.dot.warn { background: var(--yellow); }
.dot.bad { background: var(--danger); }

/* ---- the gradient sweep: the site's one signature ---------------------- */

/* A coloured band travelling under a line of type. On the site it sits
   under the rotating hero word; here it marks whatever is happening now.
   It is the only decorative animation in the product, which is exactly
   why it reads as deliberate. */
.sweep {
  height: 2px;
  border-radius: var(--radius-pill);
  background-image: linear-gradient(90deg,
    rgba(255,255,255,0.16) 0%, rgba(255,255,255,0.16) 35%,
    #6366f1 40%, #a855f7 45%, #ec4899 50%, #f97316 55%, #eab308 60%,
    rgba(255,255,255,0.16) 65%, rgba(255,255,255,0.16) 100%);
  background-size: 300% 100%;
  animation: sweep 3.2s linear infinite;
}
@keyframes sweep {
  0% { background-position: 120% 0; }
  100% { background-position: -120% 0; }
}
@media (prefers-reduced-motion: reduce) { .sweep { animation: none; } }

/* ---- gate ------------------------------------------------------------- */

.gate-body { flex: 1; display: grid; place-items: center; padding: 24px; }
.gate-card { width: min(420px, 92%); text-align: center; }
.gate-card > * { margin-bottom: 0; }
.gate-mark { width: 34px; height: 34px; margin: 0 auto 26px; opacity: 0.9; }
.gate-card h1 { font-size: 30px; font-weight: 500; letter-spacing: -0.028em; line-height: 1.1; }
.gate-sub { color: var(--muted); font-size: 14.5px; margin: 12px auto 32px; max-width: 34ch; }
.gate-hint { color: var(--faint); font-size: 12px; margin: 18px auto 0; max-width: 34ch; line-height: 1.55; }
.gate-step { color: var(--muted); font-size: 13px; }
.gate-code {
  font-family: var(--mono); font-size: 38px; font-weight: 500;
  letter-spacing: 0.18em; margin: 16px 0 6px;
}
.gate-sweep { width: 160px; margin: 14px auto 0; opacity: 0.9; }
.gate-error { color: var(--danger); font-size: 13px; margin-top: 16px; }

/* ---- home ------------------------------------------------------------- */

.home {
  flex: 1; overflow-y: auto; overflow-x: hidden;
  display: flex; flex-direction: column; align-items: center;
  /* The hero is the only thing on this page now, so the column centres it
     and the bottom padding is the dock's clearance -- not a guess. */
  justify-content: center;
  padding: 0 32px var(--dock-clear);
}
.home-hero { width: 100%; display: flex; flex-direction: column; align-items: center; padding: 0; }

/* The hail: the mark and the person's name, small, above the question.
   Left-aligned with the composer rather than centred, so the eye starts
   at the same x as the box it is about to type in. */
.home-hail {
  width: min(680px, 100%);
  display: flex; align-items: center; gap: 9px;
  font-size: 15px; color: var(--muted); margin-bottom: 6px;
}
.home-hail img { width: 19px; height: 19px; display: block; flex: none; }

.home-title { text-align: left; width: min(680px, 100%); }
.home-sub { color: var(--muted); font-size: 15px; margin: 14px 0 34px; text-align: center; }

.composer-shell { width: min(600px, 100%); }
.ideas { display: flex; gap: 8px; margin-top: 18px; flex-wrap: wrap; justify-content: center; }
.idea {
  font-size: 12.5px;
  color: var(--muted);
  background: transparent;
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  padding: 7px 15px;
  cursor: pointer;
  transition: border-color 0.15s, color 0.15s;
}
.idea:hover { border-color: var(--line-strong); color: var(--text); }

.apps { width: min(940px, 100%); margin-top: 12vh; }
.apps-head { margin-bottom: 18px; }
.apps-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(236px, 1fr)); gap: 16px; }

.app-card {
  position: relative;
  text-align: left;
  /* Card glass is DENSER than chrome glass, deliberately. A card holds a
     name and a line of description that have to stay crisply readable, and
     a card you can see through is a card with no hierarchy -- which is why
     the references reserve heavy blur for chrome and keep cards nearly
     solid. This is the lighter blur and a thinner tint over the panel: it
     picks up a hint of what is behind it at the edges without ever letting
     the page compete with the label. */
  background: linear-gradient(
    180deg,
    rgba(255, 255, 255, 0.032),
    rgba(255, 255, 255, 0.012)
  ), var(--panel);
  backdrop-filter: var(--glass-blur-soft);
  -webkit-backdrop-filter: var(--glass-blur-soft);
  border: 1px solid var(--glass-edge-soft);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05);
  border-radius: var(--radius-card);
  overflow: hidden;
  cursor: pointer;
  transition: border-color 0.2s, transform 0.2s ease;
}
.app-card:hover { border-color: var(--line-strong); transform: translateY(-2px); }

/* The app's own frame, cropped to its head and running off the bottom of
   the well: a screen on a surface, not a screenshot with grey bars. */
.thumb-well {
  height: 148px;
  padding: 16px 16px 0;
  display: flex; align-items: flex-end; justify-content: center;
  background: var(--bg);
  border-bottom: 1px solid var(--line);
  overflow: hidden;
}
.thumb-well img {
  max-width: 100%; max-height: 100%;
  width: auto; height: auto;
  border-radius: 6px 6px 0 0;
  border: 1px solid var(--line);
  border-bottom: none;
  object-fit: cover; object-position: top center;
}
.thumb-well.blank { align-items: center; color: var(--ghost); font-size: 12px; text-align: center; padding: 16px; }

.card-body { padding: 13px 15px 15px; }
.card-body .name { color: var(--text); font-size: 13.5px; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.card-body .meta { color: var(--faint); font-size: 11.5px; margin-top: 3px; }

.card-x {
  position: absolute; top: 10px; right: 10px;
  width: 24px; height: 24px;
  display: grid; place-items: center;
  border: 1px solid var(--line-strong); border-radius: 7px;
  background: rgba(10,10,10,0.85);
  color: var(--muted); font-size: 14px; line-height: 1;
  cursor: pointer; opacity: 0;
  transition: opacity 0.15s, color 0.15s;
}
.app-card:hover .card-x { opacity: 1; }
.card-x:hover { color: var(--danger); border-color: var(--danger); }
.apps-empty { color: var(--faint); font-size: 13px; }

/* ---- session ---------------------------------------------------------- */

.session-body { flex: 1; display: flex; min-height: 0; }
.rail {
  width: var(--rail); min-width: var(--rail);
  display: flex; flex-direction: column;
  border-right: 1px solid var(--line);
}

/* Where a message's text actually begins, measured rather than assumed.
   `.thread` has padding 22px 18px AND padding-left: 30px, and the second
   wins -- it is not 18+30. The composer below uses the same number so the
   two edges line up; measured in a browser at 1440 wide, gap 0. */
:root { --thread-gutter-left: 30px; --thread-gutter-right: 18px; }
.thread { flex: 1; overflow-y: auto; padding: 22px 18px; display: flex; flex-direction: column; gap: 20px; }
/* The conversation is the product, so it is set to be read.
 *
 * Three things were wrong beside a terminal like Claude Code's, and the
 * founder could see it without being able to name it (K-822):
 *
 *  - 13.5px is a caption size. Prose people actually read sits at 14-15;
 *    this is 14.5 with a slightly looser line, which is where long
 *    paragraphs stop feeling cramped.
 *  - Krate's own replies were --muted, 62% white: the app's MAIN content
 *    was the dimmest text on the screen, dimmer than the labels above it.
 *    They are near-full strength now, and the distinction from the
 *    person's own lines is carried by the label and the rail dot, which
 *    is what those are for.
 *  - The YOU/KRATE label was ghost-grey at 36%, so the eye had to hunt
 *    for the start of each turn. Still quiet, just legible.
 */
/* The live work panel in the transcript (K-823).
 *
 * Collapsed: a caret and the line the AI is on, quiet enough to sit under
 * the build chip without competing with it. Open: the recent steps in the
 * mono face the log uses, capped in height so a long build cannot push the
 * composer off the screen. The caret turns, as every disclosure does. */
/* The plan card's action row (K-824). Centred under the plan, with the
   same air the question card's actions have. */
.plan-actions { display: flex; gap: 10px; justify-content: center; margin-top: 18px; }
.plan-actions.hidden { display: none; }

.vwork { margin: 6px 0 0 0; }
.vwork-head {
  display: flex; align-items: center; gap: 7px;
  background: none; border: 0; padding: 2px 0; width: 100%;
  text-align: left; cursor: pointer; color: var(--faint);
  font-size: 12px; font-family: inherit;
}
.vwork-head:hover { color: var(--muted); }
.vwork-caret { display: inline-block; transition: transform 0.15s; font-size: 13px; line-height: 1; }
.vwork.open .vwork-caret { transform: rotate(90deg); }
.vwork-now { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vwork-steps {
  margin-top: 6px; max-height: 190px; overflow-y: auto;
  border-left: 1px solid var(--line); padding: 2px 0 2px 11px;
}
.vwork-step {
  font: 11.5px/1.7 var(--mono); color: var(--faint);
  margin: 0; white-space: pre-wrap; word-break: break-word;
}
.vwork-step:last-child { color: var(--muted); }

/* A tool row: the action, the file, and a count when it repeats (K-836).
   The verb is quiet and fixed-width-ish so the file names line up and can
   be scanned; the file is the thing a developer is reading for. */
.vwork-tool { display: flex; align-items: baseline; gap: 8px; }
.vwork-verb {
  color: var(--ghost); flex: none;
  min-width: 52px;
}
.vwork-file {
  color: var(--muted); overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap;
}
.vwork-step:last-child .vwork-file { color: var(--text); }
.vwork-count { color: var(--ghost); flex: none; margin-left: auto; }

.msg .who {
  font-size: 10.5px; font-weight: 560; letter-spacing: 0.1em;
  color: var(--faint); display: block; margin-bottom: 6px;
}
.msg .body {
  color: var(--text); font-size: 14.5px; line-height: 1.65;
  user-select: text; letter-spacing: -0.003em;
}
.msg.krate .body { color: var(--reply); }

/* Krate does not say one kind of thing, so it should not look like it.
 *
 * Twenty-four places in the UI speak as KRATE, and until now every one of
 * them rendered identically -- the plan a person agrees to looked exactly
 * like "Noted, I'll do that when this finishes". Three weights instead of
 * one (K-835):
 *
 *   plan  the thing being agreed to. The most substantial text on the
 *         screen after the person's own request, because it is what they
 *         read before pressing Build it.
 *   (none) an ordinary answer -- the reading size set for the transcript.
 *   note  running commentary: stopping, queueing, resuming. True, worth
 *         keeping in the record, and not worth the same weight as a plan.
 */
.msg.plan .body {
  font-size: 15px;
  line-height: 1.7;
  color: var(--text);
}
.msg.note .body {
  font-size: 13px;
  color: var(--faint);
}
/* A note's rail dot recedes with it: the timeline should show at a glance
   where the substance is. */
.msg.note::before { background: var(--line); width: 6px; height: 6px; top: 6px; }

/* Whose turn is whose, at a glance.
 *
 * The only thing telling a person's message from Krate's was the colour of
 * a 8px dot on the rail. Everything else -- size, weight, colour, indent --
 * was identical, so a long session read as one undifferentiated column and
 * the founder could see it was wrong beside a terminal agent without being
 * able to name it (K-832).
 *
 * What a person WROTE is the anchor: slightly larger, full strength, and
 * carrying a quiet tint so the eye lands on it while scrolling back for
 * "what did I ask for". Krate's replies stay the reading size they were
 * set to. No bubbles: this is a transcript, not a chat app, and a box
 * around every line would fight the timeline rail. */
.msg.you .body {
  font-size: 15px;
  font-weight: 450;
  color: var(--text);
}
.msg.you .who { color: var(--blue); }
/* A hairline on the person's turn, in the accent the rail dot uses. It
   reads as "this is where you spoke" without drawing a container. */
.msg.you {
  border-left: 2px solid color-mix(in srgb, var(--blue) 45%, transparent);
  padding-left: 12px;
  /* No negative margin: it pulled the rule past the thread's gutter and
     clipped the first character of every line the person wrote. The rule
     sits inside the text column, which is where the eye expects it. */
}
.msg .files { color: var(--faint); font-size: 11.5px; margin-top: 4px; display: block; }

/* The composer lines up with the words above it.
 *
 * The thread indents its text by its own 18px plus the 30px the timeline
 * rail takes, so messages start 48px in -- while this box started at 16px.
 * A 32px step between the last line of the conversation and the box you
 * answer it in, most visible on a wide window where there is nothing else
 * to judge the edge against (K-820). One variable, so the two cannot drift
 * apart again.
 *
 * The right side keeps the thread's own 18px: the rail is a left-hand
 * thing, and matching it on both sides would push the box off-centre in
 * the other direction. */
.composer {
  padding: 14px var(--thread-gutter-right) 16px var(--thread-gutter-left);
  border-top: 1px solid var(--line);
}
.attach-row { display: flex; flex-wrap: wrap; gap: 6px; padding-bottom: 10px; }
.attach-chip {
  display: flex; align-items: center; gap: 7px;
  font-size: 11.5px; color: var(--muted);
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  padding: 4px 7px 4px 12px;
}
.attach-chip button { background: none; border: none; color: var(--faint); cursor: pointer; font-size: 13px; line-height: 1; }
.attach-chip button:hover { color: var(--danger); }

.composer-box {
  display: flex; align-items: flex-end; gap: 8px;
  /* The lozenge. This is the one control the person actually touches, so
     it gets the full material: a barely-there tint, a real blur, and the
     lit top rim that makes it sit forward of the page.
     Radius is larger than the card radius on purpose -- the references all
     use a soft, almost pill-like input, and a 12px corner on a 48px-tall
     field reads as a text box while an 18px one reads as a surface. */
  background: var(--glass-chrome);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border: 1px solid var(--glass-edge);
  border-radius: 18px;
  box-shadow:
    inset 0 1px 0 var(--glass-lit),
    0 8px 28px rgba(0, 0, 0, 0.34);
  padding: 10px 10px 10px 14px;
  transition: border-color 0.2s, box-shadow 0.2s, background 0.2s;
}
/* Focus lifts the glass rather than drawing a ring around it: the rim
   brightens and the shadow deepens, the way a pane tilts into the light.
   A coloured outline would be the one loud thing on an otherwise quiet
   screen. */
.composer-box:focus-within {
  background: rgba(255, 255, 255, 0.062);
  border-color: rgba(255, 255, 255, 0.14);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.13),
    0 10px 34px rgba(0, 0, 0, 0.42);
}
.composer-box textarea {
  flex: 1; background: none; border: none; outline: none; resize: none;
  color: var(--text); font: 400 14px/1.55 var(--sans);
  max-height: 132px; padding: 3px 0;
}
.composer-box textarea::placeholder { color: var(--ghost); }

.clip {
  display: grid; place-items: center;
  width: 30px; height: 30px; flex: none;
  border-radius: 8px; border: none; background: none;
  color: var(--faint); cursor: pointer; transition: color 0.15s;
}
.clip:hover { color: var(--text); }
.send {
  display: grid; place-items: center;
  width: 30px; height: 30px; flex: none;
  border-radius: 8px; border: none;
  background: var(--blue); color: #0a0a0a;
  cursor: pointer; transition: background 0.15s, opacity 0.15s;
}
.send:hover { background: var(--blue-soft); }
.send:disabled { opacity: 0.32; cursor: default; }
/* One button, two jobs. People reach for the spot below-right of the box to
   stop a running thing, because that is where every chat tool puts it now.
   So while a build runs and the box is empty, the arrow becomes a square and
   the button stops the build. Type a character and it is a send button again.
   Both icons ship in the markup and the class picks which one shows -- a
   swap of innerHTML would lose the icon on any re-render. */
.send .send-square { display: none; }
.send.stopping .send-arrow { display: none; }
.send.stopping .send-square { display: block; }
/* Written to beat `.send.ready`, which sets the same property at the same
   specificity and appears later in this sheet. A stale `ready` alongside
   `stopping` painted the stop square blue with a send button's glow, and
   the JS that clears one when the other lands is the first line of
   defence, not the only one. */
.send.stopping,
.send.ready.stopping,
.bigbar-row .send.ready.stopping {
  background: var(--text); color: var(--bg); box-shadow: none;
}
.send.stopping:hover,
.send.ready.stopping:hover { background: var(--muted); filter: none; }
.composer-hint { margin-top: 9px; font-size: 11px; color: var(--ghost); text-align: right; line-height: 1.5; }

/* ---- stage ------------------------------------------------------------ */

.stage {
  flex: 1;
  display: grid; place-items: center; align-content: center;
  padding: 36px;
  overflow-y: auto; overflow-x: hidden;
}
.stage-state { width: 100%; display: grid; place-items: center; }

.stage-empty { text-align: center; color: var(--ghost); }
.stage-empty img { width: 26px; height: 26px; opacity: 0.22; margin-bottom: 16px; }

/* building */
.build-card {
  width: min(420px, 92%);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  padding: 24px 26px 22px;
}
.build-title { font-size: 18px; font-weight: 500; letter-spacing: -0.022em; }
.build-elapsed { color: var(--faint); font-size: 12px; margin: 5px 0 3px; font-variant-numeric: tabular-nums; }
.build-sweep { width: 100%; margin-bottom: 26px; }

/* The app's own latest test frame, updating as the AI works. Small on
   purpose -- a preview riding above the stages, not the finished-app stage. */
.build-shot { position: relative; margin: 10px 0 14px; border-radius: 12px; overflow: hidden; border: 1px solid var(--line); background: var(--film); min-height: 190px; }
.build-shot img { display: block; width: 100%; max-height: 240px; object-fit: cover; object-position: top; }

/* The forming skeleton: a ghost of a window, breathing, until the AI's
   first real test frame replaces it. One object, one idea -- the app,
   arriving -- instead of a dead text card. */
.forming-ghost {
  /* Bottom padding clears the "taking shape…" tag; without it the last
     ghost line ran straight under the label (seen in review). */
  position: absolute; inset: 0; padding: 14px 16px 42px;
  display: flex; flex-direction: column; gap: 12px;
}
.g-chrome { display: flex; gap: 5px; margin-bottom: 2px; }
.g-chrome i { width: 8px; height: 8px; border-radius: 50%; background: rgba(255, 255, 255, 0.1); }
.g-block {
  display: block; border-radius: 8px;
  background: linear-gradient(100deg, rgba(255, 255, 255, 0.045) 40%, rgba(255, 255, 255, 0.1) 50%, rgba(255, 255, 255, 0.045) 60%);
  background-size: 220% 100%;
  animation: shimmer 1.6s ease-in-out infinite;
}
.g-hero { flex: 1; min-height: 54px; }
.g-row { display: flex; gap: 10px; }
.g-half { height: 34px; flex: 1; }
.g-line { height: 12px; width: 70%; }
.g-line.short { width: 45%; }
.forming-sweep {
  position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(115deg, transparent 42%, rgba(124, 108, 255, 0.07) 50%, transparent 58%);
  background-size: 300% 100%;
  animation: formingSweep 3.2s ease-in-out infinite;
}
@keyframes formingSweep { 0% { background-position: 120% 0; } 100% { background-position: -120% 0; } }
@media (prefers-reduced-motion: reduce) {
  .g-block, .forming-sweep { animation: none; }
}

/* The thin honest track: moves on real stages only. Bright enough to be
   part of the composition -- at 2px on 7% it disappeared and its margins
   read as a dead gap. */
.build-track {
  height: 4px; border-radius: 4px; margin: 12px 0 10px;
  background: var(--film-strong); overflow: hidden;
}
.build-track i {
  display: block; height: 100%; border-radius: 3px;
  background: linear-gradient(90deg, var(--accent, #6291ff), rgba(124, 108, 255, 0.55));
  transform: scaleX(0.04); transform-origin: left center;
  transition: transform 0.6s cubic-bezier(0.2, 0.8, 0.2, 1);
}

/* The card's vertical rhythm lives with the .build-card.brail flex rules
   further down -- one block owns the layout, because two owners was how
   the track got crushed to 0px and the live line rendered past the card
   border on short windows. */
.build-shot-tag {
  position: absolute; left: 8px; bottom: 8px;
  font-size: 10.5px; letter-spacing: 0.02em; color: #fff;
  background: rgba(0, 0, 0, 0.55); border-radius: 6px; padding: 3px 8px;
}

/* 10px apart, not 14: five stages read as one list rather than five
   separate lines drifting down the screen. */
.stages { list-style: none; display: flex; flex-direction: column; gap: 10px; }
.stages li {
  display: flex; align-items: center; gap: 11px;
  color: var(--ghost); font-size: 13px;
  transition: color 0.3s ease;
}
/* The dot alone carries the state -- waiting, now, done -- by colour. It
   used to also receive a checkmark from the JS, which a 6px box cannot
   hold: the glyph spilled out below and left of the dot. */
.stages li .tick {
  width: 6px; height: 6px; flex: none;
  border-radius: 50%;
  background: var(--line-strong);
  transition: background 0.3s ease, box-shadow 0.3s ease;
}
.stages li.now { color: var(--text); }
.stages li.now .tick { background: var(--blue); box-shadow: 0 0 0 4px rgba(124,108,255,0.14); }
.stages li.done { color: var(--muted); }
.stages li.done .tick { background: var(--cyan); }

.now-line {
  display: flex; align-items: center; gap: 9px;
  margin-top: 20px;
  padding-top: 16px;
  border-top: 1px solid var(--line);
  font: 11.5px/1.5 var(--mono);
  color: var(--faint);
  white-space: nowrap; overflow: hidden;
}
.now-line span:first-child { overflow: hidden; text-overflow: ellipsis; flex: 1; min-width: 0; }
.caret { width: 6px; height: 12px; flex: none; background: var(--blue); animation: blink 1.1s steps(1) infinite; }
@keyframes blink { 50% { opacity: 0; } }
@media (prefers-reduced-motion: reduce) { .caret { animation: none; } }

.build-log-wrap { margin-top: 18px; }
.build-log-wrap summary {
  color: var(--faint); font-size: 12px; cursor: pointer;
  list-style: none; display: flex; align-items: center; gap: 7px;
  transition: color 0.15s;
}
.build-log-wrap summary::-webkit-details-marker { display: none; }
.build-log-wrap summary::before { content: "›"; display: inline-block; transition: transform 0.2s ease; }
.build-log-wrap[open] summary::before { transform: rotate(90deg); }
.build-log-wrap summary:hover { color: var(--muted); }
.build-log {
  margin-top: 12px; max-height: 240px; overflow-y: auto;
  padding: 14px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  font: 11px/1.7 var(--mono);
  color: var(--muted);
  white-space: pre-wrap; user-select: text;
}
.stop { margin-top: 16px; margin-left: -12px; }
.stop:hover { color: var(--danger); }

/* done */
.done-card { width: min(500px, 94%); max-height: 100%; display: flex; flex-direction: column; }
.shot-stage {
  position: relative;
  flex: 0 1 auto; min-height: 0;
  display: flex; align-items: flex-end; justify-content: center;
  padding: 28px 28px 0;
  background: var(--panel);
  border: 1px solid var(--line);
  border-bottom: none;
  border-radius: var(--radius-card) var(--radius-card) 0 0;
  overflow: hidden;
}
.shot-stage img {
  display: block;
  max-width: 100%; max-height: 42vh;
  width: auto; height: auto;
  /* Room for the caption strip, which is drawn over the bottom of this
     box. Without it the strip covered the bottom 18% of every preview at
     88% opacity: on a pomodoro timer that was its Start button, which is
     the one thing somebody wants to see in the picture.
     Two lines' worth, because the strip wraps its trust line onto a
     second row on a narrow card. Measured at 63px there, 40px when it
     fits on one. */
  margin-bottom: 64px;
  border: 1px solid var(--line-strong);
  border-bottom: none;
  border-radius: 8px 8px 0 0;
  object-fit: cover; object-position: top center;
}
/* No picture: a quiet placeholder rather than a broken-image glyph. An old
   session or a failed --shoot both land here. */
.shot-stage.no-shot { padding: 0; min-height: 96px; align-items: center; }
/* With no src the <img> still paints: a broken-image glyph and its alt
   text, on top of the "no preview" line that exists precisely to replace
   it. Not a phone bug -- it looks the same on a desktop, and was simply
   easier to see on a small screen. */
.shot-stage.no-shot img { display: none; }
.shot-stage.no-shot::after {
  content: "no preview for this one";
  color: var(--ghost); font-size: 12px;
}

.sheen {
  position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(105deg, transparent 42%, rgba(255,255,255,0.09) 50%, transparent 58%);
  background-size: 300% 100%;
}
.done-card.in .sheen { animation: sheenPass 1s ease 0.35s both; }
@keyframes sheenPass { from { background-position: 130% 0; } to { background-position: -130% 0; } }
@media (prefers-reduced-motion: reduce) { .done-card.in .sheen { animation: none; } }

.done-meta {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 0 0 var(--radius-card) var(--radius-card);
  padding: 20px 22px 22px;
}
.done-name { font-size: 17px; font-weight: 500; letter-spacing: -0.022em; }
.done-sub { color: var(--faint); font-size: 12.5px; margin-top: 4px; font-variant-numeric: tabular-nums; }
/* The engine's verdict on an app that built but is not what was asked:
   plain words on the card, in the same place every time. */
.done-verdict {
  margin: 12px 0 0; padding: 9px 12px;
  font-size: 12.5px; line-height: 1.5; color: var(--text);
  border: 1px solid rgba(240, 180, 200, 0.28); border-radius: 10px;
  background: rgba(240, 180, 200, 0.07);
}
.asks { list-style: none; margin: 16px 0 0; display: flex; flex-wrap: wrap; gap: 6px; }
.asks li {
  font-size: 11.5px; color: var(--muted);
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  padding: 4px 12px;
}
.asks li::before { content: "asks "; color: var(--ghost); }
.done-actions { display: flex; flex-wrap: wrap; gap: 9px; margin-top: 20px; }

/* The link is the harvest: the moment the app becomes sendable deserves
   its own small stage, not a mono string tucked under a border. */
.share-result {
  margin-top: 16px; padding: 14px 16px 13px; border-radius: 12px;
  background: rgba(98, 145, 255, 0.05);
  border: 1px solid rgba(98, 145, 255, 0.16);
}
.share-head { font-size: 13px; font-weight: 500; color: var(--text); margin-bottom: 9px; }
.share-row {
  display: flex; align-items: center; gap: 9px;
  background: rgba(0, 0, 0, 0.3); border: 1px solid var(--line);
  border-radius: 9px; padding: 6px 6px 6px 11px;
}
.share-row svg { flex: none; color: var(--blue); }
.share-result .link {
  flex: 1; min-width: 0;
  font: 12px var(--mono); color: var(--blue-soft);
  user-select: text;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.share-copy {
  flex: none; border: 0; border-radius: 7px; padding: 6px 12px;
  font-family: var(--sans); font-size: 12px; font-weight: 600;
  background: var(--text); color: var(--bg); cursor: pointer;
  transition: opacity 0.15s ease;
}
.share-copy:hover { opacity: 0.88; }
.share-copy.done { background: transparent; color: var(--cyan); cursor: default; }
.share-sub { margin-top: 8px; font-size: 11.5px; line-height: 1.5; color: var(--faint); }
body.light .share-result { background: rgba(58, 99, 216, 0.05); border-color: rgba(58, 99, 216, 0.18); }
body.light .share-result .link { color: #3a63d8; }
body.light .share-row { background: rgba(0, 0, 0, 0.04); }
/* errors reuse the stage, stripped to the sentence */
.share-result.error { background: rgba(255, 127, 149, 0.05); border-color: rgba(255, 127, 149, 0.22); }
.share-result.error .share-head, .share-result.error .share-sub,
.share-result.error .share-row svg, .share-result.error .share-copy { display: none; }
.share-result.error .share-row { background: none; border: 0; padding: 0; }
.share-result.error .link { color: var(--danger); font-family: var(--sans); white-space: normal; }

/* Failure is a designed moment, not an apology with a stack trace. A
   quiet card: soft mark, one honest line, the doors out (retry, another
   AI, report), the coming Krate AI named, evidence folded away. */
.fail-card {
  width: min(460px, 92%); text-align: center;
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--radius-card); padding: 34px 30px 24px;
  --bloom-color: rgba(255, 176, 130, 0.16);
}
.fail-mark {
  display: inline-flex; align-items: center; justify-content: center;
  width: 44px; height: 44px; border-radius: 50%; margin-bottom: 16px;
  color: #ffb082; background: rgba(255, 176, 130, 0.09);
  border: 1px solid rgba(255, 176, 130, 0.22);
}
.fail-title { font-size: 19px; font-weight: 550; letter-spacing: -0.022em; }
.fail-why { color: var(--muted); font-size: 13.5px; margin: 10px auto 22px; line-height: 1.6; max-width: 360px; }
.fail-actions { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; }
.fail-krateai {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  margin: 20px 0 0; padding: 11px 14px; text-align: left;
  border: 1px dashed var(--line); border-radius: 10px;
  color: var(--faint); font-size: 12.5px; opacity: 0.75;
}
.fail-krateai b { color: var(--muted); font-weight: 550; }
/* The offer row is a live door, not a preview: full presence, solid line. */
.fail-offer { opacity: 1; border-style: solid; color: var(--muted); }
.fail-offer b { color: var(--text); }
.makeit-recap {
  margin: 14px 0; padding: 11px 14px; text-align: left;
  border-left: 2px solid var(--line-strong); color: var(--muted);
  font-size: 13px; line-height: 1.55; background: rgba(255, 255, 255, 0.025);
  border-radius: 0 8px 8px 0;
}
.makeit-email {
  width: 100%; margin-bottom: 14px; padding: 10px 13px;
  background: var(--panel-deep, rgba(255,255,255,0.04)); color: var(--text);
  border: 1px solid var(--line); border-radius: 9px; font-size: 13.5px;
}
.makeit-email:focus { outline: none; border-color: var(--line-strong); }
.fail-raw-wrap { margin-top: 16px; text-align: left; }
.fail-raw-wrap summary {
  font-size: 11.5px; color: var(--quiet, rgba(255,255,255,0.35));
  cursor: pointer; list-style: none; text-align: center;
}
.fail-raw-wrap summary::-webkit-details-marker { display: none; }
.fail-raw-wrap[open] summary { color: var(--faint); }

/* ---- sheets ----------------------------------------------------------- */

.sheet-wrap {
  position: fixed; inset: 0; z-index: 40;
  display: grid; place-items: center;
  background: rgba(6,6,6,0.66);
  animation: fade 0.2s ease;
}
@keyframes fade { from { opacity: 0; } }
.sheet {
  width: min(460px, 92%); max-height: 82vh; overflow-y: auto;
  background: var(--panel);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-card);
  padding: 26px;
  display: flex; flex-direction: column; gap: 14px;
  animation: sheetIn 0.28s ease;
}
@keyframes sheetIn { from { opacity: 0; transform: translateY(14px); } }
.sheet h2 { font-size: 17px; font-weight: 500; letter-spacing: -0.022em; }
.sheet-sub { color: var(--muted); font-size: 13px; line-height: 1.6; }
.sheet-foot { color: var(--faint); font-size: 11.5px; line-height: 1.65; }
.sheet-foot code { font: 11px var(--mono); color: var(--muted); }
.sheet-close { align-self: flex-end; }

/* The heading over each group of tools (K-819). Quiet and small: it is a
   label for the rows under it, not a section of its own, so it sits closer
   to what it names than to what came before. The first one needs no top
   margin -- the sheet's own heading is right above it. */
.ai-group {
  font-size: 11px; font-weight: 560; letter-spacing: 0.04em; text-transform: uppercase;
  color: var(--faint); margin: 18px 0 2px;
}
.ai-group:first-of-type { margin-top: 6px; }
.ai-row { display: flex; align-items: center; gap: 12px; padding: 14px 0; border-bottom: 1px solid var(--line); }
.ai-row:last-of-type { border-bottom: none; }
.ai-name { font-size: 13.5px; font-weight: 500; }
.ai-detail { color: var(--faint); font-size: 12px; margin-top: 2px; }
.ai-remedy { font: 11px var(--mono); color: var(--muted); margin-top: 7px; user-select: text; overflow-x: auto; white-space: nowrap; }
.ai-row .grow { flex: 1; min-width: 0; }
/* The button never shrinks. Without this the row's text -- an install hint
   is a full sentence with a URL in it -- takes the width and squeezes the
   Install button to nothing, so the picker reads as five dead rows telling
   you to go run commands elsewhere. That is what a first user saw, and he
   gave up on connecting an AI at all (K-183). */
.ai-row > .btn { flex: none; white-space: nowrap; }

.setting-row { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 14px 0; border-bottom: 1px solid var(--line); }
.setting-row:last-of-type { border-bottom: none; }
.setting-name { font-size: 13.5px; font-weight: 500; }
.setting-value { color: var(--faint); font-size: 11.5px; margin-top: 3px; word-break: break-all; }

/* ---- scrollbars ------------------------------------------------------- */

/* Thin and translucent, the way every app people compare this to draws them.
 *
 * macOS hides scrollbars until you scroll, so an 11px bar with a solid
 * --line-strong thumb was invisible there and looked like a heavy grey slab
 * on Windows, where the bar is always on. Windows also draws its own default
 * bar over any element that does not opt out -- the recent-sessions list in
 * the drawer got exactly that -- so this has to cover the whole app, not
 * just the panels somebody remembered.
 *
 * A translucent thumb over a transparent track means the bar tints whatever
 * is behind it instead of introducing a second surface colour, which is what
 * makes it read as subtle in both themes without a per-theme rule. */
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-thumb {
  background: rgba(255, 255, 255, 0.16);
  border-radius: 999px;
  border: 2px solid transparent;
  background-clip: content-box;
}
::-webkit-scrollbar-thumb:hover { background: rgba(255, 255, 255, 0.3); background-clip: content-box; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-corner { background: transparent; }
body.light ::-webkit-scrollbar-thumb { background: rgba(12, 16, 23, 0.2); background-clip: content-box; }
body.light ::-webkit-scrollbar-thumb:hover { background: rgba(12, 16, 23, 0.34); background-clip: content-box; }
/* Firefox and any engine with no ::-webkit- support.
 *
 * Guarded, because where BOTH are understood the standard property wins the
 * width and its "thin" is 11px -- wider than the 8px above, so declaring it
 * unconditionally silently undid the ::-webkit- rule. `@supports not` hands
 * the width to whichever mechanism the engine actually has. */
@supports not selector(::-webkit-scrollbar) {
  * { scrollbar-width: thin; scrollbar-color: rgba(255, 255, 255, 0.16) transparent; }
  body.light * { scrollbar-color: rgba(12, 16, 23, 0.2) transparent; }
}

/* ---- bloom: light from the object, not shadow under it ---------------- */

/* Every reference shares this: a card floats on a soft coloured glow rather
   than a hard drop shadow. It reads as the thing being lit from within,
   which is the difference between "a rectangle" and "an object".
   One element, positioned behind, blurred hard, low opacity. */
.bloom { position: relative; }
.bloom::before {
  content: "";
  position: absolute;
  left: 12%; right: 12%; bottom: -14px; height: 60%;
  background: radial-gradient(ellipse at 50% 100%, var(--bloom-color, rgba(124,108,255,0.45)), transparent 70%);
  filter: blur(34px);
  opacity: 0.55;
  z-index: -1;
  pointer-events: none;
  transition: opacity 0.6s ease;
}
.bloom > * { position: relative; }

/* ---- progress: a gradient that FILLS, not just travels ---------------- */

/* The mint bar and the course bar in the references both fill with a live
   gradient. Our sweep travels; this fills. Together they say two different
   true things: "something is happening" and "this much of it is done". */
.progress {
  height: 3px;
  border-radius: var(--radius-pill);
  background: var(--film-strong);
  overflow: hidden;
}
.progress .fill {
  height: 100%;
  width: 0%;
  border-radius: var(--radius-pill);
  background: linear-gradient(90deg, #6366f1, #a855f7, #ec4899, #f97316, #eab308);
  background-size: 200% 100%;
  animation: shimmer 3s linear infinite;
  transition: width 0.7s cubic-bezier(0.3, 0.9, 0.3, 1);
}
@keyframes shimmer { to { background-position: -200% 0; } }
@media (prefers-reduced-motion: reduce) {
  .progress .fill { animation: none; }
}

/* The step being worked on now, named, with its neighbours receding --
   the "Generating outline..." pattern. Ours is a list; this makes the
   current line unmistakable without adding a second widget. */
.stages li.now .tick { position: relative; }
.stages li.now .tick::after {
  content: "";
  position: absolute; inset: -5px;
  border-radius: 50%;
  border: 1px solid rgba(124,108,255,0.35);
  animation: ripple 2s ease-out infinite;
}
@keyframes ripple {
  0% { transform: scale(0.8); opacity: 0.9; }
  100% { transform: scale(1.5); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) { .stages li.now .tick::after { animation: none; } }

/* ---- live pill: gradient border for a state that is happening --------- */

.live-pill {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 3px 11px;
  font-size: 11px; font-weight: 500;
  border-radius: var(--radius-pill);
  color: var(--text);
  background:
    linear-gradient(var(--panel), var(--panel)) padding-box,
    linear-gradient(90deg, #6366f1, #a855f7, #ec4899, #f97316) border-box;
  border: 1px solid transparent;
}
.live-pill .pulse {
  width: 5px; height: 5px; border-radius: 50%;
  background: var(--cyan);
  animation: livepulse 1.6s ease-in-out infinite;
}
@keyframes livepulse { 50% { opacity: 0.3; } }
@media (prefers-reduced-motion: reduce) { .live-pill .pulse { animation: none; } }

/* ---- attachments as objects ------------------------------------------- */

/* The upload reference gives the file a real icon and a type badge rather
   than a bare filename. A dropped file should look like a thing you handed
   over. */
.attach-chip .kind {
  font-size: 9px; font-weight: 500; letter-spacing: 0.06em;
  color: var(--faint);
  background: var(--film);
  border-radius: 4px;
  padding: 1px 5px;
  text-transform: uppercase;
}

/* ---- building card, laid out for the live pill ------------------------ */

.build-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }

/* Details: the same card, showing the engine's real output instead of the
   forming frame. The default screen is written for someone who does not
   want to know; this is for the person who does, and both are one click
   apart rather than two different screens. */
.build-detail-toggle {
  display: inline-flex; align-items: center; gap: 6px; flex: none;
  padding: 5px 10px; border-radius: 999px;
  background: none; border: 1px solid var(--line);
  color: var(--faint); font-family: var(--sans); font-size: 11.5px;
  cursor: pointer; transition: color 0.15s, border-color 0.15s, background 0.15s;
}
.build-detail-toggle:hover { color: var(--text); border-color: rgba(255,255,255,0.22); }
.build-detail-toggle[aria-pressed="true"] {
  color: var(--text); border-color: rgba(255,255,255,0.28);
  background: var(--film);
}

/* Fills the forming frame's rectangle exactly, so switching views never
   changes the card's height. */
.build-term {
  position: absolute; inset: 0; display: flex; flex-direction: column;
  background: #0b0b0d; z-index: 2;
}
.term-bar {
  display: flex; align-items: center; gap: 8px; flex: none;
  padding: 7px 10px; border-bottom: 1px solid var(--line);
  font-family: var(--mono); font-size: 10.5px; color: var(--faint);
}
.term-what { color: var(--text); }
.term-stage { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.term-copy {
  flex: none; background: none; border: 0; cursor: pointer;
  color: var(--faint); font-family: var(--sans); font-size: 11px;
  padding: 2px 6px; border-radius: 6px;
}
.term-copy:hover { color: var(--text); background: var(--film-strong); }
.term-log {
  flex: 1; margin: 0; padding: 9px 11px 11px; overflow-y: auto;
  font: 11px/1.65 var(--mono); color: #b9bfc9;
  white-space: pre-wrap; word-break: break-word;
}
.term-log::-webkit-scrollbar { width: 8px; }
.term-log::-webkit-scrollbar-thumb {
  background: var(--film-strong); border-radius: 4px;
}
/* The terminal is the tall view: a log at 190px is a keyhole. */
.build-shot.showing-term { min-height: 320px; }
.build-shot.showing-term img { display: none; }
.build-progress { margin: 4px 0 26px; }
/* The build blooms blue; the finished app blooms warmer, because arriving
   and working are different feelings. */
.build-card { --bloom-color: rgba(124,108,255,0.34); }
.build-card.bloom::before { left: 6%; right: 6%; bottom: -8px; height: 46%; }
.done-card { --bloom-color: rgba(108,244,215,0.28); }

/* ---- the build that is still running --------------------------------- */

/* A build keeps going while you browse, and without this the home screen
   looked idle -- then the next request was refused with "one app is already
   being made" and there was no way to reach it. */
/* The bar is no longer one button: opening the session and stopping the
   build are different decisions and each needs its own target. */
.building-now {
  width: min(940px, 100%);
  display: flex; align-items: center; gap: 12px;
  margin-top: 6vh;
  padding: 13px 16px;
  background: var(--panel);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-card);
  color: var(--muted);
  font-size: 13px;
  text-align: left;
  transition: border-color 0.15s, color 0.15s;
}
.building-now:hover { border-color: rgba(255,255,255,0.34); color: var(--text); }
.building-now .grow {
  flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; display: flex; gap: 10px; align-items: center;
  background: none; border: none; color: inherit; font: inherit;
  cursor: pointer; padding: 0; text-align: left;
}
.building-now .bn-stop {
  flex: none; background: none; cursor: pointer;
  border: 1px solid var(--line-strong); border-radius: var(--radius-pill);
  color: var(--faint); font: inherit; font-size: 12px; padding: 5px 14px;
  transition: color 0.15s, border-color 0.15s;
}
.building-now .bn-stop:hover { color: var(--danger); border-color: rgba(255,127,149,0.4); }
.building-now b { color: var(--text); font-weight: 500; }
.building-now .go { color: var(--faint); font-size: 12px; flex: none; }
.building-now .pulse {
  width: 7px; height: 7px; flex: none; border-radius: 50%;
  background: var(--blue);
  animation: livepulse 1.6s ease-in-out infinite;
}

/* ---- the account sheet ------------------------------------------------ */

.sheet-account { text-align: center; align-items: center; }
.account-avatar { width: 56px; height: 56px; border-radius: 50%; }
.sheet-actions { display: flex; gap: 9px; justify-content: center; margin-top: 6px; }

/* The one step happening now, at eye level. The full list moved behind the
   details disclosure: the chat narrates the story, so the stage does not
   need to repeat all five lines. */
.now-stage {
  font-size: 14px; color: var(--text);
  margin: 22px 0 0;
}
.build-log-wrap .stages { margin: 12px 0; }

/* ---- the rotating word: the site's own hero ---------------------------- */

/* krate.tech says "Make an app you can send / run / trust / keep" with the
   word swapping and a gradient bar travelling underneath. Asking "What do
   you want to make?" is what every other tool asks; this says what Krate
   is for. Measured off the site and rebuilt to the same numbers. */
.rot { position: relative; display: inline-block; white-space: nowrap; }

/* The colour lives IN the word, not on a rule beneath it. An underline
   reads as a progress bar that never finishes -- the word itself carrying
   a slow travelling gradient says the same thing and looks like language
   rather than chrome.

   How the one continuous run is achieved is below, on the letters. */
.rot .word { display: inline-block; }
/* The colour is ONE gradient, and the LETTERS carry it.
 *
 * This was a single ::after layer painting the word over the top, which made
 * the word the right colour and killed the bounce: the letters underneath
 * still animated, but a static copy sat on top of them, so nothing appeared
 * to move.
 *
 * Now each letter is a window onto one shared gradient. `background-attachment:
 * fixed` resolves the background against the VIEWPORT rather than each
 * element, so every letter samples the same ramp at its own position -- one
 * continuous gradient across the word, by construction, with no per-letter
 * offsets to compute. The letters keep their own transform, so they leave
 * upward and arrive from below exactly as the site's do. */
/* The chosen treatment: the gradient STANDS STILL (blue, violet, pink --
 * the warm tail is gone, which is what made the old flow feel like it was
 * rotating hues), and a soft light sweeps across it every few seconds.
 *
 * Two background layers per letter, both resolved against the viewport
 * (background-attachment: fixed), so every letter samples the same ramp at
 * its own position -- one continuous gradient and one continuous sweep
 * across the word, no per-letter offsets to compute. The sweep animates
 * background-position of the FIRST layer only, travelling the viewport so
 * it crosses the word wherever the window puts it. The letters keep their
 * own transforms, so the bounce between words is untouched. */
.rot .word span {
  display: inline-block;
  will-change: transform, opacity, filter;
  /* Set per letter by paintFlow(): where this letter sits in the word, and
     how wide the word is. The ramp spans exactly the word; the sweep is a
     wide soft band that crosses it. Both are sliced per letter by these
     offsets, so the bounce transforms never disturb the colour. */
  --off: 0px;
  --ww: 220px;
  background-image:
    linear-gradient(100deg, transparent 40%, rgba(255, 255, 255, 0.9) 50%, transparent 60%),
    linear-gradient(100deg, #6291ff 0%, #a855f7 50%, #ec4899 100%);
  background-size: 340px 100%, var(--ww) 100%;
  background-repeat: no-repeat, no-repeat;
  background-position: calc(-1 * var(--off) - 340px) 0, calc(-1 * var(--off)) 0;
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  animation: wordshine 4.2s ease-in-out infinite;
}
@keyframes wordshine {
  0%, 45% { background-position: calc(-1 * var(--off) - 340px) 0, calc(-1 * var(--off)) 0; }
  100%    { background-position: calc(-1 * var(--off) + var(--ww)) 0, calc(-1 * var(--off)) 0; }
}
@media (prefers-reduced-motion: reduce) {
  .rot .word span { animation: none; }
}

/* ---- the draggable divider -------------------------------------------- */

/* The rail/stage split is a preference, not a constant: someone reading a
   long conversation wants a wide rail, someone watching a build wants a
   wide stage. A 5px grab strip, invisible until approached. */
.divider {
  width: 5px; flex: none;
  margin-left: -3px; margin-right: -2px;
  cursor: col-resize;
  background: transparent;
  transition: background 0.15s;
  z-index: 5;
}
.divider:hover, .divider.dragging { background: var(--line-strong); }
body.resizing { cursor: col-resize; user-select: none; }
body.resizing .divider { background: var(--blue); }


/* ---- composer tools ---------------------------------------------------- */

/* Attaching was a bare paperclip inside the session composer, and a person
   describing their first app never found it -- "how is someone supposed to
   make something they already want to show?". It says what it does now, and
   it sits under the box where the description is typed. */
.composer-tools {
  display: flex; align-items: center; gap: 4px;
  padding: 8px 6px 0;
}
/* Quiet text actions, not a second row of boxed buttons fighting the
   composer for attention. One line each, never wrapped -- the boxed pills
   used to break "Add a picture or / file" across lines and the row read as
   debris. The note explains the attach action and yields first when narrow;
   "Open a .krate" keeps to the far right as the quiet second door. */
.tool {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 6px 9px; border-radius: 8px;
  border: none; background: none;
  color: var(--faint); font-size: 12.5px; cursor: pointer;
  white-space: nowrap; flex: none;
  transition: color 0.15s, background 0.15s;
}
.tool:hover { color: var(--text); background: var(--panel-raised); }
.tool svg { flex: none; }
#openKrateBtn { margin-left: auto; }
.tool-note {
  color: var(--ghost); font-size: 12px;
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.link-btn {
  padding: 5px 10px; border-radius: 8px;
  border: none; background: none; color: var(--faint);
  font-size: 13px; cursor: pointer; transition: color 0.15s, background 0.15s;
  -webkit-app-region: no-drag;
}
.link-btn:hover { color: var(--text); background: var(--panel-raised); }

/* ---- Krate Cloud ------------------------------------------------------- */

.cloud { flex: 1; overflow-y: auto; padding: 40px 32px 64px; }
.cloud-head { max-width: 640px; margin: 0 auto 28px; text-align: center; }
.cloud-head .display { font-size: 30px; margin-bottom: 8px; }
.cloud-sub { color: var(--faint); font-size: 14px; line-height: 1.6; }
.cloud-state { display: grid; place-items: center; gap: 12px; padding: 48px 0; color: var(--faint); font-size: 13px; }
.cloud-error { max-width: 520px; margin: 24px auto; text-align: center; color: var(--danger); font-size: 13px; }
.cloud-grid {
  display: grid; gap: 12px; max-width: 1000px; margin: 0 auto;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
}

/* ---- the gallery as shelves -------------------------------------------
 * A grid of everything works at twenty apps and fails at a thousand. The
 * landing view is a few short rows instead -- new, most opened, then the
 * busiest categories -- each scrolling sideways, with search for the rest.
 * A row that scrolls says "there is more this way" in a way a wrapped grid
 * of five hundred cards does not. */
/* min-width:0 on the rows, or the strips inside them refuse to be narrower
   than their content: a grid item's default min-width is auto, so twelve
   240px cards made the row 3012px wide and nothing ever scrolled. */
.cloud-shelves { max-width: 1000px; margin: 0 auto; display: grid; gap: 30px; }
.cloud-shelves > .shelf-row { min-width: 0; }
.shelf-row-head {
  display: flex; align-items: baseline; gap: 12px; margin-bottom: 12px;
}
.shelf-row-head h2 {
  font-size: 15px; font-weight: 500; letter-spacing: -0.01em; color: var(--text);
  text-transform: capitalize;
}
.shelf-row-more {
  margin-left: auto; background: none; border: 0; cursor: pointer;
  font-family: var(--sans); font-size: 12.5px; color: var(--faint);
  transition: color 0.15s;
}
.shelf-row-more:hover { color: var(--text); }
.shelf-strip {
  display: flex; gap: 12px; overflow-x: auto; padding-bottom: 4px;
  scroll-snap-type: x proximity;
  /* No bar under each row. Three shelves would mean three horizontal rules
     across the page, which is louder than the cards they belong to -- and
     the row already says it scrolls by cutting a card off at the edge. */
  scrollbar-width: none;
}
.shelf-strip::-webkit-scrollbar { height: 0; display: none; }
.shelf-strip .cloud-card {
  flex: none; width: 240px; scroll-snap-align: start;
}
.cloud-card {
  display: flex; flex-direction: column; gap: 10px; text-align: left;
  padding: 16px; border-radius: 12px;
  border: 1px solid var(--line); background: var(--panel-raised);
  cursor: pointer; transition: border-color 0.15s, transform 0.15s;
}
.cloud-card:hover { border-color: var(--line-strong); transform: translateY(-1px); }
.cloud-id { display: flex; align-items: center; gap: 10px; min-width: 0; }
.cloud-icon {
  flex: none; width: 34px; height: 34px; border-radius: 9px;
  display: grid; place-items: center;
  color: rgba(255, 255, 255, 0.92); font-size: 13px; font-weight: 620;
  letter-spacing: 0.01em; border: 1px solid rgba(255, 255, 255, 0.08);
}
.cloud-titles { min-width: 0; }
.cloud-name {
  font-size: 14.5px; font-weight: 560;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.cloud-tag { color: var(--ghost); font-size: 11.5px; }
.cloud-foot {
  display: flex; align-items: center; justify-content: space-between;
  gap: 8px; margin-top: auto;
}
.cloud-by { display: flex; align-items: center; gap: 6px; color: var(--faint); font-size: 12.5px; min-width: 0; }
.cloud-by img { width: 16px; height: 16px; border-radius: 50%; }
.cloud-meta { color: var(--ghost); font-size: 12px; white-space: nowrap; }

/* ---- cloud search and categories --------------------------------------- */

.cloud-controls {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  max-width: 1000px; margin: 0 auto 14px;
}
.cloud-search {
  display: flex; align-items: center; gap: 8px; flex: 1; min-width: 200px;
  padding: 8px 12px; border-radius: 10px;
  border: 1px solid var(--line); background: var(--panel);
  color: var(--faint);
}
.cloud-search:focus-within { border-color: var(--line-strong); }
.cloud-search input {
  flex: 1; border: none; background: none; outline: none;
  color: var(--text); font: inherit; font-size: 13.5px;
}
.cloud-search input::placeholder { color: var(--ghost); }
.cloud-search input::-webkit-search-cancel-button { filter: invert(0.5); }

.cloud-cats { display: flex; gap: 6px; flex-wrap: wrap; }
.cat {
  padding: 6px 12px; border-radius: var(--radius-pill);
  border: 1px solid var(--line); background: none;
  color: var(--faint); font-size: 12.5px; cursor: pointer;
  transition: color 0.15s, border-color 0.15s, background 0.15s;
}
.cat:hover { color: var(--text); border-color: var(--line-strong); }
/* The selected chip inverts: the app's ink becomes the background and the
   page becomes the text. #0a0a0a was hardcoded for the dark theme, where
   --text is white; in light mode --text is nearly black and the label was
   black on black at 1.04:1. --bg tracks the theme, so it reads either way. */
.cat.on { color: var(--bg); background: var(--text); border-color: var(--text); }

.cloud-count {
  max-width: 1000px; margin: 0 auto 12px;
  color: var(--ghost); font-size: 12px;
}
.cloud-desc {
  color: var(--muted); font-size: 12.5px; line-height: 1.5;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}

/* ---- app details ------------------------------------------------------- */

.sheet-info { max-width: 460px; text-align: left; }
.sheet-info h2 { text-align: left; }
.sheet-info .sheet-sub {
  font-family: var(--mono); font-size: 11.5px; color: var(--ghost);
  word-break: break-all; text-align: left;
}
.info-rows {
  display: grid; grid-template-columns: auto 1fr; gap: 8px 18px;
  margin: 18px 0; font-size: 13px;
}
.info-rows dt { color: var(--faint); }
.info-rows dd { color: var(--text); }
.info-caps { border-top: 1px solid var(--line); padding-top: 16px; }
.info-caps ul { list-style: none; margin: 10px 0 0; padding: 0; }
.info-caps li {
  position: relative; padding: 4px 0 4px 18px;
  font-size: 13px; color: var(--text);
}
.info-caps li::before {
  content: ""; position: absolute; left: 2px; top: 11px;
  width: 5px; height: 5px; border-radius: 50%; background: var(--blue);
}
.info-note {
  margin-top: 14px; color: var(--faint); font-size: 12px; line-height: 1.55;
}
/* The button row the report and publish sheets share. */
.detail-actions { display: flex; gap: 8px; margin: 20px 0 0; }

/* ---- one published app ------------------------------------------------- */

/* The stagger the whole page arrives on. */
@keyframes rise { to { opacity: 1; transform: none; } }

/* The page is one scrolling column, not two side-by-side ones. The old
   layout put the screenshot in a grid cell next to the text, so a tall
   app image and a short fact list slid out of step and neither had a
   natural bottom. Here the app is the hero and the facts sit under it. */
.detail { flex: 1; overflow-y: auto; overflow-x: hidden; }
.detail-inner {
  max-width: 1060px; margin: 0 auto; width: 100%;
  padding: 0 32px var(--dock-clear);
}

/* The stage.
 *
 * No fixed aspect ratio. Published screenshots run from 0.56 (a tall
 * phone-shaped window) to 1.57 (a wide one) -- measured across every app
 * on the hub, and ten of twenty-three are taller than they are wide. Any
 * single ratio with object-fit:cover would crop somebody's app to
 * nothing. So the picture keeps its own shape inside a fixed-height
 * stage, and the room left over is filled by a blurred copy of the same
 * picture rather than a grey bar.
 */
.detail .appstage {
  position: relative; margin-top: 4px; overflow: hidden;
  border: 1px solid var(--line); border-radius: 18px;
  /* Literal, not a token: the frame is dark in light mode too, so the
     blurred backdrop and the white app name have ground to sit on. */
  background: #0b0c0e;
  opacity: 0; transform: translateY(14px); animation: rise 0.5s ease 0.04s forwards;
}
.detail .appstage-shot {
  position: relative; height: 44vh; min-height: 260px; max-height: 420px;
  display: flex; align-items: center; justify-content: center; overflow: hidden;
}
/* Flex, not grid: a grid item takes its natural height and max-height:100%
   does not bind against it, so a 1280px-tall screenshot overflowed a
   378px stage and was clipped mid-picture. Flex sizes it to the line. */
.detail .appstage-shot img.shot {
  position: relative; z-index: 1;
  max-width: 100%; max-height: 100%; width: auto; height: auto;
  min-height: 0; display: block; object-fit: contain;
}
/* The backdrop: the same image, blown up and blurred, so a portrait
   screenshot sits in colour that belongs to it instead of a void. */
/* inset:0 with width/height auto -- as a flex item, width:100% resolved
   against the wrong box and the backdrop covered only part of the stage.
   The scale hides the soft edge the blur leaves at the border. */
.detail .appstage-shot img.shot-bed {
  position: absolute; inset: 0; z-index: 0;
  width: auto; height: auto; max-width: none; max-height: none;
  object-fit: cover;
  filter: blur(44px) saturate(1.25); transform: scale(1.2); opacity: 0.5;
}
.shot-none {
  display: grid; place-items: center; height: 100%; width: 100%;
  color: var(--ghost); font-size: 13px;
  background: radial-gradient(120% 120% at 50% 0%, rgba(124, 108, 255, 0.07), transparent 70%);
}
/* The name has to stay legible over whatever the app happens to draw, so
   the bottom of the stage is dimmed rather than trusting the screenshot. */
/* The fade and the name sit above the picture. Without an explicit stack
   the screenshot -- which needs z-index of its own to clear its blurred
   backdrop -- painted straight over the app's name. */
.detail .appstage-fade {
  position: absolute; inset: auto 0 0 0; height: 44%; z-index: 2;
  pointer-events: none;
  background: linear-gradient(to top,
    rgba(8, 8, 9, 0.96) 18%, rgba(8, 8, 9, 0.62) 58%, transparent 100%);
}
.detail .ident {
  position: absolute; left: 24px; right: 24px; bottom: 20px; z-index: 3;
  display: flex; align-items: flex-end; gap: 16px;
}
.detail .ident-icon-slot { flex: none; }
.detail .ident-icon-slot .cloud-icon {
  width: 66px; height: 66px; border-radius: 17px; font-size: 23px;
  border: 1px solid rgba(255, 255, 255, 0.09);
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.6);
}
.detail .ident-text { min-width: 0; flex: 1; }
/* The stage stays a dark frame in both themes -- it shows an app's own
   pixels, the way a video player does, and its text sits on that frame
   rather than on the page. So these colours are literal, not tokens:
   in light mode the tokens would turn this white-on-dark text black. */
.detail .ident h1 {
  font-size: 32px; font-weight: 500; letter-spacing: -0.03em; line-height: 1.1;
  color: #fff; text-shadow: 0 2px 22px rgba(0, 0, 0, 0.85);
}
.detail .ident-by {
  display: flex; align-items: center; gap: 7px; margin-top: 9px;
  font-size: 13px; color: rgba(255, 255, 255, 0.62);
  text-shadow: 0 1px 12px rgba(0, 0, 0, 0.8);
}
.detail .ident-by img.ident-avatar {
  width: 19px; height: 19px; border-radius: 50%; object-fit: cover;
  border: 1px solid rgba(255, 255, 255, 0.12);
}
/* Not ".cat" -- that is the cloud filter chip, and it would paint a
   pill border around what is meant to be a plain trailing word. */
.detail .ident-by .ident-cat { color: rgba(255, 255, 255, 0.4); }
.detail .ident-by .ident-cat::before {
  content: "·"; margin: 0 7px; color: rgba(255, 255, 255, 0.28);
}

.detail .actionbar {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 18px;
  opacity: 0; transform: translateY(14px); animation: rise 0.5s ease 0.1s forwards;
}
.detail .actionbar .btn { height: 40px; }
/* Size and date are the two facts a person weighs before clicking Open,
   so they sit on the same line as the button and not in a list below. */
.detail .quick { margin-left: auto; display: flex; align-items: center; gap: 20px; }
.detail .quick .k {
  display: block; font-size: 10.5px; letter-spacing: 0.11em;
  text-transform: uppercase; color: var(--faint);
}
.detail .quick .v { display: block; font-size: 13.5px; margin-top: 3px; text-align: right; }
.detail-note { margin-top: 11px; color: var(--faint); font-size: 12.5px; min-height: 1em; }

.detail .cols {
  display: grid; grid-template-columns: minmax(0, 1.35fr) minmax(280px, 0.65fr);
  gap: 26px; margin-top: 26px; align-items: start;
  opacity: 0; transform: translateY(14px); animation: rise 0.5s ease 0.16s forwards;
}
/* `.detail .cols` above is 0-2-0 and this override was 0-1-0, so it lost
   and the two-column desktop layout stayed on a phone. Measured at 390px:
   the columns resolved to `11.99px 280px`, which squeezed "What it is
   allowed to do" into a 42px-wide, 503px-tall ribbon reading one word per
   line, with the right column overlapping it. Matched at the same
   specificity. */
@media (max-width: 880px) {
  .detail .cols, .cols { grid-template-columns: minmax(0, 1fr); }
}
.detail .card {
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--radius-card); padding: 20px;
}
.detail .permhead { display: flex; align-items: baseline; gap: 10px; }
.detail .permhead .count { margin-left: auto; font-family: var(--mono); font-size: 11px; color: var(--faint); }

/* Permissions as pills, grouped by whether they leave the sandbox. As a
   stacked bullet list this ran to fourteen lines and the one that
   mattered -- the microphone, the network -- hid among the boring ones. */
.detail .pgroup { margin-top: 16px; }
.detail .pgroup-t { display: flex; align-items: center; gap: 8px; font-size: 12.5px; color: var(--muted); }
.detail .pgroup-t .pip { width: 6px; height: 6px; border-radius: 50%; flex: none; }
.detail .pgroup.out .pip { background: var(--yellow); }
.detail .pgroup.in .pip { background: var(--cyan); }
.detail .plist { list-style: none; display: flex; flex-wrap: wrap; gap: 7px; margin-top: 11px; }
.detail .plist li {
  font-size: 12.5px; padding: 6px 12px; border-radius: var(--radius-pill);
  border: 1px solid var(--line); background: var(--panel-raised); color: var(--text);
}
.detail .pgroup.out .plist li {
  border-color: rgba(244, 220, 116, 0.22); background: rgba(244, 220, 116, 0.06);
}
.detail .cap-dim { margin-top: 14px; color: var(--ghost); font-size: 13px; }
.detail .wall {
  margin-top: 18px; padding-top: 16px; border-top: 1px solid var(--line);
  font-size: 12.5px; line-height: 1.6; color: var(--faint); display: flex; gap: 10px;
}
.detail .wall svg { flex: none; margin-top: 2px; color: var(--cyan); }

.detail-desc { margin: 12px 0 4px; color: var(--muted); font-size: 13.5px; line-height: 1.65; }
.detail .facts {
  /* `auto 1fr` lets the label column collapse to nothing when the value
     column claims the row: measured at 390px it resolved to `0px 220px`,
     so "Runs on" and "Download" had literally no width. max-content for
     the label keeps it as wide as its longest word and no wider. */
  display: grid; grid-template-columns: max-content minmax(0, 1fr);
  gap: 11px 18px;
  margin-top: 14px; font-size: 13px;
}
.detail .facts dt { color: var(--faint); }
.detail .facts dd { color: var(--text); text-align: right; }
/* The share link is the thing people actually paste to each other, so it
   is a control you can click, not a fact you have to retype. */
.detail .code {
  display: flex; align-items: center; gap: 8px; width: 100%; margin-top: 14px;
  padding: 9px 12px; border-radius: 10px; cursor: copy; text-align: left;
  border: 1px solid var(--line); background: var(--panel-raised);
  font-family: inherit; color: inherit; transition: border-color 0.15s;
}
.detail .code:hover { border-color: var(--line-strong); }
.detail .code span {
  font-family: var(--mono); font-size: 11.5px; color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.detail .code svg { flex: none; color: var(--faint); }
.detail .code.copied span { color: var(--cyan); }

/* ---- cloud cards as a store -------------------------------------------- */

.cloud-shot {
  margin: -16px -16px 2px;
  border-bottom: 1px solid var(--line);
  border-radius: 12px 12px 0 0;
  overflow: hidden; aspect-ratio: 16 / 10; background: var(--bg);
}
.cloud-shot img {
  width: 100%; height: 100%; object-fit: cover; object-position: top;
  display: block;
}
.gate-code { cursor: copy; }
.gate-code.copied::after { content: " copied"; color: var(--good, #4ade80); font-size: 12px; }
.ai-error { color: var(--danger); font-size: 12.5px; line-height: 1.5; word-break: break-word; }
/* The engine's own words, inside the card rather than across it.
 *
 * A <pre> does not wrap, so one long line -- "This is a problem with the
 * AI tool, not with Krate..." -- ran off the right edge of the failure
 * card and over whatever was beside it. max-width alone could not hold it
 * because nothing told it to break (K-827). pre-wrap keeps the engine's
 * own line breaks, which is why it is a <pre> at all, and wraps the rest;
 * the width follows the card instead of a fixed 520. */
.fail-raw {
  margin-top: 10px; font-family: var(--mono); font-size: 11px;
  color: var(--quiet, rgba(255, 255, 255, 0.35));
  white-space: pre-wrap; overflow-wrap: anywhere; word-break: break-word;
  max-width: 100%; max-height: 220px; overflow-y: auto;
}

/* The update chip: present, never shouting. */
.update-chip { color: var(--accent); }
.update-chip::before {
  content: ""; display: inline-block; width: 6px; height: 6px;
  border-radius: 50%; background: var(--accent); margin-right: 6px;
  vertical-align: 1px;
}

/* A real uploaded logo shares the monogram tile's geometry. */
img.cloud-icon { object-fit: cover; }

/* ---- the publish sheet ------------------------------------------------- */

.sheet-publish { max-width: 520px; text-align: left; }
.sheet-publish h2 { text-align: left; }
.pub-sub { color: var(--faint); font-size: 12.5px; margin: 6px 0 16px; line-height: 1.55; }
.pub-label {
  display: block; margin-bottom: 12px;
  color: var(--faint); font-size: 12px;
}
.pub-label input {
  display: block; width: 100%; margin-top: 5px;
  padding: 8px 11px; border-radius: 9px;
  border: 1px solid var(--line); background: var(--panel);
  color: var(--text); font: inherit; font-size: 13.5px; outline: none;
}
.pub-label input:focus { border-color: var(--line-strong); }
.pub-media { display: flex; gap: 14px; margin: 4px 0 16px; }
.pub-slot { flex: 1; min-width: 0; }
.pub-slot-icon { flex: 0 0 158px; }
.pub-opt { color: var(--ghost); text-transform: none; letter-spacing: 0; }
.pub-preview {
  margin: 6px 0 8px; border: 1px solid var(--line); border-radius: 10px;
  background: var(--panel); overflow: hidden;
  aspect-ratio: 16 / 10; display: grid; place-items: center;
  color: var(--ghost); font-size: 11.5px; text-align: center; padding: 0 10px;
}
.pub-preview img { width: 100%; height: 100%; object-fit: cover; padding: 0; }
.pub-preview-icon { aspect-ratio: 1; width: 96px; border-radius: 22px; }
.pub-preview-icon img { object-fit: cover; }

/* ---- the conversation gate's voice ------------------------------------- */

/* A question must read as a question: bright, marked with the accent bar.
   Plans and progress notes stay muted -- the first live session could not
   tell KRATE's question from ambient context, and answered past it. */
.msg.ask .body { color: var(--text); }
.msg.ask {
  border-left: 2px solid var(--blue);
  padding-left: 12px;
}
.msg-actions { display: flex; gap: 8px; margin-top: 10px; flex-wrap: wrap; }
.msg-btn { padding: 6px 16px; font-size: 12.5px; }

/* ================= Home 3: the bar is the room ========================= */

/* A faint aurora behind the hero: two blurred discs drifting on transform
   only, so the compositor pays and the main thread never does. Under 6%
   opacity -- felt, not seen. Reduced motion holds them still. */
/* Scrolls vertically, clips horizontally. `overflow: hidden` on both axes was
   here to keep the blurred aurora discs from widening the page, and it also
   sealed off everything below the fold: with more than a few sessions the app
   grid simply could not be reached. The glow is `position: absolute` inside
   this box, so clipping x alone still contains it. */
.home { position: relative; overflow-y: auto; overflow-x: hidden; }
/* Pinned to the viewport, not to the scrolled content. As an `absolute` child
   of the scroll box it was only as tall as the first screenful, so scrolling
   down ran out of aurora and the page went flat black halfway. `fixed` keeps
   the wash behind the whole room however far it scrolls. */
.home-glow { position: fixed; inset: 0; pointer-events: none; z-index: 0; }
.home-glow i {
  position: absolute; width: 560px; height: 560px; border-radius: 50%;
  filter: blur(90px); opacity: 0.055; will-change: transform;
}
.home-glow i:first-child {
  background: radial-gradient(circle, var(--blue), transparent 65%);
  top: -10%; left: 12%;
  animation: drift-a 26s ease-in-out infinite alternate;
}
.home-glow i:last-child {
  background: radial-gradient(circle, var(--cyan), transparent 65%);
  top: 16%; right: 8%;
  animation: drift-b 32s ease-in-out infinite alternate;
}
@keyframes drift-a { to { transform: translate(90px, 60px) scale(1.12); } }
@keyframes drift-b { to { transform: translate(-110px, -40px) scale(0.94); } }
@media (prefers-reduced-motion: reduce) { .home-glow i { animation: none; } }

/* The two-story bar. Everything a first message needs lives inside it. */
.bigbar {
  width: min(620px, 86vw);
  background: var(--panel);
  border: 1px solid var(--line-strong);
  border-radius: 18px;
  padding: 16px 16px 12px;
  box-shadow: 0 24px 70px rgba(0, 0, 0, 0.4);
  transition: border-color 0.15s, box-shadow 0.15s;
}
/* Focus: the pane lifts, it does not light up.
 *
 * This was a violet ring -- a 3px halo in a colour the app no longer uses
 * anywhere, left behind by the palette that went with the starfield. On a
 * screen whose whole point is calm, a glowing outline is the loudest thing
 * in the window, and Krate focuses this box on launch, so that glow would
 * have been the FIRST thing anybody saw.
 *
 * Instead the rim brightens and the shadow deepens: the same gesture the
 * session composer uses, and what a real pane tilting into the light does.
 * Enough to say "the cursor is here", not enough to be a decoration. */
.bigbar:focus-within {
  background: rgba(255, 255, 255, 0.062);
  border-color: rgba(255, 255, 255, 0.16);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.13),
    0 30px 66px -30px rgba(0, 0, 0, 0.9);
}
body.light .bigbar {
  background: var(--glass-chrome);
  box-shadow:
    inset 0 1px 0 var(--glass-lit),
    0 20px 50px -28px rgba(12, 16, 23, 0.35);
}
body.light .bigbar:focus-within {
  background: rgba(255, 255, 255, 0.94);
  border-color: rgba(12, 16, 23, 0.18);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.95),
    0 22px 54px -28px rgba(12, 16, 23, 0.45);
}
.bigbar textarea {
  width: 100%; border: none; background: none; outline: none; resize: none;
  color: var(--text); font: inherit; font-size: 16.5px; line-height: 1.5;
  padding: 2px 4px 14px; caret-color: var(--blue);
}
.bigbar textarea::placeholder { color: var(--ghost); }
.bigbar-row {
  display: flex; align-items: center; gap: 16px;
  border-top: 1px solid var(--line); padding-top: 11px;
}
.bigbar-row .tool {
  display: flex; gap: 7px; align-items: center;
  background: none; border: none; cursor: pointer;
  color: var(--faint); font-size: 13px;
  transition: color 0.15s;
}
.bigbar-row .tool:hover { color: var(--text); }
.bigbar-row .send {
  margin-left: auto;
  width: 34px; height: 34px; border-radius: 11px;
  background: var(--blue); border: none; color: #fff; cursor: pointer;
  display: grid; place-items: center;
  transition: transform 0.12s, background 0.15s;
}
.bigbar-row .send:hover { background: var(--blue-soft); color: #0a0a0a; }
.bigbar-row .send:active { transform: scale(0.92); }

/* One quiet sentence under the bar: who builds, and the door to change it. */
.airow { margin-top: 16px; display: flex; justify-content: center; }
.aichip {
  display: inline-flex; gap: 8px; align-items: center;
  border: 1px solid var(--line); background: none; cursor: pointer;
  border-radius: var(--radius-pill); padding: 7px 15px;
  color: var(--muted); font-size: 12.5px;
  transition: border-color 0.15s, color 0.15s;
}
.aichip:hover { border-color: var(--line-strong); color: var(--text); }
/* Size only. The COLOUR is the general .dot rule above, which defaults to
   neutral and takes .ok / .warn / .bad from setChips.
   This said `background: var(--good, #4ade80)` with no state variants, so
   the "Built by" chip was green whatever the truth was: green beside
   "Built by Claude" on a machine whose Claude sign-in had expired, and
   green before the first probe had even answered, next to a name still
   showing its literal placeholder. A dot whose only job is to report a
   state must not hardcode one (K-804, the desktop twin of K-780). */
.aichip .dot { width: 6px; height: 6px; border-radius: 50%; }
.aichip b { font-weight: 560; color: var(--text); }
.aichip .chev { color: var(--ghost); }

/* Inside the composer the chip is a control on a row of controls, not a
   standalone pill: no border, tighter, and it only picks up a background
   on hover the way the + and Plan buttons do. */
.bigbar-row .aichip {
  border: 0; padding: 5px 9px; gap: 6px; font-size: 12px;
  border-radius: 9px; margin-left: 2px;
}
.bigbar-row .aichip:hover { background: var(--film); color: var(--text); }
.bigbar-row .aichip .dot { width: 5px; height: 5px; }
.bigbar-row .aichip .chev { font-size: 11px; }

/* Your apps become a shelf: present, never competing with the bar. */
.apps { width: 100%; max-width: 860px; margin: auto auto 0; padding-bottom: 28px; }

/* ================= The session timeline ================================ */

/* The thread grows a spine; every message becomes a node on it. People and
   versions are the only citizens -- machine chatter compresses to chips. */
.thread { position: relative; padding-left: 30px; }
.thread::before {
  content: ""; position: absolute; left: 21px; top: 26px; bottom: 12px;
  width: 1px; background: var(--line);
}
.msg { position: relative; }
.msg::before {
  content: ""; position: absolute; left: -13px; top: 5px;
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--blue);
}
.msg.krate::before { background: var(--line-strong); }
.msg.ask { border-left: none; padding-left: 0; }
.msg.ask .askbar {
  border-left: 2px solid var(--blue); padding: 1px 0 2px 12px;
}
.msg.ask::before {
  background: var(--blue);
  box-shadow: 0 0 0 4px rgba(124, 108, 255, 0.18);
  animation: node-pulse 1.4s ease-in-out infinite;
}
@keyframes node-pulse { 50% { box-shadow: 0 0 0 2px rgba(124, 108, 255, 0.05); } }
@media (prefers-reduced-motion: reduce) { .msg.ask::before { animation: none; } }
.msg.ask .body { color: var(--text); }

/* Build receipts: one chip is the whole build. */
.vchip {
  display: inline-flex; gap: 9px; align-items: center;
  border: 1px solid var(--line); border-radius: 10px;
  padding: 8px 12px; font-size: 12.5px; color: var(--muted);
  max-width: 100%;
}
.vchip b { color: var(--text); font-weight: 560; }
.vchip .vm { font-family: var(--mono); font-size: 10.5px; color: var(--ghost); }
.vchip .vact {
  /* --bg, not #0a0a0a: this inverts against --text, so the label has to be
     the page colour or it is black on black in light mode. Same bug the
     category chip had. */
  background: var(--text); color: var(--bg); border: none; cursor: pointer;
  border-radius: 99px; padding: 4px 12px; font-size: 11.5px; font-weight: 500;
}
.vchip .vact.vg { background: none; color: var(--faint); font-weight: 400; }
.msg.vok::before { background: var(--good, #4ade80); }
.msg.vbad::before { background: var(--danger); }
.msg.vlive::before {
  background: var(--blue);
  box-shadow: 0 0 0 4px rgba(124, 108, 255, 0.18);
  animation: node-pulse 1.4s ease-in-out infinite;
}
.vchip.vlivec { border-color: rgba(124, 108, 255, 0.35); }
.vchip.vbadc { border-color: rgba(255, 127, 149, 0.3); }
.vchip .vbar {
  width: 66px; height: 3px; border-radius: 3px; background: var(--line);
  overflow: hidden; position: relative;
}
.vchip .vbar i {
  position: absolute; inset: 0; border-radius: 3px; background: var(--blue);
  transform-origin: left; transition: transform 0.4s ease;
}

/* The plan card: unmistakably a plan, never chat. */
.plancard {
  border: 1px solid var(--line); border-left: 2px solid var(--cyan);
  border-radius: 4px 12px 12px 4px; background: var(--panel);
  padding: 12px 14px; margin-top: 2px;
}
.plancard .ph {
  font-size: 10.5px; letter-spacing: 0.11em; color: var(--cyan);
  margin-bottom: 6px;
}
.plancard .pt { font-size: 13.5px; color: var(--muted); line-height: 1.55; }
.plancard .pn { margin-top: 8px; font-size: 12.5px; color: var(--faint); }

/* ================= The stage: deploy rail ============================== */

.brail { width: min(560px, 86%); }
.brail h2 { font-size: 16px; font-weight: 560; margin-bottom: 3px; }
.brail .bsub { font-size: 12px; color: var(--faint); margin-bottom: 18px; }
.brow { display: flex; gap: 12px; padding: 10px 0; border-top: 1px solid var(--line); font-size: 13.5px; align-items: flex-start; }
.brow:first-of-type { border-top: 0; }
.brow .bic {
  width: 16px; height: 16px; border-radius: 50%; flex: none; margin-top: 2px;
  display: grid; place-items: center; font-size: 9px;
}
.brow.bdone { color: var(--muted); }
.brow.bdone .bic { background: rgba(74, 222, 128, 0.14); color: var(--good, #4ade80); }
.brow.bcur .bic {
  border: 2px solid var(--blue); border-top-color: transparent;
  animation: bspin 0.9s linear infinite;
}
@keyframes bspin { to { transform: rotate(360deg); } }
.brow.bpend { color: var(--ghost); }
.brow.bpend .bic { border: 1.5px solid var(--line-strong); }
.bpeek {
  margin: 7px 0 2px 28px; padding: 9px 12px;
  background: var(--panel); border: 1px solid var(--line); border-radius: 8px;
  font-family: var(--mono); font-size: 11px; color: var(--faint); line-height: 1.75;
  max-height: 64px; overflow: hidden;
}
.bpeek .new { color: var(--muted); }
.bfoot { margin-top: 16px; display: flex; justify-content: space-between; font-size: 12.5px; color: var(--ghost); }
.bfoot button { background: none; border: none; color: var(--faint); cursor: pointer; font: inherit; }
.bfoot button:hover { color: var(--text); }

/* ---- the stage list becomes the deploy rail (chosen design B) ---------- */

.stages li {
  display: flex; gap: 12px; align-items: flex-start;
  padding: 10px 0; border-top: 1px solid var(--line);
  font-size: 13.5px; color: var(--ghost);
}
.stages li:first-child { border-top: 0; }
.stages li .tick {
  width: 16px; height: 16px; border-radius: 50%; flex: none; margin-top: 1px;
  background: none; border: 1.5px solid var(--line-strong);
  box-shadow: none; display: grid; place-items: center;
}
.stages li.done { color: var(--muted); }
.stages li.done .tick {
  background: rgba(74, 222, 128, 0.14); border-color: transparent;
}
.stages li.done .tick::before {
  content: "✓"; font-size: 9px; color: #4ade80;
}
.stages li.now { color: var(--text); }
.stages li.now .tick {
  background: none;
  border: 2px solid var(--blue); border-top-color: transparent;
  box-shadow: none;
  animation: bspin 0.9s linear infinite;
}
.stages li.now .tick::after { content: none; }
@media (prefers-reduced-motion: reduce) { .stages li.now .tick { animation: none; border-top-color: var(--blue); } }


/* The rail-stage panel, exactly the agreed frame: full height, flat,
   steps as the content, footer pinned to the bottom. */
.build-card.brail {
  width: min(620px, 94%);
  padding: 26px 28px 22px;
  /* Was `height: calc(100% - 56px)` + `overflow: hidden`. When the stages, the
     peek and an open "full log" grew past that fixed height, hidden clipped the
     bottom of the card -- which is the footer with the Stop button. A real
     build on a shorter window then showed everything through "the full log" and
     no Stop, with no way to end the build from the session view. Cap the height
     but let the card grow to its content up to that cap, and never clip: the
     footer keeps its space (flex: none below) so Stop is always visible. */
  max-height: calc(100% - 56px);
  display: flex; flex-direction: column;
}
/* Who gives up height when the window is short: the FRAME, down to a
   floor -- never the words, never the 3px track (a shrinkable track
   rendered at literal zero height, and an unclipped card let the live
   line spill past its own border; both shipped, both photographed). */
.brail .build-shot { margin: 0 0 20px; flex: 0 1 auto; height: 300px; min-height: 140px; }
.brail .build-shot img { width: 100%; height: 100%; max-height: none; object-fit: cover; object-position: top; }
.brail .build-head,
.brail .build-title,
.brail .bpeek,
.brail .build-elapsed { flex: none; }
.brail .build-title { margin: 0; font-size: 19px; font-weight: 550; }
.brail .bpeek { margin: 10px 0 0; }
.brail .build-elapsed { margin: 8px 0 0; }
/* The words sit as one block under the frame; the card ends where they
   end, instead of the title floating high over leftover space. */
.brail .bfoot { margin-top: 18px; }
/* The log may take leftover room and scroll -- but only when open, so a
   closed one-line summary does not stretch a quiet card apart. */
.build-card.brail .build-log-wrap { flex: 0 1 auto; min-height: 0; overflow-y: auto; }
.build-card.brail .build-log-wrap[open] { flex: 1 1 0; }
.build-card.brail .build-log-wrap[open] .build-log { max-height: none; }
.build-card.brail .bfoot { flex: none; }
.build-card.brail .stages { margin: 16px 0 0; }
/* The peek rides INSIDE the list, tucked under whichever row is current
   (the JS moves it there on every stage change). */
.stages .bpeek { margin: 2px 0 8px 28px; }
.build-card.brail .build-log-wrap { margin-top: 10px; }
.build-card.brail .build-log-wrap summary { color: var(--ghost); font-size: 11.5px; }
.build-card.brail .bfoot { margin-top: 18px; }

/* ---- the outstanding question, on the stage --------------------------- */

/* Loud on purpose. The question used to live only in the transcript in the
   same grey as every other Krate line, which is how a developer answered it
   by pressing Build it (K-760). The tinted border is the one thing on this
   pane asking for something.
   `flex: none` is deliberate, and was measured. With `0 1 auto` plus a
   scrollbar this block was the one the card's height cap squeezed: at
   1280x860 it got 172px for 204px of content and the sentence saying what
   skipping costs -- the whole point of K-759 -- scrolled out of sight. The
   question does not shrink. The frame above it does (see below). */
.plan-ask {
  flex: none;
  margin: 16px 0 0; padding: 14px 16px;
  background: color-mix(in srgb, var(--blue) 8%, var(--panel));
  border: 1px solid color-mix(in srgb, var(--blue) 42%, var(--line));
  border-radius: 10px; text-align: left;
}
.plan-ask-lead { margin: 0; font-size: 13.5px; font-weight: 550; color: var(--text); }
.plan-ask-list { margin: 10px 0 0; padding: 0 0 0 18px; }
.plan-ask-list li {
  font-size: 13px; line-height: 1.6; color: var(--muted);
  margin: 0 0 6px;
}
.plan-ask-list li:last-child { margin-bottom: 0; }
.plan-ask-actions { display: flex; gap: 8px; align-items: center; margin-top: 14px; flex-wrap: wrap; }
/* The skip is a real way out and must stay clickable -- it is just not the
   thing the eye lands on first. */
.plan-ask-cost { margin: 10px 0 0; font-size: 12px; line-height: 1.6; color: var(--faint); }

/* While a question is outstanding, the question outranks the decoration.
   The forming ghost held 265px of a card that had to fit four more blocks;
   it keeps a strip, so the card still reads as an app forming, and gives
   the rest to the words. This is the block that shrinks when the window is
   short, which is the whole reason .plan-ask can be flex: none. */
#statePlanning:has(#planAsk:not(.hidden)) .build-shot {
  height: 120px; min-height: 64px; margin-bottom: 14px;
}

/* ---- the report-an-issue consent sheet -------------------------------- */

.sheet-report { max-width: 520px; text-align: left; }
.sheet-report h2 { text-align: left; }
.rep-list {
  list-style: none; margin: 4px 0 10px; padding: 0;
  border: 1px solid var(--line); border-radius: 10px;
  background: var(--panel);
}
.rep-list li {
  position: relative; padding: 9px 14px 9px 30px;
  font-size: 13px; color: var(--text);
  border-top: 1px solid var(--line);
}
.rep-list li:first-child { border-top: 0; }
.rep-list li::before {
  content: ""; position: absolute; left: 14px; top: 15px;
  width: 5px; height: 5px; border-radius: 50%; background: var(--cyan);
}
.rep-list li.dim { color: var(--ghost); }
.rep-list li.dim::before { background: var(--line-strong); }
.rep-note { color: var(--faint); font-size: 12px; margin-bottom: 14px; }
.rep-fine {
  color: var(--faint); font-size: 12px; line-height: 1.6;
  margin: 12px 0 16px;
  border-left: 2px solid var(--line-strong); padding-left: 12px;
}

/* While the AI opens the app to look at it, a window really does flash on
   the person's screen and its sounds play. The card says so at that exact
   moment -- unexplained, it reads as the machine misbehaving. */
.build-card.flashing .bpeek {
  border-color: rgba(124, 108, 255, 0.35);
  color: var(--muted);
}
.build-card.flashing .bpeek::after {
  content: "a window may appear for a moment -- that's Krate testing your app";
  display: block;
  margin-top: 6px;
  font-family: var(--sans);
  font-size: 11.5px;
  color: var(--blue);
}

/* =======================================================================
 * The 2026-08 shell: a horizon, a floating dock, and pages that scroll
 * under it.
 *
 * Added rather than rewritten. Every existing view keeps its own styles;
 * these rules add the new home treatment, the dock, and the two pages
 * (settings, profile) that used to be a sheet or nothing at all.
 * ===================================================================== */

/* ---- the horizon -------------------------------------------------------
 * Blue, because the product is blue. It sits behind everything on the
 * home view and dims when a page is scrolled, so content stays readable
 * over it. */
/* Nothing behind the app.
 *
 * There used to be a violet dome here with hard-edged blades, a starfield
 * and per-pixel grain -- drawn on a canvas because CSS could not give both
 * a real edge and grain riding it. It was the most interesting code in the
 * UI and the wrong thing to look at: the app someone is building is the
 * subject, and a lit poster behind it competes for the same attention.
 *
 * The ground is now the panel colour and nothing else. What gives the
 * window depth instead is the GLASS -- the rail, the composer, the dock and
 * the cards are translucent and blur what passes behind them, so depth
 * comes from the app's own surfaces rather than from a picture underneath.
 */
.horizon, .stars, .grain { display: none; }

/* ---- the dock ----------------------------------------------------------
 * One element for the whole app, outside every view, so switching pages
 * never moves it. Centred as a GROUP -- the pill and the avatar together --
 * which is why the wrapper is the thing translated, not the nav. */
.dockwrap {
  position: fixed; left: 50%; bottom: 24px; z-index: 60;
  display: flex; gap: 9px; align-items: center;
  transform: translateX(-50%);
  transition: bottom 0.42s cubic-bezier(0.2, 0.9, 0.25, 1), opacity 0.3s ease;
}
.dockwrap.hidden { display: none; }
/* Scrolling drops it lower and makes it solid: over a wall of app cards a
 * translucent dock is unreadable, and a person scrolling wants the content,
 * not the chrome. It settles back when they stop. */
body.scrolled .dockwrap { bottom: 13px; }

.dock {
  display: flex; align-items: center; gap: 2px; padding: 5px;
  /* The same material as the rail, the composer and the cards -- through
     the tokens, so there is one definition of glass in the app and not
     four that drift apart. The lit top rim is new: the dock had the blur
     and the tint but not the highlight, which is why it read as a dark
     pill rather than as a pane. */
  background: rgba(14, 16, 19, 0.58);
  border: 1px solid var(--glass-edge);
  border-radius: var(--radius-pill); position: relative;
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  box-shadow:
    inset 0 1px 0 var(--glass-lit),
    0 14px 40px rgba(0, 0, 0, 0.5);
  transition: background 0.35s ease, border-color 0.35s ease, box-shadow 0.35s ease;
}
body.scrolled .dock {
  background: rgba(9, 11, 14, 0.96);
  border-color: rgba(255, 255, 255, 0.15);
  box-shadow: 0 20px 52px rgba(0, 0, 0, 0.75);
}
.dock .glide {
  position: absolute; top: 5px; bottom: 5px; left: 5px;
  background: var(--text); border-radius: var(--radius-pill); z-index: 0;
  transition: transform 0.42s cubic-bezier(0.2, 0.9, 0.25, 1),
              width 0.42s cubic-bezier(0.2, 0.9, 0.25, 1);
}
.dock button {
  position: relative; z-index: 1; font-family: var(--sans); font-size: 13px;
  color: var(--muted); background: none; border: 0; padding: 9px 15px;
  border-radius: var(--radius-pill); cursor: pointer;
  display: inline-flex; align-items: center; gap: 7px;
  transition: color 0.3s ease; white-space: nowrap;
}
.dock button.on { color: var(--bg); font-weight: 500; }
.dock button:not(.on):hover { color: var(--text); }
.dock .badge {
  width: 6px; height: 6px; border-radius: 50%; background: var(--blue);
  margin-left: 1px; display: none;
}
.dock .badge.show { display: block; }

.dock-solo {
  width: 40px; height: 40px; border-radius: 50%; flex: none;
  background: rgba(14, 16, 19, 0.6);
  border: 1px solid rgba(255, 255, 255, 0.08);
  display: grid; place-items: center; cursor: pointer; overflow: hidden;
  backdrop-filter: blur(26px) saturate(1.3);
  -webkit-backdrop-filter: blur(26px) saturate(1.3);
  box-shadow: 0 14px 40px rgba(0, 0, 0, 0.5);
  transition: transform 0.15s ease, background 0.35s ease, border-color 0.35s ease;
}
body.scrolled .dock-solo {
  background: rgba(9, 11, 14, 0.96); border-color: rgba(255, 255, 255, 0.15);
}
.dock-solo:hover { transform: translateY(-1px); }
.dock-solo.on { border-color: rgba(255, 255, 255, 0.5); }
.dock-solo .in {
  width: 26px; height: 26px; border-radius: 50%; display: grid; place-items: center;
  background: linear-gradient(140deg, #2b3a63, #16203a);
  font-size: 11px; font-weight: 600; color: var(--blue-soft);
}
.dock-solo img { width: 26px; height: 26px; border-radius: 50%; object-fit: cover; }

/* ---- pages that scroll under the dock --------------------------------- */
.page-scroll {
  height: 100%; overflow-y: auto; overflow-x: hidden;
  padding-bottom: var(--dock-clear);
}
.page-inner { padding: 30px 40px 0; max-width: 980px; margin: 0 auto; }
.page-head { margin-bottom: 22px; display: flex; align-items: flex-end; gap: 16px; flex-wrap: wrap; }
.page-head h1 { font-size: 24px; font-weight: 500; letter-spacing: -0.03em; margin: 0 0 6px; }
.page-head p { color: var(--muted); margin: 0; font-size: 13.5px; }
.page-head .side { margin-left: auto; display: flex; gap: 8px; }

/* ---- settings + profile rows ------------------------------------------ */
.set-col { max-width: 680px; }
.set-group { margin-bottom: 26px; }
.set-group h2 {
  font-family: var(--mono); font-size: 10.5px; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--faint); margin: 0 0 11px; font-weight: 400;
}
/* A section is ONE panel, and the rows inside it are divided by hairlines.
 *
 * Every row used to be its own floating card with its own border, its own
 * blur and 8px of space under it -- nine sections and fourteen little
 * rectangles stacked down a narrow column. That is the shape that reads as
 * unfinished: nothing is grouped, so nothing has a hierarchy, and the eye
 * has to re-establish where it is at every row.
 *
 * The references all do the same thing instead: one bordered surface per
 * section, rows separated by a line inside it. Same information, and the
 * page suddenly has structure. */
.set-group > .set-row {
  background: none;
  border: 0;
  border-top: 1px solid var(--glass-edge-soft);
  border-radius: 0;
  margin-bottom: 0;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
/* The first row in a panel has the panel's own top edge above it. */
.set-group > .set-row:first-of-type { border-top: 0; }

/* The panel itself carries the glass, once, for the whole section. */
.set-group .set-panel,
.set-group.panel {
  background: var(--glass-card);
  border: 1px solid var(--glass-edge-soft);
  border-radius: var(--radius-card);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04);
  overflow: hidden;
  backdrop-filter: var(--glass-blur-soft);
  -webkit-backdrop-filter: var(--glass-blur-soft);
}

.set-row {
  display: flex; align-items: center; gap: 14px; padding: 14px 16px;
  background: rgba(16, 18, 21, 0.7); border: 1px solid var(--line);
  border-radius: var(--radius); margin-bottom: 8px;
  backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
  transition: background 0.15s;
}
/* Hover lifts the ROW inside the panel rather than lighting its border --
   there is no longer a per-row border to light. */
.set-group > .set-row:hover { background: rgba(255, 255, 255, 0.022); }

/* Profile's connection rows live in the same panels, so they lose their
   own borders for the same reason the settings rows did. */
.set-panel > .conn-row {
  background: none;
  border: 0;
  border-top: 1px solid var(--glass-edge-soft);
  border-radius: 0;
  margin: 0;
}
.set-panel > .conn-row:first-child { border-top: 0; }
.set-panel > .conn-row:hover { background: rgba(255, 255, 255, 0.022); }
/* The "More ways to sign in" fold sits inside the panel too, so its rows
   line up with the ones above rather than starting a new stack. */
.set-panel > .conn-more { border-top: 1px solid var(--glass-edge-soft); }
.set-panel > .conn-more > summary { padding: 11px 16px; }

/* The avatar marks the profile page with a ring rather than a fill: it is
   a picture, and a rectangle behind a round photo reads as a mistake. */
.side-avatar.on img,
.side-avatar.on .side-initial {
  box-shadow: 0 0 0 2px var(--blue);
}

/* The settings overlay sits above EVERYTHING, the app rail included.
 *
 * .sheet-wrap is z-index 40 and the rail is 60, so the rail drew straight
 * over the modal's own section list -- the left third of the dialog was
 * simply gone, and the panel looked like it had lost its navigation. A
 * modal that something else can cover is not a modal. */
#setSheet { z-index: 80; }
/* Support is opened FROM Settings ("Contact support" is a settings row),
 * so it has to stand above the settings overlay the way the AI sheet
 * does. At 40 it opened underneath: the person pressed the button and saw
 * nothing change, and the form only appeared once Settings was closed. Same
 * z-index as the dialog and later in the markup, which is what wins. */
#supportSheet { z-index: 80; }

/* A text field inside a settings row. */
.set-input {
  width: 190px; padding: 6px 10px;
  background: rgba(0, 0, 0, 0.22);
  border: 1px solid var(--glass-edge-soft);
  border-radius: 8px;
  color: var(--text); font: 400 13px var(--sans);
  outline: none; transition: border-color 0.15s, background 0.15s;
}
.set-input:focus {
  background: rgba(0, 0, 0, 0.3);
  border-color: rgba(255, 255, 255, 0.16);
}
body.light .set-input { background: rgba(12, 16, 23, 0.04); }
body.light .set-input:focus { background: rgba(12, 16, 23, 0.06); }

/* Three choices as one control: a theme is one of a small closed set, and
   a segmented control says that in its shape. A dropdown for three options
   hides two of them behind a click. */
.seg {
  display: flex; gap: 2px; padding: 2px;
  background: rgba(0, 0, 0, 0.22);
  border: 1px solid var(--glass-edge-soft);
  border-radius: 9px;
}
.seg button {
  padding: 4px 11px; border: 0; border-radius: 7px;
  background: none; color: var(--faint);
  font: 400 12.5px var(--sans); cursor: pointer;
  transition: background 0.15s, color 0.15s;
}
.seg button:hover { color: var(--text); }
.seg button.on {
  background: var(--film-strong); color: var(--text);
  box-shadow: inset 0 0 0 1px var(--glass-edge-soft);
}
body.light .seg { background: rgba(12, 16, 23, 0.04); }

/* The profile header inside the dialog: the stats sit under the name
   rather than beside it, because the panel is narrower than the page was. */
#setSheet .prof-head { margin-bottom: 18px; }

/* The AI dialog wears the settings dialog's clothes, so both overlays are
   one thing the eye already knows. */
#aiSheet { z-index: 80; }
.ai-pane { display: none; }
.ai-pane.showing { display: block; }
.ai-pane > h2 {
  font-family: var(--sans); font-size: 16px; font-weight: 500;
  letter-spacing: 0; text-transform: none; color: var(--text);
  margin: 0 0 4px;
}

/* ---- the settings modal ------------------------------------------------
 * Two panes: the sections on the left, the one you picked on the right.
 * The same shape every desktop app uses for settings, and the reason it is
 * a shape at all -- a settings screen is a list of areas, and a list of
 * areas wants a list.
 *
 * Sized in viewport units with a ceiling: big enough that a section is not
 * a letterbox, never so big it stops reading as a window over the app.
 */
.set-modal {
  display: grid;
  grid-template-columns: 216px minmax(0, 1fr);
  width: min(920px, 92vw);
  height: min(660px, 86vh);
  background: var(--panel);
  border: 1px solid var(--glass-edge);
  border-radius: 16px;
  overflow: hidden;
  box-shadow:
    inset 0 1px 0 var(--glass-lit),
    0 40px 120px rgba(0, 0, 0, 0.6);
  animation: sheetIn 0.28s ease;
}

/* The modal's own rail. Deliberately flat rather than glass: it is inside
   a panel, not over the page, and glass on glass reads as fog. */
.set-side {
  display: flex; flex-direction: column;
  padding: 18px 10px 14px;
  background: rgba(0, 0, 0, 0.18);
  border-right: 1px solid var(--glass-edge-soft);
  overflow-y: auto;
}
.set-side-head {
  padding: 0 10px 12px;
  font-size: 15px; font-weight: 500; color: var(--text);
}

/* min-height: 0 on BOTH, and it is the whole reason this scrolls.
 *
 * A flex child's default min-height is auto, which means "at least as tall
 * as my content" -- so a long list made .set-main grow past the dialog
 * instead of making .set-scroll scroll inside it, and overflow-y: auto had
 * nothing to overflow. The agent list ran off the bottom of the panel with
 * no way to reach the end of it. */
.set-main {
  position: relative; min-width: 0; min-height: 0;
  display: flex; flex-direction: column;
}
.set-scroll {
  flex: 1; min-height: 0;
  overflow-y: auto; overscroll-behavior: contain;
  padding: 26px 28px 30px;
}
/* A thin rail rather than the platform's chunky default, matching the one
   in the drawer. */
.set-scroll::-webkit-scrollbar { width: 8px; }
.set-scroll::-webkit-scrollbar-thumb {
  background: rgba(255, 255, 255, 0.14);
  border-radius: 4px;
  border: 2px solid transparent;
  background-clip: content-box;
}
.set-scroll::-webkit-scrollbar-thumb:hover { background: rgba(255, 255, 255, 0.22); background-clip: content-box; }
body.light .set-scroll::-webkit-scrollbar-thumb { background: rgba(12, 16, 23, 0.18); background-clip: content-box; }

/* The section list can outgrow its rail too, on a small window. */
.set-side { min-height: 0; }

/* Close sits in the panel's own corner, out of the content's way. */
.set-close {
  position: absolute; top: 14px; right: 14px; z-index: 2;
  display: grid; place-items: center;
  width: 28px; height: 28px;
  border: 0; border-radius: 8px; background: none;
  color: var(--faint); cursor: pointer;
  transition: background 0.15s, color 0.15s;
}
.set-close:hover { background: var(--film); color: var(--text); }

/* ---- the section list --------------------------------------------------
 * A vertical list now that it has a rail to live in, rather than the
 * horizontal strip it was when settings was a full page. */
.set-nav { display: flex; flex-direction: column; gap: 1px; }
.set-nav button {
  display: flex; align-items: center; gap: 9px;
  width: 100%; padding: 7px 10px;
  border: 0; border-radius: 8px;
  background: none; color: var(--muted);
  font: 400 13px var(--sans); text-align: left; cursor: pointer;
  transition: background 0.15s, color 0.15s;
}
.set-nav button:hover { background: var(--film); color: var(--text); }
.set-nav button.on {
  background: var(--film-strong); color: var(--text);
  box-shadow: inset 0 0 0 1px var(--glass-edge-soft);
}

/* One section at a time. The whole point of the left rail is that the
   right side shows what you picked; scrolling past six other sections to
   reach Privacy is the page this was meant to replace. */
.set-scroll .set-group { display: none; }
.set-scroll .set-group.showing { display: block; }
.set-scroll .set-group > h2 {
  font-family: var(--sans); font-size: 16px; font-weight: 500;
  letter-spacing: 0; text-transform: none; color: var(--text);
  margin: 0 0 4px;
}
.set-sub {
  margin: 0 0 18px; color: var(--faint); font-size: 13px;
}

body.light .set-side { background: rgba(12, 16, 23, 0.03); }
.set-row .lab { font-size: 13.5px; }
.set-row .hint { font-size: 12px; color: var(--faint); margin-top: 2px; }
.set-row .ctl { margin-left: auto; display: flex; align-items: center; gap: 9px; }
.set-val {
  font-family: var(--mono); font-size: 11.5px; color: var(--muted);
  background: var(--film); border: 1px solid var(--line);
  border-radius: 7px; padding: 5px 9px;
}
.set-mini {
  font-size: 12.5px; color: var(--muted); background: var(--film);
  border: 1px solid var(--line); border-radius: var(--radius-pill);
  padding: 6px 12px; cursor: pointer; font-family: var(--sans);
  transition: color 0.15s, border-color 0.15s, background 0.15s;
}
.set-mini:hover { color: var(--text); border-color: var(--line-strong); }
.set-mini.go { background: var(--blue); border-color: var(--blue); color: #04122e; font-weight: 500; }
.set-mini.go:hover { filter: brightness(1.08); color: #04122e; }
.set-mini:disabled { opacity: 0.5; cursor: default; }

.sw {
  width: 38px; height: 22px; border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, 0.1); position: relative; cursor: pointer;
  transition: background 0.22s; border: 0; flex: none;
}
.sw::after {
  content: ""; position: absolute; top: 3px; left: 3px; width: 16px; height: 16px;
  border-radius: 50%; background: #fff;
  transition: transform 0.24s cubic-bezier(0.3, 1.4, 0.5, 1);
}
.sw.on { background: var(--blue); }
.sw.on::after { transform: translateX(16px); }

/* ---- the updater ------------------------------------------------------- */
.upd {
  background: rgba(16, 18, 21, 0.7); border: 1px solid var(--line);
  border-radius: var(--radius-card); padding: 18px;
  backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
  transition: border-color 0.3s;
}
.upd.ready { border-color: rgba(124, 108, 255, 0.35); }
.upd.done { border-color: rgba(214, 188, 255, 0.3); }
.upd-top { display: flex; align-items: center; gap: 13px; }
.upd-ic {
  width: 38px; height: 38px; border-radius: 11px; flex: none;
  display: grid; place-items: center; background: var(--film);
  border: 1px solid var(--line); transition: background 0.3s, border-color 0.3s;
}
.upd.ready .upd-ic { background: rgba(124, 108, 255, 0.12); border-color: rgba(124, 108, 255, 0.3); }
.upd.done .upd-ic { background: rgba(214, 188, 255, 0.12); border-color: rgba(214, 188, 255, 0.3); }
.upd-t { font-size: 14px; font-weight: 500; letter-spacing: -0.015em; }
.upd-s { font-size: 12.5px; color: var(--muted); margin-top: 3px; }
.upd-act { margin-left: auto; display: flex; gap: 8px; align-items: center; }
.upd-bar {
  height: 4px; border-radius: var(--radius-pill); background: var(--film-strong);
  margin-top: 15px; overflow: hidden; display: none;
}
.upd.working .upd-bar { display: block; }
.upd-bar i {
  display: block; height: 100%; width: 0%; border-radius: var(--radius-pill);
  background: linear-gradient(90deg, #3a63d8, var(--blue)); transition: width 0.3s ease;
}
.upd-notes { margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--line); display: none; }
.upd.ready .upd-notes, .upd.working .upd-notes { display: block; }
.upd-notes h3 {
  font-family: var(--mono); font-size: 10px; letter-spacing: 0.13em;
  text-transform: uppercase; color: var(--faint); margin: 0 0 8px; font-weight: 400;
}
.upd-notes ul { margin: 0; padding-left: 17px; color: var(--muted); font-size: 12.5px; line-height: 1.75; }
.upd-spin {
  width: 15px; height: 15px; border-radius: 50%;
  border: 2px solid rgba(124, 108, 255, 0.25); border-top-color: var(--blue);
  animation: updspin 0.8s linear infinite;
}
@keyframes updspin { to { transform: rotate(360deg); } }

/* ---- profile ----------------------------------------------------------- */
.prof-head {
  display: flex; align-items: center; gap: 18px; margin-bottom: 28px; padding: 22px;
  background: rgba(16, 18, 21, 0.7); border: 1px solid var(--line);
  border-radius: 16px; backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
}
.prof-av {
  width: 64px; height: 64px; border-radius: 50%; flex: none; display: grid; place-items: center;
  background: linear-gradient(140deg, #2b3a63, #16203a);
  border: 1px solid rgba(124, 108, 255, 0.28);
  font-size: 24px; font-weight: 500; color: var(--blue-soft); overflow: hidden;
}
.prof-av img { width: 100%; height: 100%; object-fit: cover; }
.prof-name { font-size: 19px; font-weight: 500; letter-spacing: -0.02em; }
.prof-mail { font-size: 13px; color: var(--muted); margin-top: 3px; }
.prof-stat { margin-left: auto; display: flex; gap: 24px; text-align: right; }
.prof-stat .n { font-size: 20px; font-weight: 500; font-variant-numeric: tabular-nums; }
.prof-stat .l {
  font-family: var(--mono); font-size: 10px; letter-spacing: 0.1em;
  color: var(--faint); text-transform: uppercase; margin-top: 3px;
}
.conn-row {
  display: flex; align-items: center; gap: 13px; padding: 14px 16px;
  background: rgba(16, 18, 21, 0.7); border: 1px solid var(--line);
  border-radius: var(--radius); margin-bottom: 8px;
  backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
  transition: border-color 0.2s;
}
.conn-row:hover { border-color: var(--line-strong); }
.conn-row .lg {
  width: 34px; height: 34px; border-radius: 9px; flex: none; display: grid; place-items: center;
  /* A white film over a dark panel, which on paper was white on white. The
     marks inside are currentColor now, so the badge carries their ink. */
  background: var(--film); border: 1px solid var(--line);
  color: var(--text);
}
.conn-row .nm { font-size: 13.5px; }
.conn-row .sub { font-size: 12px; color: var(--faint); margin-top: 2px; }
.conn-row .act { margin-left: auto; }
.linked {
  display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--cyan);
  background: rgba(214, 188, 255, 0.08); border: 1px solid rgba(214, 188, 255, 0.22);
  border-radius: var(--radius-pill); padding: 5px 11px;
}

/* ---- the new home hero ------------------------------------------------- */
.home-v2 {
  min-height: 100%; display: flex; flex-direction: column;
  align-items: center; justify-content: center; padding: 16px 26px 0;
  position: relative; z-index: 1;
}
.home-v2 .display { text-align: center; text-wrap: balance; }
/* The same width as the box above, because they are one column.
 *
 * The list was min(660px, 100%) against the composer's min(620px, 86vw) --
 * 40px wider, invisible while the rows were transparent and obvious the
 * moment one is hovered: the highlight sticks out past the prompt box on
 * both sides and the column stops looking aligned.
 *
 * Written the same way rather than as 620px, so the two cannot drift apart
 * again the next time one of them is adjusted. */
.sugg-list { width: min(620px, 86vw); margin-top: 14px; }
.sugg-list button {
  width: 100%; display: flex; align-items: center; gap: 11px; font-family: var(--sans);
  font-size: 13.5px; color: var(--muted); background: transparent; border: 0;
  border-radius: 11px; padding: 11px 13px; cursor: pointer; text-align: left;
  transition: background 0.16s, color 0.16s;
}
.sugg-list button:hover { background: var(--film); color: var(--text); }
.sugg-list .ic {
  width: 24px; height: 24px; border-radius: 7px; border: 1px solid rgba(255, 255, 255, 0.1);
  display: grid; place-items: center; flex: none;
}
.sugg-list .ic i {
  width: 9px; height: 9px; border: 1.3px solid rgba(255, 255, 255, 0.35);
  border-radius: 3px; transform: rotate(45deg); display: block;
}
.sugg-list .go { margin-left: auto; opacity: 0; transition: opacity 0.16s, transform 0.16s; transform: translateX(-4px); }
.sugg-list button:hover .go { opacity: 0.6; transform: none; }

/* ---- corrections after seeing it run ----------------------------------
 * Four things the running app showed that the mockup could not:
 *
 * 1. The horizon never appeared. `.home > :not(.home-glow)` lifts every
 *    child to z-index 1, and it names the OLD glow element -- so the new
 *    gradient was a child like any other and sat under an opaque stack.
 * 2. The dock sat on top of the app cards. The home view scrolls in its
 *    own element, so its bottom padding, not the page's, is what clears
 *    the dock.
 * 3. The headline used the 52px display scale meant for a landing page.
 * 4. Nothing dimmed on scroll, because there was nothing to dim. */


/* The hero, sized for an app window rather than a marketing page. */

/* Room for the dock to float over, and a shorter run-up to the apps. */


/* ---- what the running app showed, round two ---------------------------
 * 1. The dock was unclickable. `.view` is a 100vh flex column and the
 *    fixed dock sat at z-index 60 -- but the home view's own stacking
 *    context put its children above it, so every click landed on the page
 *    behind. The dock is chrome for the whole window: it needs a z-index
 *    that no view can climb over.
 * 2. The horizon never showed because it was absolute inside a scrolling
 *    box (fixed above), AND because the old .home-glow rule still lifted
 *    every child above it. That rule named an element that no longer
 *    exists; it is retired here rather than left to fight the new one.
 * 3. The diamond in front of each suggestion was decoration pretending to
 *    be an icon. Removed. */


.dockwrap { z-index: 9000; }

/* Suggestions: the text is the thing. */
.sugg-list .ic { display: none; }
.sugg-list button { padding-left: 15px; }


/* ---- round three: what the screenshots showed --------------------------
 * The gradient was never a stacking problem. Every radial was centred at
 * 120-126% of the element -- below the window -- so only its faintest
 * outer edge reached the screen. Centres now sit on the bottom edge.
 * The horizon is also taller and lives behind every page, not just home,
 * because the dock and the cloud grid sit over the same light. */

/* the horizon keeps its own geometry now */

/* A trending mark in front of each suggestion: these are the things people
 * are actually making, so the icon says "popular", not "generic bullet". */
.sugg-list .trend {
  width: 22px; height: 22px; flex: none; display: grid; place-items: center;
  color: var(--blue); opacity: 0.85;
}
.sugg-list button:hover .trend { opacity: 1; }

/* Settings that are not wired to anything yet read as unavailable rather
 * than pretending to work. */
.set-row.soon { opacity: 0.45; }
.set-row.soon:hover { border-color: var(--line); }
.sw[data-soon] { cursor: default; opacity: 0.6; }
.set-mini[data-soon] { cursor: default; opacity: 0.5; }
.set-row .soon-tag {
  font-family: var(--mono); font-size: 9.5px; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--faint);
  border: 1px solid var(--line); border-radius: var(--radius-pill);
  padding: 3px 8px;
}

/* ---- the cloud's loading shell ---------------------------------------- */
.skel-card {
  background: rgba(16, 18, 21, 0.6); border: 1px solid var(--line);
  border-radius: var(--radius-card); padding: 0 0 14px; overflow: hidden;
  opacity: 0; animation: skelIn 0.4s ease forwards;
}
@keyframes skelIn { to { opacity: 1; } }
.skel-thumb {
  height: 132px; background: linear-gradient(100deg,
    rgba(255,255,255,0.03) 30%, rgba(255,255,255,0.07) 50%, rgba(255,255,255,0.03) 70%);
  background-size: 220% 100%; animation: shimmer 1.5s ease-in-out infinite;
  border-bottom: 1px solid var(--line);
}
.skel-line {
  height: 10px; margin: 13px 14px 0; border-radius: 5px;
  background: linear-gradient(100deg,
    rgba(255,255,255,0.04) 30%, rgba(255,255,255,0.08) 50%, rgba(255,255,255,0.04) 70%);
  background-size: 220% 100%; animation: shimmer 1.5s ease-in-out infinite;
}
.skel-line.short { width: 45%; height: 8px; margin-top: 9px; }
@keyframes shimmer {
  from { background-position: 130% 0; }
  to { background-position: -130% 0; }
}
@media (prefers-reduced-motion: reduce) {
  .skel-thumb, .skel-line { animation: none; }
}

/* The horizon now lives at body level so every page shares one light.
 * The .home-scoped rules above no longer reach it, and the views must sit
 * over it -- they are opaque only where they paint, and a view with no
 * background lets the light through by design. */
body > .horizon, body > .grain { position: fixed; z-index: 0; }
.view { position: relative; z-index: 1; background: transparent; }

/* ---- centring, round four ---------------------------------------------
 * The hero sat high because .home-hero carries a 9vh top padding from the
 * old layout, on top of the flex centring. With the apps grid gone there
 * is nothing below it to balance against, so the padding just pushed
 * everything up. The column centres itself now. */

/* ---- light theme -------------------------------------------------------
 * Not an inversion: the dark room is the design, so the light one keeps
 * the same structure and swaps the ground for paper, with the blue kept
 * as the one warm-cold accent so the horizon still reads. */
/* (The real light palette is the `body.light` block further down, near the
   end of the file -- it lands last and is the one that wins. Everything
   theme-related belongs there, not here.) */

/* Dark keeps the values the rules were written with, so the fallbacks in
   `var(--x, …)` never have to carry the design. */
body:not(.light) {
  --film: rgba(255, 255, 255, 0.05);
  --film-strong: rgba(255, 255, 255, 0.10);
}
body.light .dock {
  background: rgba(255, 255, 255, 0.78);
  border-color: rgba(13, 16, 21, 0.1);
  box-shadow: 0 14px 40px rgba(13, 16, 21, 0.14);
}
body.scrolled.light .dock {
  background: rgba(255, 255, 255, 0.97);
  border-color: rgba(13, 16, 21, 0.16);
  box-shadow: 0 20px 52px rgba(13, 16, 21, 0.2);
}
body.light .dock .glide { background: #0d1015; }
body.light .dock button.on { color: #ffffff; }
body.light .dock-solo,
body.scrolled.light .dock-solo {
  background: rgba(255, 255, 255, 0.9); border-color: rgba(13, 16, 21, 0.1);
}

/* The composer on paper. The dark theme's focus state brightens a white
   tint and a white rim, both of which are invisible here, so the same
   gesture is expressed the other way: the pane whitens toward solid and
   its shadow deepens, which is what a card lifting off paper does. */
body.light .composer-box {
  box-shadow:
    inset 0 1px 0 var(--glass-lit),
    0 6px 20px rgba(12, 16, 23, 0.07);
}
body.light .composer-box:focus-within {
  background: rgba(255, 255, 255, 0.92);
  border-color: rgba(12, 16, 23, 0.18);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.95),
    0 8px 26px rgba(12, 16, 23, 0.12);
}
/* Cards on paper: a plain white face with an ink hairline. The dark
   theme's white-on-white gradient would be invisible. */
body.light .app-card {
  background: var(--panel);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.9);
}

body.light .composer-shell .bigbar,
body.light .set-row, body.light .conn-row, body.light .prof-head,
body.light .upd, body.light .skel-card {
  background: rgba(255, 255, 255, 0.9);
}
body.light .btn.btn-primary { color: #ffffff; }

/* ---- the hero, sized once ---------------------------------------------
 * One rule, at the end, after the consolidation above. The 52px display
 * scale belongs to a landing page; an app window wants something that
 * does not fill a third of the screen. */
.home-title.display { font-size: clamp(22px, 2.1vw, 27px); letter-spacing: -0.012em; }
.home-sub { font-size: 14.5px; margin: 12px 0 32px; }

/* ---- light theme, done as a real palette ------------------------------
 * Not an inversion of the dark one: the same structure, re-grounded on
 * paper. The neutrals carry a slight blue bias so they belong to the same
 * family as the accent rather than reading as generic grey, and the
 * borders are darker than a naive flip so cards keep their edges on a
 * light ground -- the thing that usually breaks when a dark UI is
 * inverted. */
body.light { --reply: rgba(12, 16, 23, 0.92); }
body.light {
  --bg: #eef1f6;
  --panel: #ffffff;
  --panel-raised: #f5f7fa;
  --line: #dde2ea;
  --line-strong: #c2cad8;
  --text: #0c1017;

  /* Glass has to be INVERTED on paper, not merely dimmed.
     Every value in the dark palette is white at a low alpha: a lit rim, a
     pale tint, a bright hairline. On a white page all three vanish -- white
     on white is nothing -- so the composer lost its edge entirely and read
     as a hole rather than a surface.
     On paper the same physics point the other way: a pane over white casts
     a faint shade, and its edge is DARKER than what surrounds it. So the
     tint goes slightly dark, the rim becomes a soft shadow rather than a
     highlight, and the borders are ink at low alpha. */
  --glass-chrome: rgba(255, 255, 255, 0.62);
  --glass-card: rgba(255, 255, 255, 0.52);
  --glass-edge: rgba(12, 16, 23, 0.10);
  --glass-edge-soft: rgba(12, 16, 23, 0.07);
  --glass-lit: rgba(255, 255, 255, 0.85);
  /* Weightier than a straight flip of the dark values.
     Ink on paper needs more of it than light on black to read as the same
     strength. At 0.6 / 0.4 / 0.16 the secondary lines measured 2.6:1 and
     the faintest 1.4:1 against their own backgrounds -- under the 3:1 floor
     for text anyone is expected to read, across all nine views. */
  --muted: rgba(12, 16, 23, 0.72);
  --faint: rgba(12, 16, 23, 0.56);
  /* --ghost is the faintest step, but it still carries TEXT -- the
     composer's placeholder, the hint under it, capability labels. It has to
     clear 3:1 like anything else meant to be read, which on paper is about
     0.48, not the 0.16 a straight inversion suggests. */
  --ghost: rgba(12, 16, 23, 0.48);
  --quiet: rgba(12, 16, 23, 0.56);
  --blue: #3f6fe0;
  --blue-soft: #2b52ac;

  /* The two big panels, both hardcoded before: #0F0F10 for the drawer and
     #131315 for the shelf. On paper they stayed black slabs -- 240px of
     drawer down the side and the whole floor of the room. White here,
     because on a light ground a surface reads as RAISED by being lighter
     than the room, the exact inverse of dark mode. */
  --side-bg: #ffffff;
  --shelf-bg: #ffffff;

  /* The send button: quiet until there is something to send, then bright.
     Every value was a hardcoded white, so on paper it was a white circle
     with a white arrow in it -- the control this whole screen exists for.
     "Bright" in light mode means ink. */
  --send-idle-bg: rgba(12, 16, 23, 0.07);
  --send-idle-fg: rgba(12, 16, 23, 0.55);
  --send-hover-bg: rgba(12, 16, 23, 0.12);
  --send-hover-fg: #0c1017;
  --send-ready-bg: #0c1017;
  --send-ready-fg: #ffffff;
  --send-ready-hover: #232833;

  /* A white film over a dark ground has to become a dark film over a light
     one, or the hover states and raised strips simply are not there. */
  --film: rgba(12, 16, 23, 0.05);
  --film-strong: rgba(12, 16, 23, 0.09);
}

/* The horizon is the product's signature, so it survives the theme --
 * lighter, but still blue light coming up from under the page.
 *
 * It is a DARK indigo glow, which is light against black and mud against
 * paper: at 40% it greyed the lower half of the window and took every word
 * on it down too. Light mode gets its own gradient rather than a dimmed
 * copy of the dark one -- same colour, far weaker, so the accent is a tint
 * instead of a haze. */
body.light .horizon {
  opacity: 1;
  background: radial-gradient(80% 100% at 50% 100%,
    rgba(99, 91, 214, 0.13), transparent 68%);
}

/* The field canvas is a violet dome painted for a black room: it composes
 * at up to ~0.5 alpha, which on paper is a purple wash over the whole
 * window. The painting code has no idea which theme is on -- it is one code
 * path by design -- so the theme is applied here rather than forking the
 * renderer. `multiply` keeps it a tint of the page, not a film over it. */

body.light .grain { opacity: 0.1; mix-blend-mode: multiply; }

/* Surfaces: white cards on the tinted ground, with a real border. */
body.light .bigbar,
body.light .set-row, body.light .conn-row, body.light .prof-head,
body.light .upd, body.light .skel-card, body.light .app-card,
body.light .cloud-card, body.light .sheet {
  background: #ffffff;
  border-color: var(--line);
  box-shadow: 0 1px 2px rgba(12, 16, 23, 0.04);
}
body.light .bigbar { box-shadow: 0 10px 30px rgba(12, 16, 23, 0.07); }

/* The dock: dark pill on a light ground, which is the reference exactly. */
body.light .dock {
  background: rgba(255, 255, 255, 0.82);
  border-color: rgba(12, 16, 23, 0.09);
  box-shadow: 0 12px 34px rgba(12, 16, 23, 0.13);
}
body.scrolled.light .dock {
  background: #ffffff;
  border-color: rgba(12, 16, 23, 0.14);
  box-shadow: 0 18px 44px rgba(12, 16, 23, 0.18);
}
body.light .dock .glide { background: #0c1017; }
body.light .dock button.on { color: #ffffff; }
body.light .dock button:not(.on) { color: rgba(12, 16, 23, 0.58); }
body.light .dock button:not(.on):hover { color: var(--text); }
body.light .dock-solo,
body.scrolled.light .dock-solo {
  background: rgba(255, 255, 255, 0.92);
  border-color: rgba(12, 16, 23, 0.09);
  box-shadow: 0 12px 34px rgba(12, 16, 23, 0.13);
}

/* The send button's ink comes from --send-*-fg now. These two rules forced
   #fff, which was right when the button was a blue disc and wrong the
   moment its idle state became a pale chip: white arrow, white chip. */
body.light .btn.btn-primary { background: var(--text); color: #ffffff; border-color: var(--text); }
body.light .set-mini.go { color: #ffffff; }
/* The title bar stays transparent in light mode too.
 *
 * It was painted white at 72% over a #eef1f6 page, which drew a bright strip
 * across the top of every view and a rule under it -- exactly the header the
 * base rule refuses to draw ("The window is one surface: a line here draws a
 * header that is not really there"). Dark mode has no background on it at
 * all; light mode should not either. The controls in the bar carry their own
 * ink, so nothing needs a plate behind it. */
body.light .tchip, body.light .agent-chip, body.light .aichip {
  background: #ffffff; border-color: var(--line);
}
body.light .skel-thumb, body.light .skel-line {
  background: linear-gradient(100deg,
    rgba(12,16,23,0.04) 30%, rgba(12,16,23,0.08) 50%, rgba(12,16,23,0.04) 70%);
  background-size: 220% 100%;
}
body.light .thumb-well { background: var(--panel-raised); }


/* ---- one bottom for every page ----------------------------------------
 * Measured, not guessed: .page-scroll reserved 110px, .home 104px and
 * .cloud 64px, so the dock sat at a different distance from the content
 * on each page. They all read --dock-clear now. */
.cloud { padding-bottom: var(--dock-clear); }

/* ---- the hero sits where the eye expects ------------------------------
 * Centred in the column looks LOW, because the titlebar takes 60px off
 * the top and the dock's clearance takes 108px off the bottom -- the
 * optical centre is above the geometric one. Pulling it up by the
 * difference puts it where a person reads it as centred. */
/* Measured: with 108px reserved at the bottom and nothing at the top,
 * centring inside that box puts the hero 54px above the optical centre
 * (top gap 301, bottom gap 409 in a 1200px window). Matching the top
 * padding to the bottom splits the free space evenly, so centred looks
 * centred. */
.home { justify-content: center; padding-top: var(--dock-clear); }

/* ---- the greeting ------------------------------------------------------
 * Above the headline, quiet: it says the app knows who you are without
 * competing with the thing you came to do. */
.home-greet {
  font-size: 14px; color: var(--muted); margin: 0 0 10px;
  text-align: center; letter-spacing: -0.005em;
}
.home-greet:empty { display: none; }


/* The divider had z-index without position, so the stacking order it
 * asked for never applied and the rail's border sat on top of it. Drag
 * was dead for that reason alone. */
.divider { position: relative; }

/* The greeting reads as one sentence with the headline rather than a
 * label stacked above it. */
.home-greet { font-size: 15px; margin-bottom: 4px; }

/* =======================================================================
 * First run: a moment, not a form.
 *
 * Three scenes. The first is a fanned spread of real apps people built
 * with Krate, because the promise is visual and a screenshot argues it
 * better than a paragraph. Everything animates in on a stagger, the way
 * the rest of the app already does.
 * ===================================================================== */

#viewOnboard { position: relative; overflow: hidden; }

/* A wash behind the cards so they sit in a space rather than on a wall. */
/* Onboarding is the first thing anybody sees, so it has to be the same
   product as the rest of the app. It had its own violet sky -- two big
   radial washes -- which survived the field's removal and would have left
   the welcome screen glowing while every screen after it was calm. The
   fan of real apps is the colour on this screen now, which is the right
   thing to be looking at anyway. */
.ob-sky { display: none; }

.ob-skip {
  position: absolute; top: 20px; right: 22px; z-index: 20;
  background: none; border: 0; color: var(--faint); cursor: pointer;
  font-family: var(--sans); font-size: 13px; padding: 8px 12px;
  border-radius: var(--radius-pill); transition: color 0.15s;
}
.ob-skip:hover { color: var(--text); }

/* Centred when it fits, scrollable when it does not.
 *
 * The agent step is the tall one -- seven rows, a heading, a subhead and a
 * button -- and a plain centred flex box CLIPS what does not fit, at both
 * ends at once. On a short window that meant the heading was cut off at the
 * top and Continue was somewhere below the bottom edge, with no way to reach
 * either. `auto` margins centre the child while still letting the box
 * scroll; `align-items: center` does not. */
.ob-stage {
  position: relative; z-index: 1; height: 100%;
  display: flex; justify-content: center;
  overflow-y: auto;
  padding: 32px;
}
.ob-scene { display: none; width: min(620px, 100%); text-align: center; }
/* `margin: auto 0` is what centres this inside a SCROLLABLE parent. The
   stage cannot use align-items for that, because centring a flex child
   taller than its box clips it at both ends instead of letting it scroll. */
.ob-scene.on { display: block; margin: auto 0; }

.ob-h {
  font-family: var(--sans); font-weight: 500;
  font-size: clamp(26px, 3.2vw, 36px); letter-spacing: -0.04em;
  line-height: 1.1; margin: 0; text-wrap: balance;
  opacity: 0; transform: translateY(14px);
  animation: obUp 0.6s cubic-bezier(0.2, 0.9, 0.3, 1) 0.35s forwards;
}
.ob-p {
  color: var(--muted); font-size: 14.5px; margin: 10px 0 0;
  opacity: 0; transform: translateY(14px);
  animation: obUp 0.6s cubic-bezier(0.2, 0.9, 0.3, 1) 0.44s forwards;
}
@keyframes obUp { to { opacity: 1; transform: none; } }

/* ---- the fan ----------------------------------------------------------
 * Five real apps, the middle one lifted and lit. Each card flies in from
 * a stack into its place, so the first thing the app ever does is show
 * what it makes. */
.fan {
  position: relative; height: 260px; margin-bottom: 34px;
  display: flex; align-items: center; justify-content: center;
  perspective: 1200px;
}
.fan-card {
  position: absolute; width: 210px; height: 140px; object-fit: cover;
  border-radius: 14px; border: 1px solid rgba(255, 255, 255, 0.1);
  box-shadow: 0 18px 40px rgba(0, 0, 0, 0.5);
  opacity: 0; transform: translateY(30px) scale(0.9);
  animation: fanIn 0.85s cubic-bezier(0.2, 0.9, 0.28, 1) forwards;
}
.fan-card:nth-child(1) { animation-delay: 0.04s; --x: -230px; --r: -16deg; --y: 24px; --s: 0.86; --o: 0.5; z-index: 1; }
.fan-card:nth-child(2) { animation-delay: 0.11s; --x: -120px; --r: -8deg;  --y: 10px; --s: 0.93; --o: 0.78; z-index: 2; }
.fan-card:nth-child(3) { animation-delay: 0.18s; --x: 0px;    --r: 0deg;   --y: -6px; --s: 1.1;  --o: 1; z-index: 3; }
.fan-card:nth-child(4) { animation-delay: 0.11s; --x: 120px;  --r: 8deg;   --y: 10px; --s: 0.93; --o: 0.78; z-index: 2; }
.fan-card:nth-child(5) { animation-delay: 0.04s; --x: 230px;  --r: 16deg;  --y: 24px; --s: 0.86; --o: 0.5; z-index: 1; }
.fan-card.lead {
  border-color: rgba(255, 255, 255, 0.22);
  box-shadow: 0 26px 60px rgba(0, 0, 0, 0.65), 0 0 0 1px rgba(124, 108, 255, 0.2);
}
@keyframes fanIn {
  to {
    opacity: var(--o);
    transform: translate(var(--x), var(--y)) rotate(var(--r)) scale(var(--s));
  }
}
/* A slow drift once they land, so the screen breathes. */
.fan:hover .fan-card {
  transition: transform 0.5s cubic-bezier(0.2, 0.9, 0.3, 1);
}
.fan:hover .fan-card:nth-child(3) {
  transform: translate(var(--x), calc(var(--y) - 8px)) rotate(var(--r)) scale(calc(var(--s) + 0.03));
}

/* ---- the one button --------------------------------------------------- */
.ob-cta {
  display: inline-flex; align-items: center; gap: 9px;
  margin-top: 30px; padding: 13px 26px;
  background: var(--text); color: var(--bg); border: 0;
  border-radius: var(--radius-pill); cursor: pointer;
  font-family: var(--sans); font-size: 14.5px; font-weight: 500;
  opacity: 0; transform: translateY(14px);
  animation: obUp 0.6s cubic-bezier(0.2, 0.9, 0.3, 1) 0.55s forwards;
  transition: transform 0.16s cubic-bezier(0.3, 1.4, 0.5, 1), filter 0.16s;
}
.ob-cta:hover { transform: translateY(-2px); filter: brightness(0.94); }
.ob-cta:active { transform: translateY(0) scale(0.98); }
.ob-cta svg { transition: transform 0.2s ease; }
.ob-cta:hover svg { transform: translateX(3px); }

.ob-quiet {
  display: block; margin: 16px auto 0; background: none; border: 0;
  color: var(--faint); font-family: var(--sans); font-size: 13px;
  cursor: pointer; transition: color 0.15s;
  opacity: 0; animation: obUp 0.6s ease 0.7s forwards;
}
.ob-quiet:hover { color: var(--muted); }

/* ---- agents ------------------------------------------------------------ */
.ob-agents {
  display: grid; gap: 8px; margin-top: 26px; text-align: left;
  opacity: 0; transform: translateY(14px);
  animation: obUp 0.6s cubic-bezier(0.2, 0.9, 0.3, 1) 0.5s forwards;
}
.ob-agent {
  display: flex; align-items: center; gap: 12px; width: 100%;
  padding: 15px 17px; background: var(--panel);
  border: 1px solid var(--line); border-radius: var(--radius);
  cursor: pointer; font-family: var(--sans); color: var(--text);
  font-size: 14px; transition: border-color 0.18s, background 0.18s, transform 0.18s;
}
.ob-agent:hover:not(.off) { border-color: var(--line-strong); transform: translateY(-1px); }
.ob-agent.picked { border-color: var(--blue); background: rgba(124, 108, 255, 0.07); }
.ob-agent.off { opacity: 0.4; cursor: default; }
.ob-agent-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--ghost); flex: none; }
.ob-agent-dot.ok { background: var(--cyan); }
.ob-agent-name { font-weight: 500; }
.ob-agent-state {
  margin-left: auto; font-family: var(--mono); font-size: 10.5px;
  letter-spacing: 0.08em; text-transform: uppercase; color: var(--faint);
}
.ob-none { color: var(--faint); font-size: 13px; text-align: center; margin: 10px 0 0; }

.ob-input {
  width: min(360px, 100%); margin: 26px auto 0; display: block;
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 15px 18px; color: var(--text);
  font-family: var(--sans); font-size: 16px; outline: 0; text-align: center;
  opacity: 0; transform: translateY(14px);
  animation: obUp 0.6s cubic-bezier(0.2, 0.9, 0.3, 1) 0.5s forwards;
  transition: border-color 0.2s, box-shadow 0.2s;
}
.ob-input:focus { border-color: rgba(124, 108, 255, 0.5); box-shadow: 0 0 0 3px rgba(124, 108, 255, 0.09); }

@media (prefers-reduced-motion: reduce) {
  .fan-card, .ob-h, .ob-p, .ob-cta, .ob-agents, .ob-input, .ob-quiet {
    animation-duration: 0.01ms !important;
    opacity: 1; transform: none;
  }
  .fan-card { transform: translate(var(--x), var(--y)) rotate(var(--r)) scale(var(--s)); opacity: var(--o); }
}

/* The sign-in mark was 34px on a full screen: technically present,
 * visually absent. It is the only object above the headline, so it
 * carries the moment and needs the size to do it. */
.gate-mark {
  width: 76px; height: 76px; margin: 0 auto 30px; opacity: 1;
  filter: drop-shadow(0 10px 26px rgba(124, 108, 255, 0.22));
}

/* "Hi, Y" sat at 32% opacity muted grey, which on this ground is barely
 * there. It is a greeting to a person by name: it should read. */
.home-greet {
  color: var(--muted);
  font-size: 15px;
  font-weight: 500;
  letter-spacing: 0.01em;
  opacity: 0.92;
  margin-bottom: 8px;
}

/* ---- AI marks ----------------------------------------------------------
 * The tool's own shape, with its status dot tucked into the corner: one
 * glance answers both "which is this" and "can I use it". */
.ai-mark {
  position: relative; width: 30px; height: 30px; flex: none;
  display: grid; place-items: center; border-radius: 9px;
  background: var(--panel-raised); border: 1px solid var(--line);
  color: var(--text);
}
.ai-mark .dot {
  position: absolute; right: -2px; bottom: -2px;
  width: 9px; height: 9px; border-radius: 50%;
  border: 2px solid var(--panel);
}
.ob-agent-mark {
  width: 30px; height: 30px; flex: none; display: grid; place-items: center;
  border-radius: 9px; background: var(--panel-raised);
  border: 1px solid var(--line); color: var(--text);
}
.ob-agent.off .ob-agent-mark { opacity: 0.6; }

/* ---- the greeting ------------------------------------------------------
 * A badge with the initial, then the name. Reads as addressed to someone
 * rather than as a faint label floating over the headline. */
.home-greet {
  display: inline-flex; align-items: center; gap: 9px;
  margin: 0 auto 14px; padding: 5px 14px 5px 5px;
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  color: var(--muted); font-size: 13.5px; font-weight: 400;
  opacity: 1; letter-spacing: 0;
}
.home-greet:empty { display: none; }
.greet-badge {
  width: 24px; height: 24px; border-radius: 50%; flex: none;
  display: grid; place-items: center;
  background: linear-gradient(140deg, #2b3a63, #16203a);
  color: var(--blue-soft); font-size: 11.5px; font-weight: 600;
}

/* ---- the divider, made hittable ---------------------------------------
 * It was 5px wide with -3px/-2px margins, so barely 2px of it could take
 * a pointer, and the rail's 1px border covered part of that. A resize
 * handle nobody can grab is not a handle. 11px with symmetric negative
 * margins keeps the visual seam exactly where it was while giving the
 * cursor something real to land on. */
.divider {
  width: 11px;
  margin-left: -5px; margin-right: -5px;
  position: relative; z-index: 20;
  background: transparent;
}
/* The seam itself stays 1px: the wide part is the hit area, not the line. */
.divider::after {
  content: ""; position: absolute; inset: 0 5px;
  background: transparent; transition: background 0.15s;
}
.divider:hover::after, .divider.dragging::after { background: var(--line-strong); }
body.resizing .divider::after { background: var(--blue); }
.divider:hover, .divider.dragging, body.resizing .divider { background: transparent; }

/* The AI picker's note and Refresh row (K-194). */
/* The old rule here pulled this paragraph UP with margin-top: -6px, back
   when it sat directly under the intro text. It now follows the refresh
   button, so the negative margin dragged it into the button's own line --
   the collision. Styled once, below, with real space above it. */
/* Refresh, its result, and the standing hint -- three things that were
 * laid out as two.
 *
 * The note sat inline beside the button and the hint paragraph sat directly
 * under both, so a refresh result appeared mid-air between them and the
 * three lines read as one run-on sentence. The note belongs on its own
 * line under the button, and the hint needs air above it to be a separate
 * thought rather than a continuation. */
.ai-actions {
  display: flex; flex-direction: column; align-items: flex-start; gap: 8px;
  margin-top: 18px; padding-top: 16px; border-top: 1px solid var(--glass-edge-soft);
}
.ai-refresh-note { color: var(--faint); font-size: 12px; line-height: 1.5; }
.ai-note {
  margin-top: 14px;
  color: var(--quiet); font-size: 12px; line-height: 1.6;
}

/* ------------------------------------------------------------- send sheet */
/* Three ways to send, the card first. Buttons read as choices, not a form. */
.sheet-send .sheet-sub { font-size: 13px; color: var(--muted); margin-top: -6px; }
.send-opts { display: flex; flex-direction: column; gap: 8px; }
.send-opt {
  display: flex; flex-direction: column; gap: 3px; text-align: left;
  padding: 13px 15px; border-radius: 11px; cursor: pointer;
  background: var(--panel-raised); border: 1px solid var(--line);
  color: inherit; font: inherit;
  transition: border-color 0.15s ease;
}
.send-opt:hover { border-color: var(--line-strong); }
.send-opt:disabled { opacity: 0.55; cursor: default; }
.send-opt b { font-size: 14px; font-weight: 500; }
.send-opt span { font-size: 12.5px; color: var(--muted); line-height: 1.45; }
.send-opt-quiet { background: transparent; }
.send-done { display: flex; flex-direction: column; gap: 8px; }
.send-done img {
  max-width: 100%; max-height: 260px; object-fit: contain;
  border: 1px solid var(--line-strong); border-radius: 10px;
  align-self: center;
}
.send-done p { font-size: 12.5px; color: var(--muted); line-height: 1.5; }
body.light .send-opt { background: #f4f5f7; border-color: #e3e5ea; }
body.light .send-opt span, body.light .send-done p { color: rgba(0,0,0,0.55); }

/* --------------------------------------------- the done card is the card */
/* The preview carries the card's own caption strip, so screenshotting the
   done screen is screenshotting the share object. */
.shot-stage { position: relative; }
.shot-cap {
  display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
  padding: 9px 13px; border-top: 1px solid var(--line);
  background: var(--panel-raised); font-size: 11.5px; color: var(--muted);
}
.shot-cap-name { color: var(--text); font-weight: 500; }
.shot-cap-trust { margin-left: auto; color: var(--muted); text-align: right; }
.shot-stage.no-shot .shot-cap { border-top: none; }
/* The one human line is the loudest thing after the name; size and minutes
   stay quiet. */
.asks { font-size: 13.5px; }
.asks li:first-child { color: var(--text); }
.now-stage { font-size: 14px; color: var(--muted); }

/* ------------------------------------------------ the AI sheet's fold */
.ai-terminal { margin-top: 4px; }
.ai-terminal summary {
  cursor: pointer; list-style: none; font-size: 11.5px; color: var(--muted);
  letter-spacing: 0.04em;
}
.ai-terminal summary::-webkit-details-marker { display: none; }
.ai-terminal summary::after { content: " ›"; }
.ai-terminal[open] summary::after { content: " ⌄"; }
.ai-terminal .ai-remedy {
  margin-top: 6px; padding: 8px 10px; border: 1px solid var(--line);
  border-radius: 8px; background: var(--panel);
  font-family: var(--mono, ui-monospace, monospace); font-size: 11px;
  color: var(--muted); white-space: pre-wrap; word-break: break-all;
}
.sheet-foot-fold summary { cursor: pointer; font-size: 12.5px; color: var(--muted); list-style: none; }
.sheet-foot-fold summary::-webkit-details-marker { display: none; }
.sheet-foot-fold summary::after { content: " ›"; }
.sheet-foot-fold[open] summary::after { content: " ⌄"; }
.send-quiet-link {
  align-self: flex-start; background: none; border: none; padding: 0;
  color: var(--quiet, rgba(255,255,255,0.35)); font: inherit; font-size: 12px;
  cursor: pointer; border-bottom: 1px solid var(--line);
}
.send-quiet-link:hover { color: var(--muted); }

/* -------------------------------------------------- the info sheet face */
.info-trust { font-size: 15px; color: var(--text); font-weight: 500; margin-top: 2px; }
.sheet-info .sheet-sub { margin-top: 2px; }
.info-fold { border-top: 1px solid var(--line); padding: 12px 0; }
.info-fold summary { cursor: pointer; list-style: none; font-size: 13px; color: var(--muted); font-weight: 500; }
.info-fold summary::-webkit-details-marker { display: none; }
.info-fold summary::after { content: " ›"; color: var(--quiet, rgba(255,255,255,0.35)); }
.info-fold[open] summary::after { content: " ⌄"; }
.info-fold ul { margin: 10px 0 0; padding-left: 1.1em; font-size: 13.5px; color: var(--muted); }
.info-fold li { margin: 3px 0; }
.info-fold .info-rows { margin-top: 10px; }
.info-fold .btn { margin-top: 10px; }

/* ------------------------------------------- build card, after the diary */
/* The heartbeat line is a quiet sentence, not a clipped terminal box: the
   old style assumed it nested under a stage row that no longer exists. */
.brail .bpeek {
  margin: 10px 0 0; padding: 0; background: none; border: none;
  max-height: none; overflow: visible;
  font-family: var(--sans); font-size: 13px; color: var(--muted);
  line-height: 1.6;
}

/* The done card's caption sits along the bottom of the shot, like the
   card's own strip -- not as a flex column beside it.
 *
 * It is DRAWN OVER the picture, which is what makes the card one
 * screenshot-able object, and that is also its hazard: the bottom of an
 * app's window is where its buttons are. Measured on a pomodoro timer,
 * the strip covered the bottom 18% of the shot at 88% opacity and cut its
 * Start button in half.
 *
 * So the stage reserves room for it. The picture is inset by the strip's
 * own height, the strip sits in that reserved band rather than on top of
 * anything, and the card still reads as one object because the two share
 * a background. Nothing an app drew is hidden. */
.shot-stage .shot-cap {
  position: absolute; left: 0; right: 0; bottom: 0;
  border-top: 1px solid var(--line);
  background: rgba(10, 10, 11, 0.88);
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
}
/* The band the strip lives in, kept clear of the picture above it.
 *
 * Set on the same rule that sizes the image (`.shot-stage img`, above),
 * not as a later override: the sheet opens with `* { margin: 0 }`, and a
 * universal reset beats a compound selector on specificity, so a separate
 * rule here was silently doing nothing. Measured before believing it. */
.shot-stage.no-shot .shot-cap { display: none; }

/* Change must be seen to do something: the composer answers with one
   breath of light when the button sends you to it. */
@keyframes composerGlow {
  0% { box-shadow: 0 0 0 0 rgba(124, 108, 255, 0.0); }
  30% { box-shadow: 0 0 0 3px rgba(124, 108, 255, 0.45); }
  100% { box-shadow: 0 0 0 0 rgba(124, 108, 255, 0.0); }
}
.composer-box.glow { animation: composerGlow 1.1s ease; }

/* ------------------------------------------------ the library's banner */
.apps-finish {
  grid-column: 1 / -1;
  display: flex; align-items: center; justify-content: space-between;
  padding: 13px 18px; border-radius: 12px; cursor: pointer;
  background: var(--panel-raised); border: 1px solid var(--line);
  color: var(--muted); font: inherit; font-size: 14px;
  transition: border-color 0.15s ease;
}
.apps-finish:hover { border-color: var(--line-strong); color: var(--text); }
.apps-finish b { color: var(--text); font-weight: 500; }
.apps-finish .go { color: var(--quiet, rgba(255,255,255,0.35)); }

/* Sign-in options that do nothing yet hide until asked for. */
.conn-more summary { cursor: pointer; list-style: none; font-size: 12.5px; color: var(--muted); padding: 8px 0 2px; }
.conn-more summary::-webkit-details-marker { display: none; }
.conn-more summary::after { content: " ›"; color: var(--quiet, rgba(255,255,255,0.35)); }
.conn-more[open] summary::after { content: " ⌄"; }

/* The dock floats over the page bottom; the scroll area owes it room. */
.page-inner { padding-bottom: 110px !important; }
.send-wrap-os { display: flex; gap: 8px; padding: 2px 4px 0; }
.pub-list-row { display: flex; gap: 10px; align-items: flex-start; font-size: 13.5px; cursor: pointer; }
.pub-list-row input { margin-top: 3px; accent-color: var(--accent, #6291ff); }
.pub-list-row small { display: block; color: var(--muted); font-size: 12px; }
/* The plan chip lives in the title bar beside the theme toggle: quiet
   until hovered, and a door to the plan sheet rather than a label. */
.plan-chip {
  padding: 5px 12px; border-radius: var(--radius-pill);
  border: 1px solid var(--line); background: none; color: var(--faint);
  font-size: 12px; cursor: pointer; white-space: nowrap;
  transition: color 0.15s, border-color 0.15s;
  -webkit-app-region: no-drag;
}
.plan-chip:hover { color: var(--text); border-color: var(--line-strong); }

.plan-list { display: flex; flex-direction: column; gap: 10px; margin-top: 14px; }
.plan-row {
  display: flex; align-items: center; gap: 14px;
  border: 1px solid var(--line); border-radius: 12px; padding: 13px 16px;
}
.plan-row > div { flex: 1; display: flex; flex-direction: column; gap: 3px; }
.plan-row b { font-size: 13.5px; color: var(--text); }
.plan-row span:not(.plan-tag) { font-size: 12.5px; color: var(--muted); line-height: 1.45; }
.plan-row.current { border-color: var(--line-strong); }
.plan-tag {
  font-size: 11.5px; color: var(--faint); white-space: nowrap;
  border: 1px solid var(--line); border-radius: var(--radius-pill);
  padding: 3px 10px;
}
.plan-row.current .plan-tag { color: var(--text); }
.plan-actions { display: flex; gap: 8px; align-items: center; flex: none; }
.plan-actions .btn { padding: 7px 12px; font-size: 12.5px; white-space: nowrap; }
.ref-block {
  margin-top: 16px; padding: 15px 16px;
  border: 1px solid var(--line); border-radius: 12px;
  display: flex; flex-direction: column; gap: 5px;
}
.ref-block b { font-size: 13px; color: var(--text); }
.ref-block > span { font-size: 12.5px; color: var(--muted); line-height: 1.45; }
.ref-box { display: flex; gap: 8px; margin-top: 8px; }
.ref-box input {
  flex: 1; min-width: 0; padding: 8px 12px; border-radius: 9px;
  border: 1px solid var(--line); background: var(--bg); color: var(--muted);
  font: inherit; font-size: 12px;
}
.ref-box .btn { padding: 7px 12px; font-size: 12.5px; flex: none; }
.ref-stat { font-size: 12px; color: var(--faint); margin-top: 6px; }
.plan-chip.upsell { border-color: var(--accent, #7ee2a8); color: var(--text); }
.sup-priority {
  margin-left: 8px; vertical-align: 2px;
  color: #7ee2a8; border-color: rgba(126, 226, 168, 0.35);
}
#setPlanUsed, #setPlanBtn { white-space: nowrap; }

/* ---- session-local aurora ---------------------------------------------
 * The shared horizon remains the shell treatment everywhere else. Inside
 * an app-making session, however, a page-wide glow crossed the conversation
 * rail and made the whole workspace feel selected. The session returns to
 * the neutral Studio ground and the same light is clipped inside the finished
 * app card itself. Nothing around the card receives the effect. */
body.session-view > .horizon { opacity: 0; }

#viewSession .done-card {
  isolation: isolate;
  overflow: hidden;
  border-radius: var(--radius-card);
}

#viewSession .done-card.bloom::before {
  inset: 0;
  height: auto;
  border-radius: inherit;
  z-index: 0;
  opacity: 1;
  filter: none;
  background:
    radial-gradient(78% 46% at 50% 102%, rgba(190, 164, 255, 0.5), transparent 76%),
    radial-gradient(104% 68% at 50% 106%, rgba(110, 98, 250, 0.34), transparent 80%),
    linear-gradient(to top, rgba(44, 38, 130, 0.34), rgba(44, 38, 130, 0.04) 72%, transparent 92%);
}

#viewSession .done-card > * { z-index: 1; }
#viewSession .done-card .shot-stage,
#viewSession .done-card .done-meta { background: rgba(15, 16, 18, 0.8); }

body.light.session-view > .horizon { opacity: 0; }
body.light #viewSession .done-card.bloom::before { opacity: 0.5; }
body.light #viewSession .done-card .shot-stage,
body.light #viewSession .done-card .done-meta { background: rgba(255, 255, 255, 0.84); }

/* The apps-count pill: one quiet rounded chip, not a mono box wrapping
   its own words. */
.apps-count {
  align-self: center; white-space: nowrap;
  font-size: 12px; color: var(--faint);
  border: 1px solid var(--line); border-radius: 99px; padding: 5px 12px;
  font-variant-numeric: tabular-nums;
}
/* Before the count is known the span is empty, and the border and padding
   still paint: a 26x12 empty pill sitting beside the button, which reads as
   a broken control rather than a chip waiting for a number. */
.apps-count:empty { display: none; }
/* The unfinished banner's delete: present, quiet, and not the row's click.
   Three children now, so the first takes the room and the rest sit right. */
.apps-finish { justify-content: flex-start; gap: 10px; }
.apps-finish > span:first-child { flex: 1; text-align: left; }
.apps-finish .apps-x {
  margin-left: auto; padding: 2px 8px; border-radius: 6px;
  color: var(--quiet, rgba(255,255,255,0.35)); font-size: 15px; line-height: 1;
}
.apps-finish .apps-x:hover { color: var(--text); background: var(--film); }

/* Support threads inside the sheet. */
#supTickets { display: flex; flex-direction: column; gap: 10px; margin-bottom: 14px; max-height: 300px; overflow-y: auto; }
.sup-tick { border: 1px solid var(--line); border-radius: 10px; padding: 10px 12px; }
.sup-tick .sup-head { display: flex; gap: 8px; align-items: center; font-size: 13px; color: var(--text); }
.sup-tick .sup-status { margin-left: auto; font-size: 11px; color: var(--faint); border: 1px solid var(--line); border-radius: 99px; padding: 2px 8px; }
.sup-msg { font-size: 12.5px; color: var(--muted); margin-top: 6px; padding: 7px 10px; border-radius: 8px; background: rgba(255,255,255,0.035); white-space: pre-wrap; }
.sup-msg.krate { background: rgba(124,108,255,0.10); }
.sup-msg .sup-who { display: block; font-size: 10.5px; color: var(--quiet, rgba(255,255,255,0.35)); margin-top: 3px; }
.sup-replyrow { display: flex; gap: 8px; margin-top: 8px; }
.sup-replyrow input { flex: 1; }
#supBody { margin-top: 8px; }
#supEmail { margin-top: 8px; }

/* The publish sheet's sign-in step: one clear button, the code as the
   quiet fallback, nothing else competing. */
.pub-signin { display: flex; flex-direction: column; gap: 6px; padding: 6px 0 2px; }
.pub-signin .sheet-actions { justify-content: flex-start; }
.pub-code { margin-top: 10px; text-align: center; }


/* =======================================================================
 * THE HOME, REDRAWN (2026-09-01)
 *
 * A workspace drawer on the left, a gradient room on the right, and the
 * box in the middle of it. Built to a reference Yashraj chose, with our
 * own background kept -- the aurora is ours and it is the one thing in
 * the picture that should not be borrowed.
 *
 * The drawer is CLOSED on arrival. Everything about this page argues that
 * the box is the only thing that matters on the way in; a panel standing
 * open beside it is a second thing to read before typing.
 * ===================================================================== */

.side {
  position: fixed; top: 0; left: 0; bottom: 0; z-index: 60;
  /* Width is a variable so the grip can move it and everything anchored
     to the drawer follows without a second source of truth. */
  width: var(--side-w, 240px); flex: none;
  display: flex; flex-direction: column;
  /* Glass, not a panel. The rail sits OVER the page rather than beside it,
     so it should carry what is behind it -- the page's own content sliding
     under the rail is what makes the edge read as a pane of glass instead
     of a black column parked on top.
     The tint still has to be dense enough to keep the rows legible over
     whatever scrolls under them, so this is heavier than the chrome token:
     a rail you can read the page through is a rail nobody can read. */
  background: color-mix(in srgb, var(--side-bg, #0F0F10) 72%, transparent);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border-right: 1px solid var(--glass-edge-soft);
  /* The lit rim, down the inner edge rather than the top: this pane is
     vertical, and the light in the window comes from the content side. */
  box-shadow: inset -1px 0 0 rgba(255, 255, 255, 0.03);
  /* No top padding: the mark sits in the window's corner, level with the
     title bar, the way every desktop app puts its identity. The rows keep
     their own side padding. */
  padding: 0 10px 12px;
  transform: translateX(-100%);
  transition: transform 0.34s cubic-bezier(0.22, 1, 0.36, 1);
}
.side[data-open="true"] { transform: none; }
/* The restored rail must not slide in on launch. It was already open when
   the window closed, so animating it into place shows the person a piece
   of furniture arriving that, as far as they are concerned, never left. */
body.side-restoring .side,
body.side-restoring .view,
body.side-restoring .shelf { transition: none !important; }
@media (prefers-reduced-motion: reduce) { .side { transition: none; } }

/* The drawer sits over the room rather than pushing it. Pushing would
   move the box mid-thought, and the box is where the person is looking. */
.side-scrim {
  position: fixed; inset: 0; z-index: 55;
  background: rgba(0,0,0,0.42);
  animation: scrimIn 0.24s ease;
}
@keyframes scrimIn { from { opacity: 0; } }

/* Same height as the title bar, so the mark sits on the window's top row
   rather than floating below it. */
/* The drawer's top strip and the title bar are the same component seen on
   two sides of the seam: same height, same vertical alignment, same bottom
   padding. They drifted apart when macOS grew its bar to sit under the
   traffic lights and this one kept centring, which put the drawer's mark
   13px above the toggle right beside it. */
.side-top {
  /* flex: none, or the drawer's flex column shrinks this strip to its
     content while the title bar beside it stays full height. */
  height: var(--titlebar); flex: none;
  display: flex; align-items: center;
  /* On macOS the traffic lights are drawn over this strip at x:20..74.
     The mark sits immediately after them on the SAME row, at the same
     height, so the drawer's top line reads as one row of controls. */
  padding: 0 2px; margin-bottom: 10px;
}
/* The drawer's mark is a 30px button (it has a hover state and a click
   target); the room's brand is a bare 21px row. Bottom-aligning two boxes
   of different heights puts their CONTENT on different lines -- 3.5px
   apart, which is exactly the daylight that showed between them. So the
   strip bottom-aligns and the button gives back its own extra height as
   negative margin, putting the glyph inside it on the room's line. */

/* The mark reads as the product's name, so it carries the wordmark beside
   the glyph at the same size the title bar uses. A lone 17px glyph in a
   26px box read as a decoration rather than as "this is Krate". */
.side-mark {
  height: 30px; display: flex; align-items: center; gap: 9px;
  padding: 0 6px; background: none; border: 0; cursor: pointer;
  border-radius: 8px; transition: background 0.15s;
}
.side-mark:hover { background: var(--film); }
.side-mark img { width: 22px; height: 22px; opacity: 1; flex: none; }
.side-mark span {
  font-size: 13px; font-weight: 600; letter-spacing: 0.08em;
  color: var(--text);
}

/* The workspace switcher and its purple tile are gone: there is one
   workspace, so a switcher for it was furniture, and a logo boxed in a
   coloured square read as a placeholder icon rather than a brand. The
   mark at the top is the identity, and it is enough. */

.side-nav { display: flex; flex-direction: column; gap: 1px; }
.side-row {
  display: flex; align-items: center; gap: 11px;
  width: 100%; height: 34px; padding: 0 8px;
  background: none; border: 0; border-radius: 8px;
  color: var(--muted); font-family: var(--sans); font-size: 13.5px;
  cursor: pointer; text-align: left;
  transition: background 0.15s, color 0.15s;
}
/* Icons sit back until their row matters. An outline at 0.72 reads as a
   label; the same glyph at full strength reads as a destination, and the
   difference between the two is what makes a list scannable. */
.side-row svg { flex: none; opacity: 0.72; transition: opacity 0.15s; }
.side-row:hover { background: var(--film); color: var(--text); }
.side-row:hover svg { opacity: 0.95; }

/* The selected row is a RAISED pane, not a lighter rectangle.
   A flat fill says "this one is a bit different"; a hairline plus a
   shadow says "you are here", because that is what a lifted surface
   means everywhere else in the window. The three together -- fill, edge,
   shadow -- are what the reference does and what a plain fill cannot. */
.side-row.on {
  background: var(--film-strong);
  color: var(--text);
  border: 1px solid rgba(255, 255, 255, 0.07);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.06),
    0 1px 3px rgba(0, 0, 0, 0.28);
  /* The border would otherwise add 2px and nudge every row below it. */
  padding: 0 7px;
}
.side-row.on svg { opacity: 1; }

/* The one piece of colour in the rail, and it is on the current page only.
   A thin blue bar down the left edge of the selected row -- the same blue
   the product already uses, in the one place a person is looking to answer
   "where am I". Colouring all six icons differently would be prettier for
   a second and useless after that: when everything is marked, nothing is.
   This is the accent doing a job. */
.side-row.on::before {
  content: "";
  position: absolute; left: 0; top: 50%;
  width: 2.5px; height: 15px; margin-top: -7.5px;
  border-radius: 0 2px 2px 0;
  background: var(--blue);
}
.side-row { position: relative; }

/* ---- the search field --------------------------------------------------
 * Glass, like every other input, but quieter: it sits inside the rail
 * rather than on the page, so it should read as part of the furniture
 * rather than as the thing being asked of you. */
.side-search {
  display: flex; align-items: center; gap: 8px;
  margin: 4px 2px 10px; padding: 0 9px;
  height: 34px;
  background: rgba(0, 0, 0, 0.22);
  border: 1px solid var(--glass-edge-soft);
  border-radius: 9px;
  color: var(--faint);
  transition: border-color 0.15s, background 0.15s;
}
.side-search:focus-within {
  background: rgba(0, 0, 0, 0.3);
  border-color: rgba(255, 255, 255, 0.14);
}
.side-search svg { flex: none; }
.side-search input {
  flex: 1; min-width: 0;
  background: none; border: 0; outline: none;
  color: var(--text); font: 400 13px var(--sans);
}
.side-search input::placeholder { color: var(--ghost); }
/* The shortcut, shown rather than remembered. */
.side-key {
  flex: none; display: grid; place-items: center;
  min-width: 17px; height: 17px; padding: 0 4px;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 4px;
  color: var(--faint); font: 400 10.5px var(--mono);
}
body.light .side-search { background: rgba(12, 16, 23, 0.04); }
body.light .side-search:focus-within { background: rgba(12, 16, 23, 0.06); }
body.light .side-key { background: rgba(12, 16, 23, 0.05); border-color: rgba(12, 16, 23, 0.1); }

/* Section headings, with a rule above them.
 *
 * The reference separates its blocks; Studio ran nav straight into Recent
 * with nothing but a gap, so eleven session rows read as a continuation of
 * the navigation rather than as a different kind of thing. A hairline and
 * a little more air is the whole fix.
 *
 * Uppercase and tracked, because a heading that looks like the rows under
 * it is not doing a heading's job. */
.side-label {
  margin: 16px 0 6px; padding: 12px 9px 0;
  border-top: 1px solid var(--glass-edge-soft);
  font-size: 10.5px; font-weight: 500;
  letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--quiet);
}

/* The bottom of the drawer: two offers and the person themself. */
.side-foot { margin-top: auto; padding-top: 14px; display: flex; flex-direction: column; gap: 8px; }
.side-card {
  display: flex; align-items: center; gap: 10px;
  width: 100%; padding: 11px 12px;
  background: var(--film);
  border: 1px solid rgba(255,255,255,0.07); border-radius: 11px;
  color: var(--text); font-family: var(--sans); cursor: pointer; text-align: left;
  transition: background 0.15s, border-color 0.15s;
}
.side-card:hover { background: var(--film-strong); border-color: rgba(255,255,255,0.13); }
.card-text { flex: 1; min-width: 0; }
.card-text b { display: block; font-size: 13px; font-weight: 500; }
.card-text small { display: block; font-size: 11.5px; color: var(--faint); margin-top: 1px; }
.card-icon { flex: none; color: var(--muted); display: grid; place-items: center; }
.card-icon.bolt { color: #8fb3ff; }
.side-card.upsell { background: rgba(98,145,255,0.10); border-color: rgba(98,145,255,0.22); }
.side-card.upsell:hover { background: rgba(98,145,255,0.16); border-color: rgba(98,145,255,0.34); }

.side-me { display: flex; align-items: center; gap: 8px; padding: 4px 2px 0; }
.side-avatar {
  width: 28px; height: 28px; border-radius: 50%; overflow: hidden;
  background: var(--panel-raised); border: 1px solid var(--line);
  color: var(--muted); font-size: 11.5px; font-weight: 500;
  display: grid; place-items: center; cursor: pointer;
  transition: border-color 0.15s;
}
.side-avatar:hover { border-color: var(--line-strong); }
.side-avatar img { width: 100%; height: 100%; object-fit: cover; }
.side-bell {
  position: relative; margin-left: auto;
  width: 28px; height: 28px; border-radius: 8px;
  display: grid; place-items: center;
  background: none; border: 0; color: var(--faint); cursor: pointer;
  transition: color 0.15s, background 0.15s;
}
.side-bell:hover { color: var(--text); background: var(--film); }
.bell-dot {
  position: absolute; top: 5px; right: 6px;
  width: 6px; height: 6px; border-radius: 50%; background: #ff6b6b;
}

/* The panel button, top-left, where the reference puts it. */
.titlebar .side-toggle { margin-right: 2px; }

/* ---- the title -----------------------------------------------------------
 * One question, left-aligned on the same edge as the box below it, with
 * the mark and the person's name small above. The eye starts where the
 * typing starts. A centred headline over a left-aligned box gives the
 * column two different left edges and neither looks deliberate. */
.home-title.display {
  display: block; text-align: left;
  /* The reference sets this at around 26px in a regular weight, not a
     display weight. At 600 and 40px it shouted; the question is a quiet
     line above the box, and the box is the subject of the screen. */
  font-size: clamp(22px, 2.1vw, 27px);
  font-weight: 400; letter-spacing: -0.012em; line-height: 1.25;
  /* --text, not #fff. This is the biggest line on the home screen and in
     light mode it was white on paper: the page's own headline, invisible. */
  color: var(--text);
  margin-bottom: 20px;
}

/* ---- the box -------------------------------------------------------------
 * Wider and taller than before, with the controls on one row underneath and
 * real air around the text. The reference's box is the biggest object on
 * the page after the title, and it should be: it is the only thing anyone
 * came here to use. */
.composer-shell { width: min(680px, 100%); }
.bigbar {
  /* The same glass as every other surface. This is the one control the
     whole home screen exists to point at, and it was the only chrome still
     wearing a hardcoded opaque panel -- so it read as a box sitting on the
     page while the rail and the dock read as panes over it. */
  background: var(--glass-chrome);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border: 1px solid var(--glass-edge);
  border-radius: 20px;
  padding: 18px 20px 0;
  box-shadow:
    inset 0 1px 0 var(--glass-lit),
    0 26px 60px -30px rgba(0, 0, 0, 0.85);
  transition: border-color 0.2s, box-shadow 0.3s, background 0.2s;
}
.bigbar:focus-within {
  border-color: rgba(255,255,255,0.17);
  box-shadow: 0 30px 70px -30px rgba(0,0,0,0.95);
}
.bigbar textarea {
  min-height: 46px; max-height: 168px;
  font-size: 15.5px; line-height: 1.55;
  padding: 0 2px 6px;
}
.bigbar-row {
  display: flex; align-items: center; gap: 8px;
  height: 52px; margin: 0 -20px; padding: 0 14px;
  border-top: 0;
}
.bigbar-row .grow { flex: 1; }

/* The round + on the left. */
.bigbar-row .tool.plus {
  width: 30px; height: 30px; padding: 0;
  border-radius: 50%;
  display: grid; place-items: center;
  color: var(--muted); background: none; border: 0;
  transition: background 0.15s, color 0.15s;
}
.bigbar-row .tool.plus:hover { background: var(--film-strong); color: var(--text); }

/* "Plan": a word, not a button. It is an alternative to sending, and
   dressing it as a control would make the send arrow look like one of two
   equal choices when it is the main one. */
.bar-btn {
  height: 30px; padding: 0 10px; border-radius: 8px;
  background: none; border: 0; color: var(--muted);
  font-family: var(--sans); font-size: 13.5px; cursor: pointer;
  transition: color 0.15s, background 0.15s;
}
.bar-btn:hover { color: var(--text); background: var(--film); }

.bigbar-row .tool.wave {
  width: 30px; height: 30px; padding: 0; border-radius: 8px;
  display: grid; place-items: center;
  color: var(--muted); background: none; border: 0;
  transition: background 0.15s, color 0.15s;
}
.bigbar-row .tool.wave:hover { background: var(--film-strong); color: var(--text); }

/* The send: a filled circle, the one solid thing in the box. */
/* Quiet until there is something to send, then bright.
 *
 * Every value here was a hardcoded white, which on paper meant a white
 * circle with a white arrow inside it -- the button you press to do the
 * one thing this screen is for, invisible. The tokens carry ink in light
 * mode and the same whites in dark. */
.bigbar-row .send {
  width: 32px; height: 32px; flex: none;
  border-radius: 50%; border: 0;
  display: grid; place-items: center;
  background: var(--send-idle-bg, rgba(255,255,255,0.16));
  color: var(--send-idle-fg, rgba(255,255,255,0.62));
  transition: background 0.2s, color 0.2s, transform 0.12s;
}
.bigbar-row .send:hover {
  background: var(--send-hover-bg, rgba(255,255,255,0.24));
  color: var(--send-hover-fg, #fff);
}
/* Awake the moment there is something to send. */
.bigbar-row .send.ready {
  background: var(--send-ready-bg, #F2F2F2);
  color: var(--send-ready-fg, #111);
}
.bigbar-row .send.ready:hover { background: var(--send-ready-hover, #fff); }
.bigbar-row .send:active { transform: scale(0.93); }

/* ---- the shelf -----------------------------------------------------------
 * A raised panel at the foot of the room, the way the reference does it:
 * pinned to the bottom, rounded only at the top, and wider than the box so
 * it reads as the floor the box is standing on rather than another card. */
.shelf {
  /* Fixed, not absolute. `.home` is the scroll container, so an absolute
     shelf anchors to the scrolled CONTENT and slides away with it -- it
     ended up 64px wide and hanging below the fold. The shelf belongs to
     the window's floor, which is what fixed means. */
  /* Centred by margins, NOT by translateX. The reveal animation sets
     `transform` inline to slide things up, and a centring transform loses
     that fight -- the shelf ended up 26px wide in the corner. Margins are
     not something an animation can take away. */
  position: fixed; left: 0; right: 0; bottom: 0;
  width: min(900px, calc(100vw - 56px));
  margin: 0 auto;
  /* Through the token, not #131315: hardcoded, this stayed a black slab
     across the bottom of a paper-white page. */
  /* Glass, so the page scrolling underneath shows through the floor of
     the window rather than disappearing behind an opaque slab. */
  background: color-mix(in srgb, var(--shelf-bg, #131315) 74%, transparent);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border: 1px solid var(--glass-edge-soft);
  border-bottom: 0;
  border-radius: 16px 16px 0 0;
  box-shadow: inset 0 1px 0 var(--glass-lit);
  padding: 12px 16px 20px;
  z-index: 3;
}
.shelf-head { display: flex; align-items: center; gap: 8px; }

/* ---- the shelf opens and shuts ----------------------------------------
 * Shut by default. Open it is 232px, and on a 672px-tall work area that is
 * a third of the window given to something the person did not ask for --
 * the third suggestion ran underneath it. The head stays as a handle, so
 * the apps are one click away, and the choice is remembered.
 *
 * Height, not display: a transition needs something to animate between,
 * and the body's height is known (a card plus its title). */
.shelf-grip {
  display: grid; place-items: center;
  width: 26px; height: 26px; flex: none;
  margin-left: -4px;
  background: none; border: 0; border-radius: 7px;
  color: var(--muted); cursor: pointer;
  transition: color 0.15s, background 0.15s;
}
.shelf-grip:hover { color: var(--text); background: var(--film); }
.shelf-grip .chev { transition: transform 0.22s cubic-bezier(0.2, 0.8, 0.2, 1); }
/* Open: the chevron points down, towards the shelf it will fold away. */
.shelf-grip .chev { transform: rotate(0deg); }
.shelf.shut .shelf-grip .chev { transform: rotate(-90deg); }

.shelf .shelf-body {
  /* 115px of card plus the 12px top margin the body already carries --
     but never more than the window can show.
     The shelf is fixed to the floor and its body grows upward, so in a
     short window the cards climbed out of the top: measured at an 800px
     window, a card's box started 22px ABOVE the window and ended 97px
     below its bottom, so "My apps" opened into space that does not exist
     and read as a dead control (K-841). 38vh keeps the tray to about a
     third of the window on any size, and the strip inside it already
     scrolls horizontally. */
  max-height: min(140px, 38vh);
  transition: max-height 0.24s cubic-bezier(0.2, 0.8, 0.2, 1),
              opacity 0.18s ease, margin-top 0.24s ease;
}
.shelf.shut .shelf-body {
  max-height: 0; opacity: 0; margin-top: 0;
  overflow: hidden; pointer-events: none;
}
/* (The room's matching padding lives beside the other .home rules further
   down, so it lands after `.home { padding-bottom: 232px }` and actually
   wins -- same specificity means source order decides.) */
.shelf-tabs {
  display: flex; gap: 3px; padding: 3px;
  background: var(--film); border-radius: 10px;
}
.shelf-tab {
  height: 28px; padding: 0 13px; border-radius: 8px;
  background: none; border: 0; color: var(--muted);
  font-family: var(--sans); font-size: 13px; cursor: pointer;
  transition: background 0.15s, color 0.15s;
}
.shelf-tab:hover { color: var(--text); }
.shelf-tab.on { background: var(--film-strong); color: var(--text); }
.shelf-all {
  margin-left: auto;
  display: inline-flex; align-items: center; gap: 7px;
  height: 28px; padding: 0 6px; border-radius: 8px;
  background: none; border: 0; color: var(--muted);
  font-family: var(--sans); font-size: 13px; cursor: pointer;
  transition: color 0.15s, gap 0.15s;
}
.shelf-all:hover { color: var(--text); gap: 9px; }

/* The apps themselves: small stills in a row, the way a shelf of things
   you have made should look. */
.shelf-body {
  display: flex; gap: 10px; margin-top: 12px;
  overflow-x: auto; scrollbar-width: none;
  padding-bottom: 2px;
}
.shelf-body::-webkit-scrollbar { display: none; }
.shelf-card {
  flex: none; width: 148px;
  background: none; border: 0; padding: 0; cursor: pointer; text-align: left;
}
.shelf-card .shot {
  width: 100%; aspect-ratio: 16 / 10; border-radius: 9px;
  background: #1A1A1C; border: 1px solid rgba(255,255,255,0.08);
  overflow: hidden; display: grid; place-items: center;
  transition: border-color 0.15s, transform 0.18s cubic-bezier(0.22,1,0.36,1);
}
/* No lift on hover. translateY moved the tile up into the row above it,
   and the shelf clips its own overflow, so the raised top edge was cut
   off against the tray's border -- the "card gets disturbed" everyone
   sees. The border brightening is the whole affordance it needed. */
.shelf-card:hover .shot { border-color: rgba(255,255,255,0.28); }
.shelf-card .shot img { width: 100%; height: 100%; object-fit: cover; object-position: top center; }
.shelf-card .shot span { color: var(--ghost); font-size: 11px; }
.shelf-card b {
  display: block; margin-top: 7px;
  font-size: 12.5px; font-weight: 400; color: var(--muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  transition: color 0.15s;
}
.shelf-card:hover b { color: var(--text); }
.shelf-empty {
  padding: 14px 2px 6px; font-size: 13px; color: var(--faint);
}

/* An empty shelf is a slab with a tab bar on it and nothing underneath: on
   first run that is the largest object on the screen, and it says the
   product is empty rather than that it is ready. So with nothing in it the
   shelf collapses to its head, and the room stops reserving a shelf's worth
   of floor for it. */
.shelf[data-empty="true"] { padding-bottom: 12px; }
.shelf[data-empty="true"] .shelf-body { display: none; }

/* The room has to leave space for the shelf, and the old dock goes: the
   shelf does its job better and two navigations at one edge is one too
   many. The reserve matches what is actually down there: a head plus a row
   of cards, or just a head when the shelf is empty. */
.home { position: relative; padding-bottom: 232px; }
.home[data-shelf="empty"] { padding-bottom: 108px; }
/* Shut, the shelf is only its head, so the room gets the rest back. Same
   reserve as the empty case, because what is down there is the same thing:
   a head and nothing else. */
.home[data-shelf-open="0"] { padding-bottom: 108px; }
.dockwrap { display: none !important; }

@media (max-width: 700px) {
  .shelf { width: 100%; border-radius: 14px 14px 0 0; padding: 10px 12px 16px; }
  .home { padding-bottom: 208px; }
  .home[data-shelf="empty"] { padding-bottom: 96px; }
  .home[data-shelf-open="0"] { padding-bottom: 96px; }
}

/* When the drawer is open, the room gets the reference's rounded top-left
   corner -- the two panels read as one window with a seam rather than a
   sheet sliding over a page. The shelf steps aside with it so it is not
   half-hidden behind the drawer. */
body.side-open .home,
body.side-open .titlebar {
  border-top-left-radius: 14px;
}
body.side-open .home { overflow: hidden; }

/* The room steps aside by the drawer's ACTUAL width, not a hardcoded 240.
   With a fixed number the room stayed put while the drawer was dragged,
   so a wider drawer simply covered more of the page: the content read as
   a static backdrop rather than a panel sharing the window.
   --side-w is the one source of truth, so dragging moves both. */
body.side-open .view {
  margin-left: var(--side-w, 240px);
  transition: margin-left 0.34s cubic-bezier(0.22, 1, 0.36, 1);
}
.view { transition: margin-left 0.34s cubic-bezier(0.22, 1, 0.36, 1); }
/* The shelf is fixed and centred by margins, so shifting its left edge
   alone would push it off-centre by the drawer's width. Giving it the
   drawer's width as a left inset lets the auto margins re-centre it in
   what is left of the window. */
body.side-open .shelf {
  left: var(--side-w, 240px);
  width: min(900px, calc(100vw - var(--side-w, 240px) - 56px));
}
/* While the grip is held the drawer follows the pointer every frame; a
   0.34s ease on the room would lag a whole third of a second behind it. */
body.side-resizing .view,
body.side-resizing .shelf { transition: none; }
@media (prefers-reduced-motion: reduce) {
  .view, body.side-open .view { transition: none; }
}
/* Below this the drawer covers the room instead of sharing it: a 240px
   drawer out of a narrow window leaves nothing to type into. */
@media (max-width: 860px) {
  body.side-open .view { margin-left: 0; }
  body.side-open .shelf { left: 0; }
}

/* ---- the drawer on a phone -------------------------------------------
 *
 * Measured at 375px: the drawer is 320 wide, so with it open the app had
 * 55px of screen and the composer was unreachable. On a desktop the drawer
 * shares the window; on a phone there is no window to share, so it becomes
 * an overlay with a scrim, the way every mobile app does it -- and tapping
 * the scrim closes it, which is the gesture people already know.
 */
@media (max-width: 860px) {
  .side {
    position: fixed; top: 0; bottom: 0; left: 0; z-index: 60;
    width: min(300px, 86vw);
    box-shadow: 0 0 60px rgba(0, 0, 0, 0.6);
  }
  /* The room keeps its full width underneath, so closing the drawer needs
     no reflow and the app never sits in a 55px strip. */
  body.side-open .view { margin-left: 0; }
  body.side-open .rail, body.side-open .home { margin-left: 0; }
}

/* ---- touch targets ----------------------------------------------------
 *
 * Studio was drawn for a pointer: its icon buttons are 30px and its
 * sidebar rows 34. A thumb needs 44. Applied only on touch-sized screens
 * so the desktop's density is untouched.
 */
/* Touch, not width: a tablet is operated by the same thumb as a phone, so
   the targets are decided by the input device rather than by how wide the
   screen happens to be.
   This used to be two near-identical blocks, one keyed on `pointer:
   coarse` and one on width, and BOTH listed the controls by name -- which
   is why the drawer's foot was missed for so long: it uses .side-row,
   .side-avatar and .side-bell, and neither list mentioned them. Measured
   with the drawer actually open at 390px: Settings 279x34, account 28x28,
   notifications 28x28, all of them things a thumb is meant to hit.
   One block now, and it sweeps every control in the drawer rather than
   naming them, so a new one is covered the day it is added. */
@media (pointer: coarse) {
  .icon-btn, .tool, .send, .set-mini { min-width: 44px; min-height: 44px; }
  #sideSearch { min-height: 44px; }

  /* Everything clickable inside the drawer, by structure not by name. */
  .side button,
  .side a[href],
  .side [role="button"] { min-height: 44px; }
  /* The square ones need the other axis too, or they stay 28 wide. */
  .side-avatar, .side-bell, .side .icon-btn { min-width: 44px; }
  /* A square control that grew to 44 keeps its shape rather than becoming
     a rounded rectangle with its glyph adrift in the middle. */
  .side-avatar, .side-bell { display: grid; place-items: center; }

  /* The shelf's own controls: the tabs that switch between your apps and
     the examples, the link to all of them, and the grip that opens it.
     Measured on an emulated phone at 375 wide, these came out 28 and 26
     pixels tall while everything around them had already been raised --
     they sit on the first screen a person sees, and they are how anybody
     reaches an app they made earlier.

     Height only for the tabs: they are wide enough already, and a minimum
     width would stretch "My apps" and "Examples" out of their row. The
     grip is square, so it needs both.

     `height`, not `min-height`: the base rules set a fixed height on all
     three, and a minimum cannot beat a fixed value -- the first attempt at
     this changed nothing at all and measured exactly 28px afterwards. */
  .shelf-tab, .shelf-all { height: 44px; }
  .shelf-grip { width: 44px; height: 44px; display: grid; place-items: center; }

  .side-foot .side-card { padding: 12px 14px; }
  /* The Build/Plan toggle is sized in bridge.js, not here: that CSS is
     injected into the head at runtime and wins on source order, so a rule
     for it in this file would be silently overridden. */

  /* The gallery's search field. The input itself is 21px, but a tap
     anywhere on its wrapper focuses it, so the wrapper is the real
     target -- and at 39px it was still five short. */
  .cloud-search { min-height: 44px; }
}

/* ---- a floor under the type, on a phone -------------------------------
 *
 * Studio is dense on purpose: it is a tool, and a desktop user sits close
 * to a large screen. A phone is held at arm's length, and the same setting
 * that reads as precise on a monitor reads as a smudge there.
 *
 * Measured across the app at 390px: labels at 10.5, a "soon" tag at 9.5,
 * the drawer's section headings at 10.5. Raised to 11.5, which is the
 * smallest size that still reads, and applied only on phones so the
 * desktop's density is untouched. Sized in one place rather than per
 * class, so a label added later is covered.
 */
@media (max-width: 860px) {
  .build-shot-tag, .side-key, .side-label, .soon-tag, .term-what,
  .l, .label, .tag, .aichip, .pub-opt, .info-note,
  .msg .who {
    font-size: 11.5px;
  }
}

/* ---- the home screen on a phone --------------------------------------- */
@media (max-width: 860px) {
  .home { padding-left: 14px; padding-right: 14px; }
  .home-title { font-size: clamp(22px, 7vw, 30px); }
  .home-hail { width: 100%; }
  .composer-shell, .bigbar { width: 100%; }
  /* One line, not wrapped: wrapping pushed the second row out of the bar
     and onto the suggestions below it. What gives instead is the "Built
     by" chip -- on the web there is only one agent, so the label is the
     least useful thing in the row. */
  .bigbar-row { flex-wrap: nowrap; gap: 6px; }
  .bigbar-row .aichip { display: none; }
  .bigbar-row .web-mode { margin-right: 0; }
  .bigbar { padding-bottom: 10px; }
  .sugg-list button { font-size: 13px; padding: 11px 12px; }
  /* The ambient mark is sized off the smaller axis, which on a tall phone
     is the width -- so it filled the screen. Held back. */
  .home-hero::before { width: min(60vh, 86vw); opacity: 0.20; }

  /* The hero sat too low and too far down. `--dock-clear` is 108px, and
     matching the top padding to it is right on a desktop, where the window
     is tall enough that 108 at each end is a small share of it. On a 915px
     phone it is not: measured before this rule the greeting had 255px of
     nothing above it and 186 below, so the block read as pushed into the
     lower half of the screen.
     The shelf overlays the bottom, so the bottom padding still has to clear
     it; the top does not, and 24px there both balances the gaps and gives
     the hero back a fifth of the screen. */
  .home { padding-top: 24px; }
}

/* ---- the ten other sheets, on a phone ---------------------------------
 *
 * Report, Send, Publish, Details, Account, the limit and make-it prompts:
 * every sheet Studio can raise over a screen, none of which any earlier
 * sweep had opened. Measured at 390x700 they share three faults, so they
 * are fixed once here rather than ten times:
 *
 *   - Buttons are 41px and inputs 38-39. Four short of a thumb, across
 *     every sheet, because the base sizes were set for a pointer.
 *   - The report sheet's second button ran to x=439 on a 390px screen:
 *     its footer is a row that never wraps.
 *   - The publish sheet keeps a desktop two-column layout, squeezing the
 *     preview slot to 125px -- the same fault as the gallery detail page.
 */
@media (max-width: 860px) {
  .sheet { padding: 20px 18px; width: min(460px, 94%); }
  /* Every control in a sheet, by structure rather than by name: these are
     ten different sheets and naming their buttons is how the drawer's
     foot went unfixed for so long. */
  .sheet .btn,
  .sheet button:not(.set-mini):not(.share-copy),
  .sheet input:not([type="checkbox"]):not([type="radio"]),
  .sheet select,
  .sheet textarea { min-height: 44px; }
  /* A checkbox stays its own size -- blowing it up to 44 would make a
     tick the size of a button -- but its label becomes the target. */
  .sheet input[type="checkbox"] { width: 18px; height: 18px; }
  .sheet label:has(input[type="checkbox"]) {
    display: flex; align-items: center; gap: 10px; min-height: 44px;
  }

  /* Action rows wrap instead of running off the edge. `.detail-actions`
     is a nowrap flex row, which is what put the report sheet's second
     button at x=439 on a 390px screen. */
  .sheet .detail-actions,
  .sheet .sheet-actions,
  .sheet .row,
  .sheet .actions { flex-wrap: wrap; gap: 8px; }

  /* The publish sheet's side-by-side slots stack. The container is
     .pub-media: the screenshot slot beside the icon slot, which at this
     width left each 125px. */
  .pub-media { flex-direction: column; }
  .pub-slot, .pub-slot-icon { flex: 1 1 auto; width: 100%; min-width: 0; }
  .pub-preview-icon { width: 96px; }
}

/* ---- the settings and AI sheets, on a phone ---------------------------
 *
 * Both are a 216px nav column beside a content pane. At 375px that leaves
 * the pane 63px wide, which is what put the API key fields and the
 * spending panel into a single column of broken words -- every label
 * wrapping one letter at a time.
 *
 * Stacked instead: the sections become a horizontal strip of chips along
 * the top, the way a phone settings screen works, and the content gets the
 * whole width beneath them. The strip scrolls sideways if there are more
 * sections than fit, which is the one place sideways scrolling is right.
 */
@media (max-width: 860px) {
  .set-modal {
    grid-template-columns: 1fr;
    grid-template-rows: auto minmax(0, 1fr);
    width: 100%; height: 100%;
    max-height: 100%;
    border-radius: 0; border: 0;
  }
  .set-side {
    flex-direction: column;
    padding: 14px 14px 10px;
    border-right: 0; border-bottom: 1px solid var(--glass-edge-soft);
    overflow: visible;
    /* A grid item's `min-width: auto` is its content's minimum, and the
       chip row below refuses to shrink -- so this column grew to 521px
       inside a 412px sheet and carried "Appearance" off the screen. The
       scrolling the chip row asks for cannot start until its parent
       stops growing to fit it. */
    min-width: 0;
  }
  .set-side-head { margin-bottom: 10px; }
  /* A settings row is a label beside its control, which needs roughly 500px
     to read well. At 326 the two halves each take about 150: the
     explanation wrapped to four lines and the value beside it ("Krate AI")
     broke across two. Stacked, the sentence gets the full width and the
     control sits under it, left-aligned with everything else. */
  .set-row { flex-direction: column; align-items: stretch; gap: 10px; }
  .set-row .ctl { margin-left: 0; flex-wrap: wrap; }
  .set-row .set-val { white-space: nowrap; }
  /* The section list becomes a row of chips. */
  .set-nav {
    min-width: 0;
    flex-direction: row; gap: 8px;
    overflow-x: auto; overflow-y: hidden;
    scrollbar-width: none; -webkit-overflow-scrolling: touch;
    padding-bottom: 2px;
  }
  .set-nav::-webkit-scrollbar { display: none; }
  .set-nav button {
    flex: none; width: auto; white-space: nowrap;
    padding: 10px 14px; border-radius: 999px;
    background: var(--film); min-height: 40px;
  }
  .set-nav button.on { background: var(--text); color: var(--bg); }
  .set-main { padding: 16px 14px 24px; overflow-y: auto; }
  /* The key rows are a label beside a field beside a button; at this width
     the label goes above and the field takes the line. */
  /* Pinned to the sheet's corner. It lives inside the scrolling content,
     so on a phone it drifted down the page as you scrolled. */
  .set-modal .set-close {
    position: fixed; top: 14px; right: 14px; z-index: 6;
    width: 44px; height: 44px; display: grid; place-items: center;
  }
  /* The spending cards: two up rather than four across. */
  .web-spend-cards { display: grid; grid-template-columns: 1fr 1fr; }
  .web-spend-card { flex: none; }
  .web-spend-row { flex-wrap: wrap; row-gap: 2px; }
  .web-spend-row .app { flex: 1 0 100%; }
}

/* ---- a session, on a phone --------------------------------------------
 *
 * The session view is a fixed-width transcript rail beside a stage. At
 * 375px the rail took 320 and the stage got 72, so the app card had zero
 * width and simply was not there. Stacked instead, app first: the thing
 * you made is what you came back to see, and the conversation reads fine
 * underneath it.
 */
@media (max-width: 860px) {
  .rail {
    width: 100%; min-width: 0; max-height: none;
    border-right: 0; border-top: 0;
  }
  /* ---- the timeline rail ----------------------------------------------
   *
   * The rail is one vertical line with a dot per message. Three numbers
   * have to agree: the thread's left padding (where a message starts),
   * the line's own `left`, and the dot's negative offset from the message.
   * The desktop set is 30 / 21 / -13, which puts the dot's centre at 21.5
   * -- dead on the line.
   *
   * This block used to change the padding to 18 and leave the other two
   * alone, so the dot's centre landed at 9 against a line at 21.5:
   * 12.5px adrift, every dot hanging off the left of its own rail, which
   * is exactly what the phone screenshot showed.
   *
   * Written as one relationship instead of three loose numbers: the
   * gutter is the padding, the line sits at its middle, and the dot is
   * pulled back by its own half-width plus the distance to that middle.
   */
  .thread {
    --rail-gutter: 26px;   /* where a message's text starts */
    --rail-line: 13px;     /* the line's centre, half the gutter */
    --rail-dot: 7px;
    padding: 16px 14px 16px var(--rail-gutter);
    overflow: visible;
  }
  /* The line is drawn on the thread, so it runs the container's full
     height and carried on past the last dot into empty space. Drawn as a
     gradient that fades out over its last stretch instead, so it reads as
     a timeline that ends rather than a border that was cut off. */
  .thread::before {
    left: calc(var(--rail-line) - 0.5px);
    top: 20px; bottom: 8px;
    background: linear-gradient(
      to bottom, var(--line) 0, var(--line) calc(100% - 56px), transparent 100%
    );
  }
  .thread .msg { padding-left: 0; }
  .thread .msg::before {
    width: var(--rail-dot); height: var(--rail-dot);
    /* Back out to the line's centre, then back by the dot's own radius. */
    left: calc(-1 * (var(--rail-gutter) - var(--rail-line)) - var(--rail-dot) / 2);
    /* The dot aligns with the cap-height of the name beside it, not with
       the top of its line box. */
    top: 4px;
  }

  /* ---- the header -----------------------------------------------------
   *
   * It was transparent and 60px tall at the top of the document, so in
   * Safari -- where the page draws behind the status bar -- the app's
   * title rendered on top of the clock and the battery. Opaque, inset by
   * the notch, and stuck to the top so the thread scrolls under it
   * rather than through it. (viewport-fit=cover in the page's head is
   * what makes the inset resolve to anything but zero.) */
  #viewSession .titlebar {
    position: sticky; top: 0; z-index: 30;
    background: var(--bg);
    border-bottom: 1px solid var(--line);
    padding-top: env(safe-area-inset-top);
    min-height: calc(52px + env(safe-area-inset-top));
    height: auto;
  }

  /* ---- the preview and the conversation, both --------------------------
   *
   * The app you just made and the conversation about it are the two
   * halves of this screen, and one must not squeeze the other to nothing.
   * Measured before this rule at 390x700: the stage got 182px and the
   * thread 328, with the composer's block taking 129 -- so the app was a
   * strip and the chat was half a screen.
   *
   * The stage now takes a share of the viewport rather than whatever is
   * left over, and the thread keeps the rest and scrolls on its own.
   * `dvh` follows Safari's collapsing toolbars; `vh` does not, and the
   * difference is the 44px that pushed the composer off the bottom.
   */
  .session-body {
    flex-direction: column;
    /* The header's real height, whatever the notch adds to it. */
    height: calc(100dvh - 52px - env(safe-area-inset-top));
    min-height: 0;
    overflow: hidden;
  }
  .stage {
    order: 1;
    flex: 0 0 auto;
    height: 34dvh; min-height: 180px; max-height: 320px;
    padding: 12px 14px; overflow-y: auto;
    border-bottom: 1px solid var(--line);
  }
  /* A card with something to say gets the room to say it. The idle state
     is a logo and a line, and 34dvh suits it; the other three carry either
     the four things you can do with a finished app, or the title, progress
     line and elapsed time that are the whole point of watching a build.
     Measured at 390x700 against a 238px stage: done 251, planning 430,
     building 495 -- so all three scrolled and cut their last lines off.
     The conversation gives up the difference, because it scrolls and these
     do not. */
  .stage:has(#stateDone:not(.hidden)),
  .stage:has(#statePlanning:not(.hidden)),
  .stage:has(#stateBuilding:not(.hidden)) {
    height: auto; max-height: 52dvh;
  }
  .rail {
    order: 2;
    flex: 1 1 auto; min-height: 0;
    display: flex; flex-direction: column;
    overflow: hidden;
  }
  .rail .thread { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
  .stage-state { width: 100%; }
  .done-card { width: 100%; max-width: 100%; }
  /* The composer is pinned to the rail's foot on a desktop; on a phone it
     belongs at the bottom of the screen, where a thumb is. */
  .rail .composer-shell { position: sticky; bottom: 0; background: var(--bg); }

  /* ---- the composer, on a phone ---------------------------------------
   *
   * Measured on the founder's screenshot: a 66px-tall box with an 18px
   * radius holding a 28px line of text, and a 44x44 send button still
   * carrying its desktop 8px corner -- a square sitting inside a lozenge,
   * which is what read as "weird shaped".
   *
   * The button is 44 because a thumb needs 44; the shape is the thing to
   * fix, not the size. A round button inside a pill-shaped field is one
   * idea instead of two, and the field's own radius is now tied to its
   * height so the two curves agree at any size.
   */
  /* Aligned with the phone thread's own gutter (26px), for the same
     reason as the desktop one above (K-820). The thread sets that number
     as --rail-gutter; repeated here because a custom property set on
     .thread is not visible to a sibling. */
  .composer { padding: 10px 14px calc(10px + env(safe-area-inset-bottom)) 26px; }
  .composer-box {
    align-items: center;
    gap: 8px;
    padding: 7px 7px 7px 14px;
    border-radius: 26px;
  }
  .composer-box .send { border-radius: 50%; }
  /* The paperclip was 30 wide against a 44-tall box: a tall thin target
     beside a round one. Square and round, matching the send button. */
  .composer-box .clip { border-radius: 50%; flex: none; min-width: 44px; }
  #prompt { align-self: center; }

  /* The hint was right-aligned at 11px, so when it wrapped it left a
     single word ("edits") alone against the right edge. Centred, and
     allowed only one line: it is a keyboard tip, and a phone has no
     keyboard to tip about. */
  .composer-hint {
    text-align: center; margin-top: 7px;
    font-size: 11px; line-height: 1.45;
  }
}

/* ---- a built app, on a phone ------------------------------------------
 *
 * The finished card is desktop-shaped: a large picture of the app, its
 * name at 17px, every capability it declares as its own chip, and four
 * buttons. Measured at 390x700 it wanted 407px inside a 363px stage, so
 * it scrolled and cut "Ship it" and "Details" in half -- which is what
 * the founder's screenshot shows. Each piece is reasonable; together they
 * are a desktop's worth of card on a phone's worth of screen.
 *
 * So the card is rebuilt for the shape it is actually in, rather than
 * shrunk until it fits:
 *
 *   - the capability chips become one quiet line, not four rows of
 *     pills. Measured: "asks save your data on this computer" is 253px
 *     wide on its own, so two never pair at this width and four chips
 *     cost 129px -- a third of the card -- to say what one sentence says.
 *     The full list is a tap away under Details, where it belongs.
 *   - the picture keeps a share of the card rather than a fixed height,
 *     and when there is no preview it stops reserving 220px to say so.
 *   - the type comes down a step: 17px on a 354px card is a headline for
 *     a filename.
 */
@media (max-width: 860px) {
  .done-actions { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 14px; }
  .done-actions .btn { width: 100%; min-height: 44px; }
  .done-card { border-radius: 16px; }
  .shot-stage { border-radius: 12px; padding: 14px 14px 0; }
  /* "no preview for this one" does not need a picture's worth of room to
     say so -- it was holding a 220px box above a card that had none to
     spare. One line, and the card gets the space back. */
  .shot-stage.no-shot { min-height: 54px; padding: 0; }
  .shot-stage.no-shot::after { font-size: 11.5px; }
  .done-meta { padding: 14px 16px 16px; }
  .done-name { font-size: 15px; }
  .done-sub { font-size: 12px; }

  /* One line instead of four rows. The chips keep their own words -- this
     is the app telling you what it will touch -- but they read as a
     sentence rather than as a stack of buttons. */
  .asks {
    margin-top: 10px;
    display: block;
    font-size: 11.5px; line-height: 1.5; color: var(--muted);
  }
  /* The <li>s are emitted with no whitespace between them, so going
     inline ran the words together -- "open a windowuse the microphone".
     The separator therefore has to carry its own spaces, and `white-space:
     pre` is what stops them being collapsed away again. */
  .asks li {
    display: inline; border: 0; padding: 0; border-radius: 0;
  }
  .asks li::before { content: none; }
  .asks li + li::before {
    /* Non-breaking spaces on BOTH sides. A generated string's ordinary
       spaces are collapsed at the edges of the box, which is why " · "
       rendered as " ·" and ran into the next words. */
    content: "\00a0\00b7\00a0"; color: var(--ghost);
  }
  /* Said once, at the front, instead of on every chip. */
  .asks::before { content: "asks\00a0"; color: var(--ghost); }
  /* The first capability is highlighted in the chip layout, where it reads
     as the headline one. In a sentence it is just the first thing before a
     comma, and colouring it differently looks like a mistake. */
  .asks li:first-child { color: inherit; }

  /* ---- while it is being made ---------------------------------------
   *
   * The same problem as the finished card, in the state a person spends
   * most of their time looking at. Measured at 390x700: the planning card
   * wants 430px and the building card 495 in a 238px stage, so the title,
   * the progress line and the elapsed time -- the four lines that say what
   * is happening -- all fell below the fold. You watch a shimmering
   * rectangle and are told nothing.
   *
   * The shimmering rectangle is 244px of that card and the words are 89.
   * So the rectangle gives up the room: it is a placeholder for an app
   * that does not exist yet, and its job is to suggest shape, which it
   * does just as well at 120px.
   */
  /* `.brail .build-shot` sets height 300 and min-height 140 at 0-2-0, so
     a bare `.build-shot` here lost -- and because min-height beats
     max-height, the box stayed at 140 however low the max was set. Matched
     at the same specificity, and the min is lowered rather than the max
     raised. */
  .brail .build-shot, .build-shot {
    /* flex-basis and no shrinking: the card is a column flexbox, so a
       `0 1 auto` child gets compressed when the card is tall -- this went
       to 68px, well under the 124 asked for, and read as a squashed
       letterbox rather than a window taking shape.
       Sized off the viewport so a short phone spends less on it: at
       667px tall a fixed 124 pushed the Stop button off the bottom, and
       Stop is the one control that must never be out of reach while
       something is running. */
    flex: 0 0 auto;
    min-height: 0; height: clamp(84px, 18dvh, 124px); margin: 8px 0 10px;
  }
  .build-shot img { max-height: 100%; }
  /* The card's own bottom margin was the last 4px of scroll in a stage
     that otherwise fit exactly. */
  .stage .build-card { margin-bottom: 0; }
  /* Measured at 375x667: of a 286px card, 48 was its own padding (26 top,
     22 bottom) and 44 more was margin between five rows -- 92px of air
     around 194px of content, which is a desktop card's generosity on a
     screen that has none to give. The rows keep their order and their
     words; they just stop being spaced like a poster. */
  /* `.build-card.brail` (0-2-0) sets both the padding and a
     `max-height: calc(100% - 56px)`, so a bare `.build-card` lost on both
     -- the card stayed capped at 296px while its rows needed 330, and the
     last one, the footer holding Stop, fell out of the bottom. The
     comment on that rule records the same button vanishing once before on
     a short desktop window; a phone is that window, always.
     Here the stage already bounds the card and scrolls if it must, so the
     cap has nothing left to protect and is removed rather than retuned. */
  .build-card, .build-card.brail {
    gap: 6px; padding: 14px 16px 16px;
    max-height: none;
  }
  /* The gap now does the spacing, so the rows' own margins would double
     it. Listed after the shot's rule above, so it clears that one too --
     which is intended: the card's gap covers it. */
  .build-card > * { margin-top: 0; margin-bottom: 0; }
  .build-title { font-size: 13.5px; }
  .bpeek, .build-elapsed, .bfoot { font-size: 11.5px; }
  /* Stop sits a little apart from the status lines above it: it is an
     action among readouts, and must not be tapped by accident. The gap is
     the card's, not a margin: a margin here escaped the card's padding and
     put the button 33px BELOW the card's own bottom edge. */
  /* `.build-card.brail .bfoot` sets this margin at 0-3-0, so a bare
     `.bfoot` lost and the 18px stayed -- which is what pushed the button
     33px past the card's bottom edge. Matched at the same specificity.
     (The build shot two rules up had the identical problem; this file
     styles the build card at three different specificities.) */
  .build-card.brail .bfoot, .brail .bfoot, .bfoot {
    margin-top: 0; padding-top: 6px;
  }
  /* And it was an 18px transparent word. Stop is the only way out of a
     build that is going wrong, so it gets a real target and an outline
     that says it is pressable. */
  .bfoot #stopBtn {
    min-height: 44px; padding: 0 18px;
    border: 1px solid var(--line); border-radius: 999px;
  }

  /* The build view is two columns on a desktop; one on a phone, with the
     transcript above the app rather than beside it. */
  .cols { grid-template-columns: 1fr; }
}

/* API keys: the no-CLI way in. Sits under the tool list because a tool
   that is already signed in is the shorter path when it exists; this is
   for when it does not. */
.ai-keys { margin-top: 18px; padding-top: 16px; border-top: 1px solid var(--line); }
.ai-keys h3 { font-size: 13px; font-weight: 600; margin-bottom: 3px; }
.ai-keys-sub { font-size: 12.5px; color: var(--faint); margin-bottom: 12px; }
.ai-key-row {
  display: flex; align-items: center; gap: 9px; padding: 7px 0;
}
.ai-key-name { flex: none; width: 84px; font-size: 13px; }
.ai-key-input {
  flex: 1; min-width: 0; padding: 7px 10px; border-radius: 9px;
  border: 1px solid var(--line); background: rgba(0,0,0,0.35);
  color: var(--text); font-family: var(--mono); font-size: 12px;
}
.ai-key-input:focus { outline: none; border-color: rgba(255,255,255,0.28); }
.ai-key-state { flex: 1; font-size: 12.5px; color: var(--faint); }
.ai-key-state.ok { color: #7ee2a8; }
.ai-key-env { flex: none; font-size: 11.5px; color: var(--ghost); }
.ai-keys-note { margin-top: 8px; font-size: 12px; color: var(--faint); min-height: 1em; }

/* ---- sessions in the drawer -------------------------------------------- */
.side-sessions {
  display: flex; flex-direction: column; gap: 1px;
  overflow-y: auto; min-height: 0; flex: 1;
  margin: 0 -4px; padding: 0 4px;
  scrollbar-width: thin;
}
.side-sessions::-webkit-scrollbar { width: 6px; }
.side-sessions::-webkit-scrollbar-thumb {
  background: var(--film-strong); border-radius: 3px;
}
.sess-row {
  display: flex; align-items: center; gap: 8px;
  width: 100%; padding: 6px 8px; min-height: 30px;
  background: none; border: 0; border-radius: 7px;
  color: var(--muted); font-family: var(--sans); font-size: 13px;
  cursor: pointer; text-align: left;
  transition: background 0.14s, color 0.14s;
}
.sess-row:hover { background: var(--film); color: var(--text); }
.sess-row.on { background: var(--film-strong); color: var(--text); }

/* The open session, in 5px.
 *
 * Deliberately the ONLY coloured mark in this list. A dot on every row --
 * the first version of this -- looked like information and was not: all 21
 * sessions on this machine are built, so every dot was green and the
 * column said nothing. One green dot on the row you are actually in says
 * something every time you look at it.
 *
 * The reserved space is what keeps it calm: every row leaves room for the
 * dot, so the titles stay on one left edge and nothing shifts when the
 * mark moves. */
.sess-dot {
  flex: none; width: 5px; height: 5px; border-radius: 50%;
  background: none;
  transition: background 0.15s;
}
.sess-dot.here { background: #4ade80; }
body.light .sess-dot.here { background: #16a34a; }

/* A row that the search has ruled out. */
.sess-row.hidden { display: none; }
.sess-name {
  flex: 1; min-width: 0; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap;
}
/* The rename field replaces the label in place, so the row does not jump. */
.sess-name[contenteditable="true"] {
  outline: none; cursor: text; color: var(--text);
  background: rgba(0,0,0,0.4); border-radius: 5px;
  padding: 1px 5px; margin: -1px -5px;
  box-shadow: inset 0 0 0 1px rgba(255,255,255,0.22);
  text-overflow: clip;
}
.sess-kebab {
  flex: none; opacity: 0; background: none; border: 0; cursor: pointer;
  color: var(--faint); padding: 2px 3px; border-radius: 5px; line-height: 0;
}
.sess-row:hover .sess-kebab { opacity: 1; }
.sess-kebab:hover { color: var(--text); background: var(--film-strong); }
.side-sessions .side-empty {
  padding: 6px 9px; font-size: 12px; color: var(--ghost);
}

/* ---- the resize grip ---------------------------------------------------
 * A hit area wider than the line it draws: a 1px target is a coin flip
 * with a mouse, and the line is the only part anyone should see. */
.side-grip {
  /* Pinned to the drawer's right edge and taken OUT of the flex flow.
     As the column's last child it was being laid out by flex, which gave
     it a computed box of 0x0 -- present in the DOM, impossible to grab,
     which is why dragging never did anything. `flex: none` plus explicit
     offsets is what makes an absolutely positioned child of a flex
     container actually take its own size. */
  /* Entirely INSIDE the drawer. It used to hang 4px past the right edge,
     and out there the scrim (which covers the whole window under the
     drawer) took the pointer instead: the grip's z-index only orders it
     within .side, so outside that box it cannot win. A 10px strip on the
     inside of the edge is grabbable and beats nothing. */
  position: absolute; top: 0; bottom: 0; right: 0;
  width: 10px; flex: none;
  cursor: col-resize; z-index: 70;
}
.side-grip::after {
  content: ""; position: absolute; top: 0; bottom: 0; right: 0; width: 1px;
  background: transparent; transition: background 0.15s;
}
.side-grip:hover::after,
.side-grip:focus-visible::after,
body.side-resizing .side-grip::after { background: rgba(140,120,255,0.55); }
.side-grip:focus-visible { outline: none; }
/* While dragging, nothing else on the page should select or hover. */
body.side-resizing { cursor: col-resize; user-select: none; }
body.side-resizing * { pointer-events: none; }
body.side-resizing .side-grip { pointer-events: auto; }

/* The Your apps header's button group. It used to carry class="side",
   which the drawer also uses: a fixed 240px panel pinned to the window's
   left edge, over the page. Two very different things cannot share a
   class name and only one of them wins. */
.page-head .head-actions { display: flex; align-items: center; gap: 10px; }

/* ---- the running build, as a tile -------------------------------------- */
.shelf-card.building .shot {
  position: relative; overflow: hidden;
  border-color: rgba(140,120,255,0.34);
  background: rgba(120,100,240,0.07);
}
/* A sweep, not a spinner: the tile is filling in, not waiting on a queue. */
.shot-working {
  position: absolute; inset: 0;
  background: linear-gradient(105deg,
    transparent 20%, rgba(168,150,255,0.24) 48%, transparent 76%);
  transform: translateX(-100%);
  animation: shotSweep 1.5s cubic-bezier(0.4, 0, 0.2, 1) infinite;
}
@keyframes shotSweep { to { transform: translateX(100%); } }
.shelf-making {
  display: block; margin-top: 2px;
  font-size: 11px; color: #a89bff;
}
@media (prefers-reduced-motion: reduce) {
  .shot-working { animation: none; transform: none; opacity: 0.5; }
}

/* ---- the send button, live as soon as there is something to send ------- */
.send.ready, .bigbar-row .send.ready {
  background: var(--blue);
  color: #fff;
  box-shadow: 0 0 0 3px rgba(98,145,255,0.16);
}
.send.ready:hover { filter: brightness(1.08); }

/* ---- the session view's field ------------------------------------------
 * Dimmer here than on the home screen. Home is a room you are standing in;
 * a session is work you are reading, and the same field behind a wall of
 * text competes with it. */

/* The drawer's two top rows.
 *
 * Row one is the system's: macOS paints its traffic lights across it, so
 * nothing of ours goes under them -- only the panel toggle, pushed to the
 * far side of the lights and centred on their line.
 *
 * Row two is ours: the Krate mark, on a clean line where no system chrome
 * is drawn. This is what ChatGPT does, and it is why its logo never looks
 * a few pixels out: it is not trying to share a row with three round
 * buttons of a different size.
 */
.side-brandrow {
  flex: none; display: flex; align-items: center;
  /* Tight under the lights row. At 44px with the row above it at 60 the
     mark sat in a pocket of empty drawer, which is dead space at the one
     place a person looks first. */
  height: 34px; padding: 0 2px; margin-bottom: 2px;
  /* Pulled up under the lights row rather than shortening that row: it is
     60px because that is what puts the toggle on the traffic lights'
     centre line, and changing it moves the toggle off them again. */
  margin-top: -14px;
}
.side-top { justify-content: flex-end; }
/* The row is flex-end, so a SMALLER right margin moves the toggle
   further right, toward the drawer's edge. */
.side-top .side-toggle { margin-right: 0; }
/* One toggle on screen at a time. While the drawer is open it carries its
   own, so the room's would be a second identical button two inches away
   doing the same thing. */
body.side-open .view .side-toggle { display: none; }
/* Off macOS there are no lights to dodge, so the two rows collapse back
   into one: the toggle sits beside the mark. */
/* Off macOS there are no traffic lights to dodge, so the lights row is not
   needed -- but it HOLDS the drawer's close button, and the room's toggle
   hides whenever the drawer is open. Hiding the row outright therefore left
   Windows and Linux with an open drawer and nothing to close it.
   The row collapses to nothing and its toggle moves into the brand row,
   which is where a single-row drawer wants it anyway. */
body:not(.macos) .side-top {
  height: 0; margin: 0; padding: 0; overflow: visible;
}
body:not(.macos) .side-brandrow {
  height: var(--titlebar); margin-bottom: 10px;
  justify-content: space-between;
}
/* top:2px, not 15px: this button has to land on the same line as the window's
   own caption buttons, which are 34px tall at top:0. At 15 it centred at y=30
   while they centre at y=17, so opening the drawer moved the toggle down a
   row. Same reasoning as the titlebar rule above. */
body:not(.macos) .side-top .side-toggle {
  position: absolute; top: 2px; right: 12px; z-index: 3;
  color: rgba(255, 255, 255, 0.72);
}
body:not(.macos) .side-top .side-toggle:hover { color: #fff; }
body:not(.macos).light .side-top .side-toggle { color: rgba(12, 16, 23, 0.75); }
body:not(.macos).light .side-top .side-toggle:hover { color: #000; }

/* ---- dictation ---------------------------------------------------------
 * The microphone has two jobs while it runs: be obviously live, and be
 * obviously stoppable. A pulsing red mark reads as recording in every app
 * a person has used, so it does not need explaining. */
/* Empty almost always -- it carries dictation status and refusals, and
 * those clear themselves after a few seconds. Reserving a line for it
 * permanently cost 23px (8px margin + a 1.2em min-height) of dead space
 * between the box and the suggestions on every screen, for a message that
 * is hardly ever there. `:empty` collapses it completely and it takes its
 * line back the moment it has something to say. */
.home-hint {
  margin: 8px 0 0;
  text-align: left; width: min(680px, 100%);
  color: var(--faint); font-size: 12.5px;
  min-height: 1.2em;
}
.home-hint:empty { min-height: 0; margin: 0; }
.tool.wave.listening {
  color: #ff6b6b;
  background: rgba(255, 107, 107, 0.12);
}
.tool.wave.listening::after {
  content: ""; position: absolute; inset: -3px;
  border-radius: inherit;
  border: 1px solid rgba(255, 107, 107, 0.5);
  animation: micPulse 1.4s ease-out infinite;
}
.tool.wave { position: relative; }
@keyframes micPulse {
  from { opacity: 0.9; transform: scale(0.92); }
  to   { opacity: 0;   transform: scale(1.25); }
}
@media (prefers-reduced-motion: reduce) {
  .tool.wave.listening::after { animation: none; opacity: 0.7; }
}

/* ---- the details sheet, for developers ---------------------------------
 * The command that reproduces a build, and the paths behind it. Monospace
 * because these are things to copy, not to read. */
.info-cmd {
  margin: 6px 0 10px; padding: 10px 12px;
  background: rgba(0, 0, 0, 0.45); border: 1px solid var(--line);
  border-radius: 9px;
  font: 11.5px/1.6 var(--mono); color: #c9cfda;
  white-space: pre-wrap; word-break: break-all;
  user-select: text;
}
.info-actions { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 8px; }
#infoSourcePath {
  font-family: var(--mono); font-size: 11px; color: var(--muted);
  word-break: break-all; user-select: text;
}
.info-note code {
  font-family: var(--mono); font-size: 11px;
  background: var(--film); border-radius: 4px; padding: 1px 5px;
}

/* ---- phone layout for rows that are defined later in this file --------
 *
 * Last, deliberately. `.ai-key-row` is declared further down than the
 * media query that overrides it, and equal specificity is decided by
 * order -- so the phone layout has to come after everything it corrects.
 * The spend rows are injected by the web bridge after this sheet loads,
 * which is why they carry their own !important below: nothing in a file
 * can out-order a stylesheet appended at runtime.
 */
@media (max-width: 860px) {
  .ai-key-row {
    display: grid; grid-template-columns: minmax(0, 1fr) auto;
    gap: 6px 10px; align-items: center;
  }
  .ai-key-row .ai-key-name { grid-column: 1 / -1; width: auto; font-weight: 500; }
  .ai-key-row .ai-key-state { grid-column: 1; }
  .ai-key-row .ai-key-input { grid-column: 1; min-width: 0; }
  .ai-key-row .ai-key-save,
  .ai-key-row .ai-key-forget { grid-column: 2; }

  /* App name and price on one line, the detail under them. */
  .web-spend-row {
    display: grid !important;
    grid-template-columns: minmax(0, 1fr) auto;
    gap: 2px 8px;
  }
  .web-spend-row .app { grid-column: 1; grid-row: 1; font-weight: 500; }
  .web-spend-row .usd { grid-column: 2; grid-row: 1; text-align: right; }
  .web-spend-row .who { grid-column: 1 / -1; font-size: 10.5px; }
}

/* ---- a phone, measured ------------------------------------------------
 *
 * Last in the file on purpose: several of the rules below correct
 * declarations that appear later than any media query placed mid-file,
 * and equal specificity is settled by source order.
 *
 * Every number here came off a 412x915 screen with the layout on it.
 */
@media (max-width: 860px) {
  /* The shelf header ran "My apps | Examples ... Browse all" on one line
     and pushed Browse all to x=427 on a 412px screen. The tabs keep the
     line; the link wraps under them when there is no room. */
  .shelf-head { flex-wrap: wrap; row-gap: 8px; }
  .shelf-all { margin-left: auto; }

  /* The composer's "Built by Krate AI · change" chip wrapped onto three
     lines, which is what pushed the dictation button off the right edge.
     It is one line or it is not shown. */
  .aichip { flex: 0 1 auto; min-width: 0; white-space: nowrap; overflow: hidden; }
  .aichip .chev { display: none; }
  .bigbar-row { min-width: 0; }
  .bigbar-row > * { flex: none; }
  .bigbar-row .grow { flex: 1 1 auto; min-width: 0; }

  /* Nothing in the app view may sit outside the screen. */
  .view { overflow-x: hidden; }
}

/* ---- the session view, on a phone ------------------------------------
 *
 * The founder's screenshot showed the "Krate AI" chip cut in half at the
 * right edge and the composer floating in the transcript rather than
 * pinned. The header is a three-part row that assumes desktop width.
 */
@media (max-width: 860px) {
  #viewSession .titlebar {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    grid-auto-flow: column;
    align-items: center;
    gap: 8px; padding-right: 10px;
    height: auto; min-height: var(--titlebar);
  }
  /* Left-aligned, not centred in its column: the title belongs beside the
     back arrow, reading as one unit with it. */
  #viewSession .crumb { justify-self: start; text-align: left; }
  #viewSession .spacer { display: none; }
  #viewSession .crumb {
    max-width: 100%; min-width: 0;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  /* The agent chip shrinks to its dot and name, and never past the edge. */
  #viewSession .agent-chip {
    max-width: 38vw; min-width: 0;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }

  /* A command in the transcript has no space to break at, so a long flag
     broke mid-word and read as truncated text. Breaking anywhere keeps the
     whole command readable in the column rather than hiding its tail. */
  .thread .msg .body code { overflow-wrap: anywhere; }

  /* The composer belongs at the bottom of the screen, above nothing. */
  .rail .composer-shell {
    position: sticky; bottom: 0; z-index: 4;
    background: var(--bg);
    padding-top: 8px;
    border-top: 1px solid var(--line);
  }
  .rail .composer-hint { padding-bottom: 6px; }
}
