/* ===========================================================================
   System theme — the ground everything else is written on.

   The reset, both theme palettes as custom properties, the body, the root
   box that carries --ui-scale, the shared `spin` keyframes every spinner in
   the system borrows, and the global blockquote. Copied into public/css/
   and linked FIRST (public/index.html): the sheets after it use these
   variables and this keyframe without redeclaring them.

   WHAT LIVES ELSEWHERE
   --------------------
   Each App and each Widget owns its own stylesheet, authored next to its
   source and copied into public/css/ by build.js. The rest of the system is
   split the same way, one sheet per system concern:

     shell.css     the desktop pane / scaled layer / window layer
     window.css    window chrome and resize handles (client/window.jsx)
     menu.css      the shared Menu (client/menu.jsx)
     toast.css     the toast stack
     errors.css    the widget error boundary
     sidebar.css   the Agent Shell rail (client/agent-shell.jsx)

   Rule of thumb: if a selector only ever matches markup one App or Widget
   paints, it belongs in that file. The system sheets must stay
   widget-agnostic — the shell renders whatever the server sends it and
   knows nothing about what any of it does.
   =========================================================================== */

* {
 box-sizing: border-box;
}

/* ─────────────────────────────────────────────────────────────────────────
   The palette.

   There is ONE set of custom properties. Which scheme is active — a built-in
   (System, Molokai, Gruvbox, Solarized…), a user-created one, or a generated
   monochrome ramp — decides the value of every one of them. A scheme is light
   or dark purely by the values it supplies; `color-scheme` is set alongside so
   native form controls, scrollbars and the default canvas agree with the ink.

   The values below are the DEFAULT scheme ("System Dark"), the palette the UI
   shipped with. The Appearance widget overwrites them at runtime by setting the
   same properties on `document.documentElement.style`, so the whole app — and a
   brand-new tab before any widget mounts — is painted from one source of truth.
   Sheets still read these through var(); nothing after this file names a colour.
   ───────────────────────────────────────────────────────────────────────── */
:root {
  font-family: var(--font-code);

  /* Whole-UI scale. 1 on a fine pointer; the coarse-pointer block at the end
     of this file raises it for a phone. Must live here, on :root, because
     .app-root's own box is divided by it (see .app-root) and
     src/system/client/ui-scale.js reads it back out for pointer math. */
  --ui-scale: 1;

  /* Native controls follow the scheme. The Appearance widget flips this to
     'light' for a light scheme. */
  color-scheme: dark;

  /* Fonts. Two families the whole UI reads prose and code through; the
     Appearance settings change them at runtime. Sheets point their
     font-family at these instead of hardcoding a stack. */
  --font-prose: 'Source Serif 4', 'Source Serif Pro', Georgia, 'Times New Roman', serif;
  --font-code: 'Victor Mono', ui-monospace, 'SF Mono', 'JetBrains Mono', 'Fira Code', Menlo, Consolas, monospace;

  /* Backgrounds */
  --bg-desktop: #1a1a2e;
  --bg-desktop-2: #16213e;
  --bg-window: #2a2a4a;
  --bg-toolbar: rgba(0, 0, 0, 0.3);
  --bg-header: #3a3a5a;
  --bg-button: #4a4a6a;
  --bg-button-hover: #5a5a7a;
  --bg-button-highlight: #4c4c7f;
  --bg-grid-control: rgba(0, 0, 0, 0.2);
  /* The ground a control is written ON. Sheets have long asked for --bg-input
     (chat, HTTP Client, Transcript) without any theme ever defining it, so the
     ones with a fallback painted --text-muted — a TEXT colour used as a SURFACE.
     Every scheme now supplies it, paired with --text-on-input below so ground
     and ink cannot drift apart. */
  --bg-input: #8888aa;
  --bg-code: #16162b;
  --bg-quote: rgba(6, 9, 54, 1);
  --grid-overlay: rgba(255, 255, 255, 0.15);
  --bg-msg-user: #959595;
  --bg-msg-agent: #1f3382;

  /* Borders */
  --border-default: #4a4a6a;
  --border-focus: #6a6a8a;
  --border-button: #5a5a7a;
  --border-button-hover: #6a6a8a;
  --border-error: #a03434;

  /* Text */
  --text-primary: #d0d0e0;
  --text-secondary: #b0b0c0;
  --text-muted: #8888aa;
  --text-control: #a0a0c0;
  /* Ink for text written on --bg-input. It cannot be --text-primary: that is
     tuned for the window surface, and light text on a light ground is unreadable.
     Paired with --bg-input in every scheme so ground and ink cannot drift apart. */
  --text-on-input: #14142a;
  --text-user: #1b1b1b;
  --text-agent: #c5c5c5;
  --text-dark: #101010;

  /* Accent — the one colour that is never a shade of the base hue. */
  --accent-color: #00ff6a;
  --accent: var(--accent-color);

  /* Semantic error ink (the Transcript's error row; light schemes dim this). */
  --error-color: #ff6b6b;

  /* Status colours. These began as the window control DOTS; the chrome is Nerd
     Font glyphs now (see window.css) and its hover ground is --bg-button-hover,
     so nothing paints chrome from these any more. What reads them is the
     widgets: Transcript's warn level, the HTTP client's status column, the
     stopwatch's running time. --control-close is white in every scheme, which is
     why it is never a glyph colour on a header. */
  --control-close: #ffffff;
  --control-minimize: #f39c12;
  --control-maximize: #27ae60;

  /* Odds and ends widgets reach for. */
  --search-title-color: #6c9ef8;
}

/* iOS Safari: `overflow: hidden` alone does NOT stop the document scroller —
   a touch drag still rubber-bands the page and pulls the refresh banner. The
   shell is an app surface: body is a fixed viewport box (nothing left for the
   document to scroll) and overscroll is refused at both ends. */
html {
  height: 100%;
  overscroll-behavior: none;
}

body {
  position: fixed;
  inset: 0;
  margin: 0;
  padding: 0;
  overflow: hidden;
  overscroll-behavior: none;
  background: var(--bg-desktop);
  font-family: system-ui, -apple-system, sans-serif;
  user-select: none;
  /* iOS Safari inflates body text on its own when a page is laid out narrower
     than its content, which is what made a chat answer's serif paragraphs read
     too large on a phone while every short chrome label stayed put. Pinning the
     adjustment off is a system-level reset concern (it governs ALL text, not one
     widget), so it belongs on the body here, not as a per-widget font-size — the
     mechanism the whole-UI --ui-scale transform above already provides is only
     the deliberate mobile scale; this stops the *unwanted* one. */
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

/*
 * The whole UI, at one scale.
 *
 * The root is laid out in a viewport/uiScale box and painted through
 * `transform: scale(uiScale)`, so the two cancel out on screen: at uiScale 1
 * this is exactly the old `100vw x 100vh` box, and at any scale above it the
 * layout viewport a phone gets is 1/uiScale as many CSS pixels while everything
 * in it is drawn uiScale times as large. Layout happens once, at the smaller size, so nothing inside has to
 * know the scale exists — no per-widget font-size or padding override, no
 * second set of breakpoints. `transform-origin: top left` keeps the box pinned
 * to the top-left corner, which is what makes the doubled image cover the
 * viewport instead of overhanging it.
 *
 * Why a transform and not `zoom` or `initial-scale`: a transform also makes
 * this element the containing block for `position: fixed` descendants (the
 * grid overlay, the toast stack, the sidebar), so those scale with everything
 * else and still fill the screen, instead of being measured against a raw
 * viewport. It is the same mechanism `.desktop-scaled` already uses one level
 * down, so window geometry and the desktop grid stay in one coordinate space.
 * The cost is that pointer deltas are in viewport pixels while layout is not;
 * ui-scale.js hands the divisor to the drag code.
 */
.app-root {
  position: relative;
  box-sizing: border-box;
  width: calc(100vw / var(--ui-scale));
  height: calc(100vh / var(--ui-scale));
  /* dvh where supported: keeps the box the height of the visible viewport while
     iOS collapses the address bar, instead of the taller large-viewport value. */
  height: calc(100dvh / var(--ui-scale));
  /*
   * Side safe-area insets only: content must clear the notch sides, and the
   * padded band paints body's desktop color. Top and bottom are deliberately
   * NOT padded here — surfaces (the rail's ground, the shell background)
   * paint through the status bar and home indicator so they read full-bleed,
   * and each surface insets its OWN content instead (see .workspace-sidebar
   * and .workspace-main in sidebar.css). Insets are viewport px while this
   * box is viewport/scale px, so each divides by --ui-scale.
  */
  padding-left: calc(env(safe-area-inset-left, 0px) / var(--ui-scale));
  padding-right: calc(env(safe-area-inset-right, 0px) / var(--ui-scale));
  transform: scale(var(--ui-scale));
  transform-origin: top left;
  overflow: hidden;
  --grid-size: 16px;
}

/* Standalone (Home Screen) has no browser toolbars: the layout viewport is
   the whole screen and dvh stops short of the home indicator, which would
   leave an unpainted bar under the root. vh is the full height there. */
@media (display-mode: standalone) {
  .app-root { height: calc(100vh / var(--ui-scale)); }
}

/* The one spinner keyframe. The shell's streaming row and several widget GUIs
   animate with it, so it belongs to the theme sheet every sheet loads after. */
@keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }

/* Markdown blockquote styling — for markdown painted without the renderer's
   `.md-blockquote` class. What the shared renderer produces is styled in
   /css/markdown.css. */
blockquote {
  border-left: 4px solid var(--accent-color);
  margin: 1em 0;
  padding: 0.5em 1em;
  color: var(--text-secondary, #adadad);
  font-style: italic;
  background: var(--bg-quote, rgba(6, 9, 54, 1));
  border-radius: 0 4px 4px 0;
}

/* Mobile may draw the entire interface magnified. See .app-root — the scale
   lives on the root box, so if it is raised the shell, the sidebar, the
   windows and every widget GUI inside scale together: ONE number, and
   anything that would need its own override is a bug somewhere else.

   1 is the calibrated default: a phone at browser zoom 50% looked right
   next to desktop, which is --ui-scale 1. Raising it magnifies everything
   uniformly; mobile-scale-check.mjs measures against whatever value is set
   (it guards layout/paint/pointer agreement, not the number).

   Keep it a PLAIN NUMBER. ui-scale.js reads it with parseFloat for pointer
   math, and .app-root divides viewport lengths by it, so a calc()/clamp()
   expression here would parse as NaN and the drags would silently divide
   by 1. */
@media (hover: none) and (pointer: coarse) {
  :root {
    --ui-scale: 1;
  }
  /* iOS Safari 17+ and Firefox for Android IGNORE user-scalable=no — a
     two-finger pinch or double-tap still zooms the page, and the zoom
     multiplies --ui-scale. touch-action intersects down to every descendant:
     single-finger pan (inner scrollers) stays, page pinch/double-tap zoom
     goes. viewport-zoom.js covers what gestures cannot: focus auto-zoom
     and the zoom iOS restores from the last visit. */
  body {
    touch-action: pan-x pan-y;
  }
}
