/*
  context-viewer — the whole stylesheet.

  Deliberately generic. It styles a file tree, a folder listing, the pages
  the app itself renders, and ordinary HTML (headings, paragraphs, lists,
  tables, code). It knows nothing about how anyone organises their notes.

  It is not served to an HTML note. A file that is already a complete HTML
  document is self-sufficient: the only thing it opts into is a class named
  ctx-, from components.css. Every rule here that mentions a document applies
  to the shells this app builds around markdown, source and plain text.

  Anything specific to your own documents goes in public/custom.css, which
  loads after this file when it exists and is git-ignored. Scope your rules
  there to :where(.cv-content, :root[data-ctx-frame] body).

  Every colour lives in public/themes.css, which loads first. Nothing here
  hard-codes a colour, so adding a theme never means adding a selector to
  this file. Type and spacing are stated here, and no theme changes them.
*/

:root {
  /* The one web font, and the only token here that anything reads. It is
     fetched by fonts.css, which the workbench and the document frame link and
     the sign-in page deliberately does not: the fallback stack is what a page
     without that link gets, and code still looks like code. --serif and
     --sans went with the two families nobody used. */
  --mono: "JetBrains Mono", ui-monospace, Menlo, monospace;
}

*, *::before, *::after { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: 1rem / 1.45 var(--ui);
  letter-spacing: -0.011em;
}

a { color: var(--accent); }

/* The default scrollbar is a light chunky bar whatever the theme is under it,
   which is the single loudest way a dark theme gives itself away. This is the
   same overlay bar in both directions, drawn from the theme's own tokens. */
* { scrollbar-width: thin; scrollbar-color: var(--scroll) transparent; }
::-webkit-scrollbar { width: 11px; height: 11px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: var(--scroll);
  background-clip: content-box;
  border: 3px solid transparent;
  border-radius: 6px;
}
::-webkit-scrollbar-thumb:hover { background: var(--scroll-hover); background-clip: content-box; }
::-webkit-scrollbar-corner { background: transparent; }

/* ═══════════════════════════════════════════════════════════
   The workbench. Activity bar, sidebar, tabs, minimap, panel:
   VS Code's layout, because that is the shape people already
   know for "browse a directory and read what is in it".
   ═══════════════════════════════════════════════════════════ */

:root {
  --ui: system-ui, -apple-system, "Segoe UI", sans-serif;
  --ui-size: 13px;
  /* Filenames are prose, not code: 22px is VS Code's density for identifiers
     and reads as cramped for "meeting notes from the quarterly planning session". */
  --row: 26px;
  --activity: 48px;
  --status: 22px;
  /* One step of tree nesting. VS Code's 8px is legible against its own 22px
     rows and always-on guides; here it left a folder's children looking like
     its siblings, so the step is wider and the guides are always drawn. */
  --ctx-indent: 14px;
  /* Two radii, and two is the whole scale: r1 for controls that sit in a row
     with others (inputs, buttons, menu items), r2 for a surface that floats
     (the card, a menu, the agent dock). Documents get neither: a document is
     paper, and paper has corners. */
  --r1: 4px;
  --r2: 8px;

  /* The chrome is a desk; the document is paper set on it. The tones used to
     be a hair apart, so nothing read as a surface. This gap is the whole
     reason the app stops looking flat, and it costs no borders or shadows.
     Both tones, and every other colour, come from the UI theme: see
     public/themes.css. */
}

/* Every child is placed explicitly. With seven children and implicit
   placement the browser is free to invent rows, which is exactly what it did:
   the sidebar collapsed to zero height and the editor landed on top of it. */
.ctx-workbench {
  display: grid;
  grid-template-columns: var(--activity) auto 4px 1fr;
  grid-template-rows: 1fr auto auto var(--status);
  height: 100vh;
  /* The visible height on a phone, between the address bar and the toolbar.
     100vh is the height with both retracted, so the status bar sat under them. */
  height: 100dvh;
  overflow: hidden;
  font-family: var(--ui);
  font-size: var(--ui-size);
}
.ctx-activity { grid-area: 1 / 1 / 4 / 2; }
.ctx-sidebar  { grid-area: 1 / 2 / 4 / 3; }
.ctx-resize-x { grid-area: 1 / 3 / 4 / 4; }
.ctx-editor   { grid-area: 1 / 4 / 2 / 5; }
.ctx-resize-y { grid-area: 2 / 4 / 3 / 5; }
.ctx-panel    { grid-area: 3 / 4 / 4 / 5; }
.ctx-status   { grid-area: 4 / 1 / 5 / -1; }

/* ── Activity bar ───────────────────────────────────────── */

.ctx-activity {
  display: flex;
  flex-direction: column;
  align-items: center;
  background: var(--chrome);
  border-right: 1px solid var(--chrome-line);
}
.ctx-activity button {
  width: var(--activity);
  height: var(--activity);
  display: grid;
  place-items: center;
  background: none;
  border: none;
  border-left: 2px solid transparent;
  color: var(--muted);
  cursor: pointer;
  transition: color 0.14s ease, background-color 0.14s ease, border-left-color 0.2s ease;
}
/* The indicator keeps its border rather than becoming a pseudo-element: the
   button is deliberately not positioned, because .ctx-badge hangs off an
   ancestor further up and making this relative would move the badge. */
.ctx-activity button:hover { color: var(--text); background: var(--hover); }
.ctx-activity button:active svg { transform: scale(0.92); }
.ctx-activity button[aria-selected="true"] {
  color: var(--text);
  border-left-color: var(--focus);
}
/* The app's icon, top left, and a way home. */
.ctx-logo { display: grid; place-items: center; width: var(--activity); height: var(--activity); flex: none; }
.ctx-logo img { width: 32px; height: 32px; }
.ctx-activity .spacer { flex: 1; }
.ctx-activity svg {
  width: 22px;
  height: 22px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  /* Small enough to feel like a response rather than an effect. */
  transition: transform 0.14s ease;
}
.ctx-activity button:hover svg { transform: scale(1.07); }
.ctx-badge {
  position: absolute;
  transform: translate(11px, 9px);
  min-width: 15px;
  height: 15px;
  padding: 0 4px;
  border-radius: 8px;
  background: var(--focus);
  color: var(--on-focus);
  font-size: 9px;
  line-height: 15px;
  text-align: center;
}

/* ── The gear menu ──────────────────────────────────────── */

.ctx-menu {
  position: absolute;
  z-index: 20;
  min-width: 180px;
  padding: 4px;
  background: var(--card);
  border: 1px solid var(--chrome-line);
  border-radius: var(--r2);
  box-shadow: 0 8px 28px -10px var(--shadow);
}
.ctx-menu[hidden] { display: none; }
/* Positioned by class, never by [data-menu]. That attribute is the dismiss
   handler's hook and nothing else: when it was doing both jobs, the context
   menu picked up this bottom and, already carrying an inline top, stretched
   between the two. 145px of items in a 377px box, the empty part of it still
   swallowing the clicks that were meant to close it. */
.ctx-root-menu { left: 8px; bottom: 56px; }
/* The context menu is placed by script, wherever it was asked for. */
.ctx-context { top: 0; left: 0; bottom: auto; }
.ctx-menu button[hidden] { display: none; }
.ctx-menu button.danger { color: var(--danger); }
.ctx-menu button {
  border-radius: var(--r1);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  width: 100%;
  padding: 5px 10px;
  font: inherit;
  text-align: left;
  color: var(--text);
  background: none;
  border: none;
  cursor: pointer;
}
.ctx-menu button:hover, .ctx-menu button[aria-selected="true"] { background: var(--selected); }
.ctx-menu button span { color: var(--muted); font-size: 11px; }
.ctx-submenu { position: relative; }
.ctx-themes {
  left: calc(100% + 2px);
  /* The gear sits at the bottom of the activity bar, so a submenu anchored at
     its item's top runs off the screen. It grows upward instead. */
  bottom: -4px;
  /* Fifteen colour themes is more than fits beside a menu near the bottom of
     the screen, so the list scrolls rather than running off it. */
  max-height: min(60vh, 420px);
  overflow-y: auto;
}
.ctx-themes button[aria-selected="true"] { background: var(--selected); }
.ctx-themes button[aria-selected="true"]::after { content: "✓"; }

/* ── Sidebar ────────────────────────────────────────────── */

.ctx-sidebar {
  width: var(--sidebar-w, 260px);
  display: flex;
  flex-direction: column;
  background: var(--chrome);
  border-right: 1px solid var(--chrome-line);
  overflow: hidden;
}
.ctx-workbench.sidebar-hidden .ctx-sidebar,
.ctx-workbench.sidebar-hidden .ctx-resize-x { display: none; }
.ctx-sidebar h2 {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin: 0;
  padding: 11px 14px 9px;
  font-size: 11px;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--chrome-text);
}
/* Refresh, at the far end of the section header, the way VS Code puts its
   section actions there. Faint but always present, on the same reasoning as
   the tab's close button a few rules down: invisible-until-hover means nobody
   finds it. Hovering the sidebar brings it up, hovering the button lights it. */
.ctx-sidebar h2 .ctx-refresh {
  margin-left: auto;
  flex: none;
  display: flex;
  align-items: center;
  padding: 2px;
  color: var(--chrome-text);
  background: none;
  border: none;
  border-radius: var(--r1);
  cursor: pointer;
  opacity: 0.4;
  transition: opacity 0.14s ease, background-color 0.14s ease, color 0.14s ease;
}
.ctx-sidebar:hover .ctx-refresh,
.ctx-sidebar .ctx-refresh:focus-visible,
.ctx-sidebar .ctx-refresh[data-spinning] { opacity: 1; }
.ctx-sidebar .ctx-refresh:hover { background: var(--hover); color: var(--text); }
.ctx-sidebar .ctx-refresh svg {
  width: 14px;
  height: 14px;
  stroke: currentColor;
  fill: none;
  stroke-width: 2;
  stroke-linecap: round;
}
.ctx-sidebar .ctx-refresh[data-spinning] { cursor: default; }
.ctx-sidebar .ctx-refresh[data-spinning] svg { animation: ctx-spin 0.7s linear infinite; }
@keyframes ctx-spin { to { transform: rotate(360deg); } }
/* Someone who asked not to see motion still gets the state, as a fade. */
@media (prefers-reduced-motion: reduce) {
  .ctx-sidebar .ctx-refresh[data-spinning] svg { animation: none; opacity: 0.45; }
}

/* The root's name, so you can tell which directory this instance serves
   without reading the breadcrumb. */
.ctx-sidebar h2 .root {
  font-size: 11px;
  font-weight: 400;
  text-transform: none;
  letter-spacing: 0;
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
}
.ctx-view { flex: 1; overflow: auto; padding-bottom: 1rem; }
.ctx-view[hidden] { display: none; }

/* Drag handles, wired up in Workbench.tsx. Four pixels of hit area, invisible
   until used. The panel's handle sits in an `auto` row, so it needs a height
   of its own or it is zero pixels tall and cannot be grabbed. */
.ctx-resize-x, .ctx-resize-y { background: none; touch-action: none; }
.ctx-resize-x { cursor: col-resize; }
.ctx-resize-y { cursor: row-resize; height: 4px; }
/* While dragging: an iframe swallows every pointer move made over it, and a
   drag should not select the text it passes. */
.ctx-workbench[data-resizing] { user-select: none; }
.ctx-workbench[data-resizing] iframe { pointer-events: none; }
.ctx-workbench[data-resizing="x"] { cursor: col-resize; }
.ctx-workbench[data-resizing="y"] { cursor: row-resize; }
.ctx-panel[hidden] + .ctx-resize-y, .ctx-resize-y:has(+ .ctx-panel[hidden]) { display: none; }
.ctx-resize-x:hover, .ctx-resize-y:hover, .ctx-dragging { background: var(--focus); }

/* ── Explorer tree ──────────────────────────────────────── */

.ctx-tree, .ctx-tree ul { list-style: none; margin: 0; padding: 0; }
.ctx-tree li { position: relative; }
/* Indent guides, the faint vertical lines VS Code draws per level. The line
   sits under the parent folder's chevron, and nesting adds no margin of its
   own: a row's indent comes from its depth and nothing else. */
.ctx-tree ul { position: relative; }
.ctx-tree ul::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  /* Under the parent row's chevron: its own indent, plus half a chevron. */
  left: calc(12px + var(--d, 0) * var(--ctx-indent));
  border-left: 1px solid var(--chrome-line);
  opacity: 0.55;
}
.ctx-tree ul:hover::before { opacity: 1; }
.ctx-row {
  display: flex;
  align-items: center;
  gap: 2px;
  height: var(--row);
  padding-left: calc(4px + var(--i, 0) * var(--ctx-indent));
  padding-right: 8px;
  color: var(--text);
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  user-select: none;
}
.ctx-row { border-left: 2px solid transparent; }
.ctx-row:hover { background: var(--hover); }
/* Matches how the activity bar marks its selected view, so "where I am"
   looks the same everywhere in the app. */
.ctx-row[aria-current] {
  background: var(--selected);
  border-left-color: var(--focus);
  color: var(--text);
  font-weight: 500;
}
.ctx-row .name { overflow: hidden; text-overflow: ellipsis; }
.ctx-chevron {
  width: 16px;
  height: 16px;
  flex: none;
  stroke: currentColor;
  fill: none;
  stroke-width: 1.6;
  color: var(--chrome-text);
  transition: transform 0.12s ease;
}
.ctx-row[aria-expanded="true"] .ctx-chevron { transform: rotate(90deg); }
.ctx-row .spacer { width: 16px; flex: none; }
.ctx-icon { width: 16px; height: 16px; flex: none; opacity: 0.75; }
.ctx-folder { color: var(--chrome-text); opacity: 0.65; }
.ctx-dots {
  margin-left: auto;
  padding: 0 2px;
  font: inherit;
  line-height: 1;
  color: var(--chrome-text);
  background: none;
  border: none;
  border-radius: 3px;
  cursor: pointer;
  opacity: 0;
}
.ctx-row:hover .ctx-dots, .ctx-dots:focus { opacity: 1; }
.ctx-dots:hover { background: var(--hover-strong); }

.ctx-tree .more { padding: 4px 20px; color: var(--muted); font-size: 11px; }

/* ── Source control ─────────────────────────────────────── */

.ctx-scm { padding: 0 8px; }
.ctx-scm textarea {
  width: 100%;
  font: inherit;
  padding: 5px 7px;
  margin-bottom: 6px;
  border: 1px solid var(--chrome-line);
  border-radius: var(--r1);
  background: var(--bg);
  color: var(--text);
  resize: vertical;
}
.ctx-scm .actions { display: flex; gap: 4px; margin-bottom: 10px; }
.ctx-scm button {
  flex: 1;
  font: inherit;
  font-size: 12px;
  padding: 4px 6px;
  border: 1px solid var(--chrome-line);
  border-radius: var(--r1);
  background: var(--bg);
  color: var(--text);
  cursor: pointer;
}
.ctx-scm button.primary { background: var(--focus); border-color: var(--focus); color: var(--on-focus); }
.ctx-scm button:disabled { opacity: 0.5; cursor: default; }
.ctx-scm h3 {
  margin: 10px 0 4px;
  font-size: 11px;
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--chrome-text);
}
.ctx-scm li { display: flex; align-items: center; gap: 6px; height: var(--row); font-size: 12px; }
.ctx-scm .path { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ctx-scm .status { margin-left: auto; font-weight: 600; color: var(--focus); }
.ctx-scm .log { font-size: 11px; line-height: 1.5; color: var(--chrome-text); }
.ctx-scm .log div { padding: 3px 0; border-bottom: 1px solid var(--chrome-line); }
.ctx-scm .log .sha { font-family: var(--mono); color: var(--focus); }
.ctx-scm .empty { padding: 6px 0; color: var(--muted); font-size: 12px; }

/* ── Editor area: tabs, document, minimap ───────────────── */

.ctx-editor { display: flex; flex-direction: column; overflow: hidden; }

.ctx-tabs {
  display: flex;
  background: var(--chrome);
  overflow-x: auto;
  scrollbar-width: none;
}
.ctx-tab {
  display: flex;
  align-items: center;
  gap: 7px;
  max-width: 15rem;
  padding: 0 6px 0 12px;
  height: 38px;
  color: var(--chrome-text);
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  /* A hairline between tabs, not a box around each. */
  box-shadow: inset -1px 0 var(--chrome-line);
  /* box-shadow carries both the hairline and the active tab's accent rule, so
     transitioning it is what makes selecting a tab read as a move rather than
     a jump. */
  transition: background-color 0.14s ease, color 0.14s ease, box-shadow 0.2s ease;
}
.ctx-tab:hover { background: var(--hover); color: var(--text); }
.ctx-tab .label { overflow: hidden; text-overflow: ellipsis; }
.ctx-tab .ctx-icon { opacity: 0.85; }

/* The active tab is the top edge of the document: same paper, an accent rule
   above it, and deliberately no line below, so the two read as one surface. */
.ctx-tab[aria-selected="true"] {
  background: var(--paper);
  color: var(--text);
  box-shadow: inset 0 2px var(--focus), inset -1px 0 var(--chrome-line);
}
.ctx-tab .close {
  width: 20px;
  height: 20px;
  flex: none;
  display: grid;
  place-items: center;
  border: none;
  background: none;
  border-radius: 4px;
  color: inherit;
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
  /* Faint but present. Invisible-until-hover means nobody finds it. */
  opacity: 0.35;
  transition: opacity 0.14s ease, background-color 0.14s ease, transform 0.14s ease;
}
.ctx-tab:hover .close, .ctx-tab[aria-selected="true"] .close { opacity: 0.75; }
.ctx-tab .close:hover { background: var(--hover-strong); opacity: 1; transform: scale(1.12); }
.ctx-tab .close:active { transform: scale(0.94); }

/* Motion is the decoration here, so it is the part that goes. Every hover
   still changes colour, which is the half that carries the meaning. */
@media (prefers-reduced-motion: reduce) {
  .ctx-activity button,
  .ctx-activity svg,
  .ctx-tab,
  .ctx-tab .close { transition: none; }
  .ctx-activity button:hover svg,
  .ctx-activity button:active svg,
  .ctx-tab .close:hover,
  .ctx-tab .close:active { transform: none; }
}

/* VS Code's proportions: a short row, and the separators given room to
   breathe horizontally rather than vertically. */
.ctx-breadcrumbs {
  display: flex;
  align-items: center;
  height: 24px;
  padding: 0 1rem;
  font-size: 12px;
  color: var(--chrome-text);
  background: var(--paper);
  border-bottom: 1px solid var(--chrome-line);
}
.ctx-breadcrumbs [data-crumbs] {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
}
.ctx-breadcrumbs [data-crumbs] > span { opacity: 0.45; }
.ctx-breadcrumbs [data-crumbs] > a:last-child { color: var(--text); }
.ctx-breadcrumbs a { color: inherit; text-decoration: none; }
.ctx-breadcrumbs a:hover { color: var(--text); text-decoration: underline; }
.ctx-breadcrumbs .right { margin-left: auto; display: flex; gap: 10px; align-items: center; }
/* In the status bar now, so it is dressed as a status bar item: no border, no
   card, the strip's own height. VS Code's status items are text with a hover
   wash, and a bordered pill in a 22px strip reads as something that fell in. */
.ctx-agent-button {
  display: flex;
  align-items: center;
  gap: 5px;
  height: 100%;
  margin: 0 -6px;
  padding: 0 6px;
  color: inherit;
  transition: background-color 0.14s ease, color 0.14s ease;
}
.ctx-status .ctx-agent-button:hover { background: var(--hover-strong); color: var(--text); }
/* Open is a state worth showing: the panel it toggles may be scrolled out of
   sight, and this is the only handle on it. */
.ctx-status .ctx-agent-button[aria-expanded="true"] { color: var(--focus); }
.ctx-agent-button svg { width: 13px; height: 13px; }

.ctx-breadcrumbs button, .ctx-breadcrumbs select {
  font: inherit;
  font-size: 12px;
  color: inherit;
  background: none;
  border: none;
  cursor: pointer;
}

.ctx-surface { flex: 1; display: flex; overflow: hidden; position: relative; }
.ctx-doc {
  flex: 1;
  overflow: auto;
  position: relative;
  padding: 1rem;
  background: var(--doc-paper);
  color: var(--doc-ink);
  font: 1rem / 1.45 var(--ui);
  letter-spacing: -0.011em;
}
/* A framed document brings its own padding and does its own scrolling, so the
   pane gets out of the way entirely. The listing, settings and the component
   reference are this app's own UI and stay as they were. */
.ctx-doc:has(iframe) { overflow: hidden; padding: 0; }
/* The document is an iframe now, so the pane is only a hole for it. A note is
   a whole HTML file: it has its own head, links its own stylesheet, and some
   define custom elements in their own script. Only a real document boundary
   runs all of that the way opening the file in a browser does. The padding
   and the paper moved inside the frame, to .ctx-frame. */
.ctx-doc iframe {
  display: block;
  width: 100%;
  height: 100%;
  border: none;
  background: var(--doc-paper);
}
/* A note that brought its own HTML gets the ground a browser would give it,
   not the theme's. It paints its own surfaces and its own ink, so a dark
   theme's paper behind it is black under text the note drew dark. Canvas
   under color-scheme: light is the browser's own default page ground, which
   is the point: this is not a colour choice, it is the absence of one. */
.ctx-doc iframe[data-note] { color-scheme: light; background: Canvas; }

/* Inside the frame, for the documents this app generates itself: markdown,
   plain text, source. A note that brought its own HTML is not served this
   stylesheet at all (see NOTE_HEAD in app/_ctx/doc), which is the whole
   point of the boundary. */
:root[data-ctx-frame] body {
  max-width: 1000px;
  margin: 0 auto;
  padding: 1rem;
  background: var(--doc-paper);
  color: var(--doc-ink);
}
:root[data-ctx-frame] body:has(.ctx-is-code) { padding: 0.9rem 1rem 3rem; }

/* The minimap is a scaled clone of the document, not a rendering of code:
   there is no editor here, so it is a shrunken picture of the same DOM with
   a viewport box over it. Cheap, and it scrolls with the real thing. */
.ctx-minimap {
  width: 110px;
  flex: none;
  position: relative;
  overflow: hidden;
  /* A scaled clone of the document, so it takes the document's ground, not
     the desk's. */
  background: var(--doc-paper);
  cursor: pointer;
}
.ctx-minimap[hidden] { display: none; }
.ctx-minimap .inner {
  position: absolute;
  top: 0;
  left: 0;
  /* Width and scale are set by drawMinimap(): the clone is laid out at the
     document pane's real width so it wraps the way the document does. These
     are only what it looks like before the first draw. */
  width: 100%;
  transform-origin: 0 0;
  pointer-events: none;
  opacity: 0.55;
}
.ctx-minimap .viewport[hidden] { display: none; }
.ctx-minimap .viewport {
  position: absolute;
  left: 0;
  right: 0;
  background: color-mix(in srgb, var(--focus) 16%, transparent);
  border-top: 1px solid color-mix(in srgb, var(--focus) 35%, transparent);
  border-bottom: 1px solid color-mix(in srgb, var(--focus) 35%, transparent);
}

/* ── Bottom panel: the agent, as a terminal ─────────────── */

.ctx-panel {
  display: flex;
  flex-direction: column;
  height: var(--panel-h, 260px);
  background: var(--bg);
  border-top: 1px solid var(--chrome-line);
  overflow: hidden;
}
.ctx-panel[hidden] { display: none; }
.ctx-panel header {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 7px 14px;
  border-bottom: 1px solid var(--chrome-line);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--chrome-text);
}
.ctx-panel header .title { color: var(--text); border-bottom: 1px solid var(--focus); padding-bottom: 3px; }
.ctx-panel header .hint { text-transform: none; letter-spacing: 0; opacity: 0.7; }
.ctx-panel header button { margin-left: auto; font: inherit; background: none; border: none; cursor: pointer; color: inherit; font-size: 14px; }
.ctx-log { flex: 1; overflow-y: auto; padding: 10px 14px; font-family: var(--mono); font-size: 12px; line-height: 1.6; }
.ctx-line { margin-bottom: 7px; white-space: pre-wrap; }
.ctx-line.me::before { content: "❯ "; color: var(--focus); }
.ctx-line.me { color: var(--chrome-text); }
.ctx-line.tool, .ctx-line.err { font-size: 11px; color: var(--muted); }
.ctx-line.err { color: var(--danger); }
.ctx-prompt { display: flex; gap: 8px; padding: 8px 12px; border-top: 1px solid var(--chrome-line); }
.ctx-prompt textarea {
  flex: 1;
  font-family: var(--mono);
  font-size: 12px;
  resize: none;
  padding: 5px 7px;
  border: 1px solid var(--chrome-line);
  background: var(--bg);
  color: var(--text);
}
.ctx-prompt button {
  font: inherit;
  padding: 0 12px;
  border: none;
  background: var(--focus);
  color: var(--on-accent);
  cursor: pointer;
}

/* ── Status bar ─────────────────────────────────────────── */

.ctx-status {
  display: flex;
  align-items: center;
  gap: 16px;
  height: var(--status);
  padding: 0 10px;
  background: var(--chrome);
  border-top: 1px solid var(--chrome-line);
  font-size: 11px;
  color: var(--chrome-text);
}
.ctx-status button { font: inherit; color: inherit; background: none; border: none; cursor: pointer; }
/* The path is the only thing here that can be any length, so it is the only
   thing allowed to shrink. Without this the flex items all gave ground
   together and a narrow window crushed "not a repo" down to "a". */
.ctx-status > * { flex: none; white-space: nowrap; }
.ctx-status .right {
  margin-left: auto;
  min-width: 0;
  flex: 1 1 auto;
  overflow: hidden;
  text-overflow: ellipsis;
  text-align: right;
}

:is(button, a, select, textarea, input, .ctx-row):focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: -2px;
}

/* An empty panel should say what it is for rather than sit blank. */
.ctx-empty-hint {
  padding: 10px 14px;
  color: var(--muted);
  font-size: 12px;
  font-family: var(--ui);
}
.ctx-empty h1 { font-size: 1.1rem; }
.ctx-empty p { color: var(--muted); }

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
}

/* ── Folder listing ─────────────────────────────────────── */

.cv-index { width: 100%; border-collapse: collapse; }
.cv-index th { text-align: left; padding: 0 0 0.5rem; border-bottom: 1px solid var(--border); }
.cv-index th a {
  font-size: 0.72rem;
  font-weight: 500;
  color: var(--muted);
  text-decoration: none;
}
.cv-index th a:hover { color: var(--text); }
.cv-index td { padding: 0; border-bottom: 1px solid var(--border); }
/* A row is a target, so it behaves like one: the whole cell is the link, and
   the row lights up under the pointer the way a tree row does. */
.cv-index tr:hover { background: var(--hover); }
.cv-index .name a {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0.4rem 0;
  color: var(--text);
  text-decoration: none;
}
.cv-index .name a:hover span { text-decoration: underline; }
.cv-index .num, .cv-index .act {
  font-family: var(--mono);
  font-size: 0.72rem;
  color: var(--muted);
  text-align: right;
  white-space: nowrap;
  /* The name cell's padding is on its link, so the numbers carry their own,
     and align to the middle: on the baseline the smaller mono rides high. */
  padding: 0.4rem 0 0.4rem 1.5rem;
  vertical-align: middle;
  width: 1%;
}
/* Row actions stay out of the way until the row is hovered: this is a
   reading surface first, and delete should never be an easy mis-click. */
.cv-index .act button {
  font: inherit;
  color: var(--muted);
  background: none;
  border: none;
  cursor: pointer;
  opacity: 0;
  padding-left: 0.6rem;
}
.cv-index tr:hover .act button, .cv-index .act button:focus { opacity: 1; }
.cv-index .act button:hover { color: var(--accent); text-decoration: underline; }

/* ── Documents ──────────────────────────────────────────── */

/* The shells this app generates have no wrapper around the article, so the
   frame's body takes the same measure (see above). */
.cv-content { max-width: 1000px; }

/* Nested under :where(), so each rule weighs no more than its bare tag: the
   listing's own rules and a note's own stylesheet still win. */
:where(.cv-content, :root[data-ctx-frame] body) {
  :is(h1, h2, h3, h4, h5, h6) { margin: 0 0 0.65rem; line-height: 1.25; }
  h1 { font-size: 1.6rem; }
  h2 { font-size: 1.32rem; }
  h3 { font-size: 1.09rem; }
  :is(p, ul, ol) { margin: 0 0 1rem; }
  li { margin-bottom: 0.1rem; }
  a { font-weight: 500; }
  hr { border: none; border-top: 1px solid var(--border); margin: 0.8rem 0; }
  img { max-width: 100%; height: auto; }
  blockquote { margin: 0.5rem 0; padding-left: 1rem; color: var(--muted); }
  :is(code, pre) { font-family: var(--mono); font-size: 0.85em; }
  code { background: var(--panel); padding: 0.05em 0.3em; }
  pre { padding: 0.2rem 0 0.2rem 1rem; border-left: 2px solid var(--border); overflow-x: auto; }
  pre code { background: none; padding: 0; }
  table { width: 100%; border-collapse: collapse; margin: 0.5rem 0; font-size: 0.95em; }
  :is(th, td) { text-align: left; padding: 0.3rem 1.2rem 0.3rem 0; vertical-align: top; }
  thead th { border-bottom: 1px solid var(--border); }
}
.cv-raw { white-space: pre-wrap; font-family: var(--mono); font-size: 0.8rem; padding: 0; border: none; }

/* Source view. A code file is not prose: it wants the full width, a tight
   left edge and numbered lines, not the reading measure a document gets. */
.ctx-is-code { max-width: none; }
.ctx-code {
  margin: 0;
  padding: 0;
  border: none;
  counter-reset: line;
  font-family: var(--mono);
  font-size: 12px;
  line-height: 1.55;
  tab-size: 2;
  overflow-x: auto;
}
.ctx-code > code { display: block; }
.ctx-doc:has(.ctx-is-code) { padding: 0.9rem 1rem 3rem; }
/* A document that paints its own ground gets the whole pane, with no band of
   our paper framing it. It brings its own padding. */
.ctx-doc:has(.ctx-bleed) { padding: 0; }
.ctx-bleed { max-width: none; }
.ctx-editor:has(.ctx-is-code) .ctx-breadcrumbs { padding-left: 1rem; }
.ctx-code span {
  display: block;
  padding-left: 3.5rem;
  text-indent: 0;
  white-space: pre;
  counter-increment: line;
}
.ctx-code span::before {
  content: counter(line);
  position: absolute;
  left: 0;
  width: 2.8rem;
  text-align: right;
  color: var(--muted);
  opacity: 0.55;
  user-select: none;
}
.ctx-code span:hover { background: var(--hover); }

/* ── Sign in, first-run setup, settings ─────────────────── */

/* The door to the app, set on the desk the way a document is: the same paper,
   the same accent top edge the active tab wears. Nothing here is a brand; it
   is the workbench with one sheet on it and no tree yet. */
.cv-auth {
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 2rem 1rem;
  background: var(--chrome);
  font-family: var(--ui);
}
.cv-card {
  width: min(23rem, 100%);
  padding: 1.6rem 1.6rem 1.75rem;
  background: var(--paper);
  border: 1px solid var(--chrome-line);
  border-top: 2px solid var(--focus);
  border-radius: var(--r2);
  box-shadow: 0 12px 36px -20px var(--shadow);
}
.cv-card h1 { font-family: var(--ui); font-size: 1.15rem; margin: 0 0 1rem; }
.cv-card > .cv-hint { margin: -0.55rem 0 1.15rem; }

/* Settings is a document in a tab, so it starts at the document's left edge
   like every other document rather than floating in the middle of the pane. */
.cv-settings { max-width: 34rem; }
.cv-settings section { margin-bottom: 2rem; padding-bottom: 1.5rem; border-bottom: 1px solid var(--border); }
.cv-settings h2 { font-size: 1rem; margin: 0 0 0.4rem; }
.cv-back { font-size: 12px; margin: 0 0 1rem; }

/* The component reference. An example, then the markup that made it: a
   reference you have to view-source to use is not a reference. */
.cv-reference { max-width: 46rem; }
.cv-demo {
  padding: 0.5rem;
  margin-top: 0.5rem;
  background: var(--paper);
  border: 1px dashed var(--border);
  border-radius: var(--r1);
}
.cv-demo > :last-child { margin-bottom: 0; }
.cv-reference .cv-demo + pre { margin-top: 0; font-size: 0.75em; }
.cv-hint { font-size: 0.8rem; line-height: 1.5; color: var(--muted); margin: 0 0 1rem; }
.cv-optional { font-weight: normal; opacity: 0.7; }

:is(.cv-card, .cv-settings) form {
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
  align-items: flex-start;
}
/* A read-only instance wraps each form's controls in a disabled fieldset, so
   the fieldset has to become the flex container the form was: a fieldset's
   own border, padding and margin are browser defaults nobody wants here. */
:is(.cv-card, .cv-settings) fieldset {
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
  align-items: flex-start;
  width: 100%;
  border: none;
  margin: 0;
  padding: 0;
  min-width: 0;
}
:is(.cv-card, .cv-settings) fieldset:disabled { opacity: 0.55; }
:is(.cv-card, .cv-settings) fieldset:disabled button { cursor: not-allowed; }

/* Says why the forms below are dead, in the same register as a hint rather
   than as an error: nothing has gone wrong, this instance is just read-only. */
.cv-readonly {
  padding: 0.6rem 0.75rem;
  border: 1px solid var(--border);
  border-radius: var(--r1);
  background: var(--paper);
}

:is(.cv-card, .cv-settings) label {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  width: 100%;
  font-size: 0.78rem;
  color: var(--muted);
}
/* A hint under a field is part of the field, not a paragraph after it. */
:is(.cv-card, .cv-settings) label .cv-hint { margin: 0.1rem 0 0; font-size: 0.72rem; }
:is(.cv-card, .cv-settings) label.cv-checkbox { flex-direction: row; align-items: center; }
:is(.cv-card, .cv-settings) input:not([type="checkbox"], [type="radio"]) {
  font: inherit;
  font-size: 0.85rem;
  width: 100%;
  padding: 0.5rem 0.65rem;
  border: 1px solid var(--border);
  border-radius: var(--r1);
  background: var(--card);
  color: var(--text);
}
:is(.cv-card, .cv-settings) input:not([type="checkbox"], [type="radio"]):focus {
  outline: none;
  border-color: var(--focus);
  box-shadow: 0 0 0 3px var(--ring);
}
:is(.cv-card, .cv-settings) button {
  font: inherit;
  font-size: 0.85rem;
  padding: 0.5rem 1rem;
  border: none;
  border-radius: var(--r1);
  background: var(--accent);
  color: var(--on-accent);
  cursor: pointer;
}
:is(.cv-card, .cv-settings) button:hover { filter: brightness(1.08); }
/* On the door there is one action, so it takes the full width. In settings
   each section has its own Save and they should not read as page-wide. */
.cv-card button { width: 100%; padding-block: 0.6rem; margin-top: 0.25rem; }

.cv-error { color: var(--danger); font-size: 0.82rem; margin: 0; }
.cv-ok { color: var(--ok); font-size: 0.82rem; margin: 0; }


/* ── The agent ──────────────────────────────────────────── */

.cv-agent {
  position: fixed;
  right: 1rem;
  bottom: 1rem;
  width: min(26rem, calc(100vw - 2rem));
  max-height: min(32rem, 80vh);
  display: flex;
  flex-direction: column;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--r2);
  box-shadow: 0 6px 24px -8px var(--shadow);
}
.cv-agent[hidden] { display: none; }
.cv-agent header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0.4rem 0.6rem;
  border-bottom: 1px solid var(--border);
  font-family: var(--mono);
  font-size: 0.65rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
}
.cv-agent header button { font: inherit; background: none; border: none; cursor: pointer; color: var(--muted); }
.cv-agent-log { flex: 1; overflow-y: auto; padding: 0.6rem; font-size: 0.82rem; }
.cv-bubble { margin-bottom: 0.6rem; white-space: pre-wrap; }
.cv-bubble.me { color: var(--muted); }
.cv-bubble:is(.note, .error) { font-family: var(--mono); font-size: 0.68rem; color: var(--muted); }
.cv-bubble.error { color: var(--danger); }
.cv-agent-form { display: flex; gap: 0.4rem; padding: 0.6rem; border-top: 1px solid var(--border); }
.cv-agent-form textarea {
  flex: 1;
  font: inherit;
  font-size: 0.82rem;
  resize: vertical;
  padding: 0.4rem;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--text);
}
.cv-agent-form button {
  font: inherit;
  padding: 0 0.8rem;
  border: none;
  background: var(--accent);
  color: var(--bg);
  cursor: pointer;
}

/* ── Narrow screens and touch ───────────────────────────── */

/* Last in the file on purpose: these are overrides, and at equal specificity
   the later rule wins. Two separate questions, asked separately: how wide the
   screen is (a phone, a split window) and whether the pointer is a finger (a
   phone, but also a tablet that is wide enough for the desktop layout). */

.ctx-scrim { display: none; }

@media (max-width: 800px) {
  /* Still four columns. Dropping to three left every grid-area behind, since
     they are written against the four-column line numbers: the editor landed
     in an implicit fifth column and the status bar's -1 stopped short of it,
     leaving the bar 48px wide with the open file's path squeezed to nothing.
     The sidebar floats over the workbench here, so its column and the drag
     handle beside it collapse to zero instead of disappearing. */
  :root { --activity: 44px; }
  .ctx-workbench { grid-template-columns: var(--activity) 0 0 1fr; }

  /* The sidebar is a drawer: closed until asked for (drawer-open is set by
     Workbench, never by the server, so a phone does not open on a wall of
     tree), and closed again once a file is picked. sidebar-hidden is the
     desktop's toggle and means nothing here. */
  .ctx-sidebar {
    position: absolute;
    left: var(--activity);
    top: 0;
    bottom: var(--status);
    z-index: 6;
    width: min(300px, calc(100vw - var(--activity) - 40px));
    box-shadow: 8px 0 28px -12px var(--shadow);
  }
  .ctx-workbench:not(.drawer-open) .ctx-sidebar { display: none; }
  .ctx-workbench.drawer-open .ctx-sidebar { display: flex; }
  /* The activity bar stays uncovered, so its buttons still switch views. */
  .ctx-scrim {
    display: block;
    position: fixed;
    inset: 0 0 0 var(--activity);
    z-index: 5;
    background: color-mix(in srgb, #000 28%, transparent);
  }

  .ctx-resize-x, .ctx-resize-y, .ctx-minimap { display: none; }
  /* No drag handle here, so the panel is a fixed share of the screen: enough
     to read an answer, with the document still visible above it. */
  .ctx-panel { height: 50dvh; }

  /* A context menu opened at the pointer runs off a narrow screen, and the ⋮
     sits at the right edge, so it always did. Here it is a sheet along the
     bottom instead. !important because the pointer position is inline. */
  .ctx-context {
    position: fixed;
    top: auto !important;
    left: 8px !important;
    right: 8px;
    bottom: calc(var(--status) + 8px);
  }
  /* The theme list opens over its menu rather than beside it: beside it is
     past the right edge of a 360px screen. */
  .ctx-themes { left: 24px; bottom: 0; }

  /* The listing keeps name, size and modified. Created is the column you miss
     least, and rename/delete are in the tree's ⋮ menu. */
  .cv-index :is(th, td):nth-child(3), .cv-index .act { display: none; }
}

@media (pointer: coarse) {
  /* A fingertip is about 40px; 26px rows are a mis-tap waiting to happen. */
  :root { --row: 36px; --status: 30px; }
  .ctx-status { font-size: 12px; }

  /* There is no hover on a touchscreen, so nothing may depend on it. The ⋮ is
     the only way to reach rename and delete from the tree (iOS fires no
     contextmenu on a long press), so it is always drawn, and wide enough to hit. */
  .ctx-dots { opacity: 0.6; align-self: stretch; padding: 0 10px; }
  .cv-index .act button { opacity: 1; }
  .ctx-sidebar .ctx-refresh { opacity: 1; padding: 6px; }
  .ctx-tab .close { width: 28px; height: 28px; opacity: 0.6; }
  .ctx-menu button { padding: 10px 12px; }
  .ctx-panel header .hint { display: none; }
  .ctx-panel header button { padding: 4px 10px; font-size: 18px; }

  /* iOS zooms the page into any field under 16px when it takes focus, and
     does not zoom back out. !important because every form in the app sets
     its own smaller size, and this has to beat all of them. */
  :is(input, textarea, select) { font-size: 16px !important; }
}



/* ── Dialogs ────────────────────────────────────────────── */

/* The app's own confirm/prompt/alert. <dialog> gives the focus trap, Escape
   and the backdrop; this only has to make it look like the rest of the app. */
.ctx-dialog {
  width: min(26rem, calc(100vw - 2rem));
  padding: 0;
  color: var(--text);
  background: var(--card);
  border: 1px solid var(--chrome-line);
  border-radius: var(--r2);
  box-shadow: 0 18px 48px -18px var(--shadow);
}
.ctx-dialog::backdrop { background: color-mix(in srgb, #000 34%, transparent); }
.ctx-dialog form { padding: 18px; }
.ctx-dialog h2 {
  margin: 0;
  font-size: 0.95rem;
  font-weight: 600;
  line-height: 1.35;
}
.ctx-dialog p {
  margin: 8px 0 0;
  font-size: 0.8rem;
  line-height: 1.5;
  color: var(--muted);
}
.ctx-dialog input {
  width: 100%;
  margin-top: 14px;
  padding: 7px 9px;
  font: inherit;
  color: var(--text);
  background: var(--paper);
  border: 1px solid var(--border);
  border-radius: var(--r1);
}
.ctx-dialog input:focus-visible { outline: 2px solid var(--focus); outline-offset: -1px; }
.ctx-dialog-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 18px;
}
.ctx-dialog-actions button {
  padding: 6px 14px;
  font: inherit;
  color: var(--text);
  background: var(--chrome);
  border: 1px solid var(--chrome-line);
  border-radius: var(--r1);
  cursor: pointer;
  transition: background-color 0.14s ease, color 0.14s ease;
}
/* Not the filled buttons: this rule outweighs .danger, and hovering Delete
   swapped its red for a grey wash under white text. */
.ctx-dialog-actions button:not(.primary, .danger):hover { background: var(--hover-strong); }
.ctx-dialog-actions .primary {
  color: var(--on-focus);
  background: var(--focus);
  border-color: var(--focus);
}
.ctx-dialog-actions .primary:hover { filter: brightness(1.08); }
/* Delete is the one action here that is hard to take back, so it is the one
   that is coloured. Everything else is a neutral button. */
.ctx-dialog-actions .danger {
  color: #fff;
  background: var(--danger);
  border-color: var(--danger);
}
.ctx-dialog-actions .danger:hover { filter: brightness(1.08); }
