/* Base — tokens, icons, shell, shared controls.
   SHARED: every lane reads these. Changing a token is a coordination event.
   Loaded from index.html. See the ownership lanes in CLAUDE.md. */

/* ------------------------------------------------------------------ tokens */
:root {
  --bg-chrome: #f5f6f8;
  --bg-panel: #ffffff;
  --bg-stage: #e9ebef;
  --line: #dfe2e7;
  --line-strong: #c8ccd4;
  --ink: #16191d;
  --ink-soft: #6b7280;
  --accent: #3b6cf6;
  --danger: #d93a3a;
  /* The "surface, one step up/down" a plain control's own hover/active state
     reaches for — a button, a row, a chip — wherever that state used to be a
     bare hex baked against a white surface. Named for what they mean rather
     than which theme they're closer to, the same reason every token here is,
     so a lane's CSS never has to know which theme is active. */
  --bg-hover: #f2f4f7;
  --bg-active: #e8ebf0;
  /* A control's own "toggled on" wash — the same accent tint at low opacity
     wherever a plain hover/active shade would read as unselected instead. */
  --bg-accent-soft: #e4ebfb;
  --bg-danger-soft: #fdeeee;
  /* A ring drawn to separate an arbitrary colour (a swatch, a picker square)
     from the panel it sits on — has to read against any colour a user might
     pick, including near-black or near-white ones, so it is ink in light
     and paper in dark rather than one fixed shade doing both jobs. */
  --ring-inset: rgba(16, 20, 26, 0.18);
  /* The "no picture chosen yet" chequer (inspector.css's own .slidepic) — an
     empty-state placeholder tile, not the alpha checker a colour picker's own
     opacity rail draws (that one stays a fixed light/dark pair in both
     themes, the same universal "these pixels are transparent" convention
     every image tool uses, regardless of the app chrome around it). */
  --checker-a: #e4e6ea;
  --checker-b: #f6f7f9;
  /* The wash a transparency checker (an asset thumbnail's, not the colour
     picker's own fixed one above) paints between itself and the picture —
     dam.js/inspector-library.js's own .damtile__thumb--alpha and
     .assetlib__tile--alpha, plus dam.js's own .daminfo__preview--alpha.
     Darkens in light theme so a mostly-white or fully transparent picture
     doesn't wash out into --checker-b's own bright square; overridden to a
     *light* wash below for the same reason in reverse — --checker-a/b are
     already dark there, so more black on top of them crushed a dark
     picture's own edges into the checker instead of separating the two. */
  --alpha-wash: rgba(0, 0, 0, 0.3);
  --radius: 6px;
  --shadow-appbar: 0 1px 0 var(--line), 0 6px 16px rgba(16, 20, 26, 0.08);
  --radius-thumb: 16px;
  --radius-panel: 14px;
  /* Floating right panel: inset from the workspace edges, plus a soft shadow
     kept as tokens so the depth can be dialled without touching the rules. */
  --panel-float-inset: 12px;
  --shadow-panel:
    0 1px 2px rgba(16, 20, 26, 0.06),
    0 6px 16px rgba(16, 20, 26, 0.08),
    0 18px 40px rgba(16, 20, 26, 0.07);
  /* Breathing room between a floating panel and the artboard. */
  --stage-gutter: 28px;
  /* Snug to the slide list's own content (slides.css's own THUMB_WIDTH plus
     the num column, gap and .slidelist padding either side) rather than a
     rounder number with slack past it — the canvas is the thing to look at,
     so space this column doesn't need goes back to it (canvas.css's own
     stage padding and toolbar.css's own left inset both read this same
     token, so they shrink to match automatically). The list's right side is
     its scroll lane (below) plus the 2px the current slide's ring reaches
     past its thumbnail, not the plain 10px the left side has. */
  --panel-left: 202px;
  --panel-right: 268px;
  --toolbar-h: 42px;
  /* Every scrollbar (the ::-webkit-scrollbar rules below): a thumb with the
     same clear space either side of it, so it sits centred between a list's
     content and the panel's edge and never over either. A scrolling list
     reserves the gutter, and pads its own content by --scrollbar-inset (plus
     whatever a selection ring reaches past an item). That inset is 0: the
     thumb's own border already keeps it clear of the content, and has to be
     on both sides — see the thumb rule below. It stays a token so a list
     that wants more room than the border gives has one place to ask. */
  --scrollbar-thumb: 6px;
  --scrollbar-edge: 3px;
  --scrollbar-inset: 0px;
  --scrollbar-gutter: calc(var(--scrollbar-thumb) + 2 * var(--scrollbar-edge));
  /* Lucide draws on a 24x24 grid at stroke 2; keep both here so an icon can be
     resized without the weight drifting away from the rest of the set. */
  --icon-size: 16px;
  --icon-stroke: 2;
  /* Plain native keywords — the browser's own cursors throughout, reached by
     name (`cursor: var(--cursor-pointer)`) so a stylesheet never repeats a
     bare `cursor: pointer` in more than one place. */
  --cursor-default: default;
  --cursor-pointer: pointer;
  --cursor-grab: grab;
  --cursor-grabbing: grabbing;
  --cursor-text: text;
  --cursor-diagonal-1: nwse-resize;
  --cursor-diagonal-2: nesw-resize;
  --cursor-horizontal: ew-resize;
  --cursor-vertical: ns-resize;
  --cursor-crosshair: crosshair;
  --cursor-add: copy;
  /* The stage's own vendored set (assets/cursors/) — a plain keyword can't
     draw a directional rotate arrow or a stacked-cursor duplicate glyph, so
     these are images instead, each with a fallback keyword for a
     browser that can't load the url() at all. The hotspot (the two numbers
     after the url) is the pixel in each 24x24 SVG that is the actual point
     of the cursor — canvas.svg's and Duplicate.svg's own arrow tip, the
     rotate glyphs' shared centre — not the image's own corner. */
  --cursor-canvas: url(../../assets/cursors/canvas.svg) 6 5, default;
  /* A speech bubble whose one sharp corner, bottom left, is the point — the
     same corner a placed pin lands on its click with. Drawn at 32px rather
     than the set's 24, so its hotspot is in 32px terms too. */
  --cursor-comment: url(../../assets/cursors/comment.svg) 8 24, copy;
  --cursor-duplicate: url(../../assets/cursors/Duplicate.svg) 3 5, copy;
  --cursor-rotate-nw: url(../../assets/cursors/rotate-top-left.svg) 12 12, alias;
  --cursor-rotate-ne: url(../../assets/cursors/rotate-top-right.svg) 12 12, alias;
  --cursor-rotate-se: url(../../assets/cursors/rotate-bottom-right.svg) 12 12, alias;
  --cursor-rotate-sw: url(../../assets/cursors/rotate-bottom-left.svg) 12 12, alias;
  color-scheme: light;
}

/* Dark theme: an app-wide preference (src/theme.js), never part of the deck
   itself — it flips the *chrome* these tokens paint, and nothing more. A
   slide's own background, fills and text keep the colours their author gave
   them regardless: those come from render.js reading the deck's own model,
   not from these tokens, so nothing here can reach the canvas or its
   objects. `[data-theme="dark"]` on <html> is what src/theme.js sets;
   `color-scheme: dark` alongside it is what gets native form controls,
   scrollbars, etc. drawn dark to match rather than fighting the chrome
   around them. Same token names throughout the app, still — a lane's CSS
   never has to know which theme is active, only which token means what it
   paints with. */
:root[data-theme="dark"] {
  --bg-chrome: #1c1e22;
  --bg-panel: #26282d;
  --bg-stage: #16171a;
  --line: #35383e;
  --line-strong: #494d55;
  --ink: #e8e9ec;
  --ink-soft: #9a9fa8;
  --accent: #5b8aff;
  --danger: #f0554f;
  --bg-hover: #303338;
  --bg-active: #3a3e45;
  --bg-accent-soft: #2a3550;
  --bg-danger-soft: #4a2626;
  --ring-inset: rgba(255, 255, 255, 0.18);
  --checker-a: #2c2f34;
  --checker-b: #24262a;
  --alpha-wash: rgba(255, 255, 255, 0.15);
  --shadow-panel:
    0 1px 2px rgba(0, 0, 0, 0.3),
    0 6px 16px rgba(0, 0, 0, 0.32),
    0 18px 40px rgba(0, 0, 0, 0.28);
  --shadow-appbar: 0 1px 0 var(--line), 0 6px 16px rgba(0, 0, 0, 0.35);
  color-scheme: dark;
}

* { box-sizing: border-box; }

/* The pointer itself, everywhere — set on the root so everything inherits it
   and any control that wants another simply says so. The browser has its own
   rules for a field and a menu, and those beat inheritance, so each is named
   here too: without them the app's own default would show over the browser's
   native one the moment the pointer crossed an input. A colour well and a
   range are pressed, not typed into, so they keep what their own rules give
   them. A checkbox is pressed too, the same reason — excluded from the plain
   text-input rule below and given the plain arrow explicitly, rather than
   left to inherit it: nothing is ever typed into one, so the text cursor
   the bare `input` selector would otherwise catch it under reads as though
   it were a field, not a toggle. */
html { cursor: var(--cursor-default); }
input:not([type="color"]):not([type="range"]):not([type="checkbox"]), textarea { cursor: var(--cursor-text); }
input[type="checkbox"] { cursor: var(--cursor-default); }
select { cursor: var(--cursor-pointer); }
label { cursor: var(--cursor-default); }

/* The chrome is not a document to read from, so nothing in it is selectable:
   a press that travels — a drag that started on a panel, a double-click that
   landed on a label rather than the control beside it, a marquee begun just
   off the artboard — used to leave a run of highlighted interface behind it,
   which is never what was meant and has to be clicked away. Set on the root
   so it inherits everywhere, the same way the pointer above does, and named
   back on in the same three places `theme.js` already treats as "somewhere
   text is being written": a field, and anything actually editable — the
   canvas's own text edit and a comment composer both being a contenteditable
   div. A deck's own copy is never selected this way either; editing it is
   what the canvas's double-click is for. Both halves carry the -webkit-
   prefix, since Safari still reads that one and would otherwise leave the
   exemptions below with nothing to opt back out of. */
:root {
  -webkit-user-select: none;
  user-select: none;
}
input,
textarea,
[contenteditable="true"] {
  -webkit-user-select: text;
  user-select: text;
}

/* A control that sets its own `display` outranks the browser's rule for
   [hidden], and then hiding it does nothing. Say it once, here. */
[hidden] { display: none !important; }

html, body {
  height: 100%;
  margin: 0;
  /* The inspector slides out past the viewport edge (see inspector.css);
     transformed content still grows scrollable overflow even though it's
     invisible, so without this the page gains a horizontal scrollbar. */
  overflow-x: hidden;
  /* The chrome is set in the brand face too, so the interface and the deck
     it makes are not speaking in two different voices. */
  font-family: Figtree, system-ui, -apple-system, "Segoe UI", Helvetica, Arial, sans-serif;
  font-size: 13px;
  color: var(--ink);
  background: var(--bg-chrome);
  -webkit-font-smoothing: antialiased;
}

/* ------------------------------------------------------------------ icons */
/* Every icon is a Lucide glyph on a 24x24 viewBox (see src/icons.js). Size and
   weight are CSS only, so a caller overrides --icon-size on the button. */
.icon {
  width: var(--icon-size);
  height: var(--icon-size);
  flex: 0 0 auto;
  fill: none;
  stroke: currentColor;
  stroke-width: var(--icon-stroke);
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* ------------------------------------------------------------------ shell */
/* Grey so the toolbar's floating card (see toolbar.css) shows its inset
   margin against something other than plain white. There is no separate
   header row above the workspace any more — .workspace is `.app`'s only
   child now, and fills it edge to edge on its own via `flex: 1` below,
   the same as it always did; only the sibling that used to share this
   column with it is gone. */
.app { display: flex; flex-direction: column; height: 100%; background: var(--bg-stage); }

.brand { font-weight: 600; letter-spacing: -0.01em; font-size: 14px; }
.brand__icon { display: block; width: 40px; height: 40px; }
/* The topbar's own brand mark doubles as the way back to the homepage — a
   plain link, not a button, since it goes somewhere rather than doing
   something to the deck open underneath it. */
.brand__link { display: flex; align-items: center; gap: 8px; text-decoration: none; color: inherit; }

/* The header every page outside the editor shares (app-bar.js). Three
   columns: the actions keep their natural width (never wrapping a button's
   label), and the lead takes whatever is left, ellipsizing a long title
   rather than pushing the actions off the edge. */
.appbar {
  /* Stays at the top while the page scrolls; on pages where only a panel
     below it scrolls, this changes nothing. Positioned with a z-index so
     its shadow paints over the content rather than under it. */
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  gap: 16px;
  height: 64px;
  flex: 0 0 64px;
  padding: 0 32px;
  background: var(--bg-panel);
  transition: box-shadow 0.15s ease;
}
/* Content has scrolled under the bar (app-bar.js decides): a shadow keeps
   the two apart. */
.appbar.is-scrolled { box-shadow: var(--shadow-appbar); }
.appbar__lead { flex: 1 1 0; min-width: 0; display: flex; align-items: center; gap: 8px; }
.appbar__center { flex: 0 1 360px; min-width: 0; }
.appbar__center:empty { display: none; }
.appbar__actions { flex: 1 0 auto; display: flex; align-items: center; justify-content: flex-end; gap: 10px; }
.appbar__title {
  min-width: 0;
  margin: 0;
  font-size: 18px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* The logo's own 40px square, so a page's title sits exactly where the
   homepage's does. */
.appbar__back {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 40px;
  height: 40px;
  border-radius: var(--radius);
  color: var(--ink-soft);
  cursor: var(--cursor-pointer);
}
.appbar__back:hover { background: var(--bg-hover); color: var(--ink); }
.appbar__back:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* One stacking context holding two layers: the canvas fills it, and the panels
   float over it. Not columns — a column would squeeze the canvas between them,
   so a zoomed canvas would be clipped at their edges instead of passing
   underneath. Nothing here clips a shadow: the panels are siblings of the
   stage, not children of it. */
.workspace { position: relative; flex: 1; min-height: 0; background: var(--bg-stage); }

.panel {
  position: absolute;
  top: 0;
  bottom: var(--panel-float-inset);
  z-index: 2; /* above the canvas layer, whatever the DOM order */
  background: var(--bg-panel);
  display: flex;
  flex-direction: column;
  min-height: 0;
  border-radius: var(--radius-panel);
  box-shadow: var(--shadow-panel);
  overflow: hidden; /* so the head rule and the scrolling body clip to the radius */
}
/* The slide list floats directly on the workspace: no card shape of its own
   (no radius, no shadow, no inset) — the thumbnails are chrome enough — but
   painted white now, the same panel colour the inspector uses, rather than
   the workspace's own grey: two flush side panels in two different colours
   read as one being unfinished, not as a deliberate difference. `overflow`
   stays visible so the thumbnails' own shadows are not clipped at the
   column's edge — the list inside does its own scrolling. */
.panel--left {
  /* Flush to the top, left and bottom of the window itself (top inherited
     from .panel's own top: 0 above) — unlike every other floating panel,
     this one has no card of its own (no radius, no shadow, no inset) to
     float *as*, so there is nothing an edge gap would be protecting. Only
     its right edge stops short, at the hairline below that reads it against
     the canvas. */
  left: 0;
  bottom: 0;
  /* canvas.css's own stage padding and toolbar.css's own left inset both
     still read --panel-left and --panel-float-inset as one combined
     reservation, so absorbing the dropped `left` inset into the width here
     is what keeps this column's own right edge exactly where those two
     already expect it, rather than opening a gap between it and the canvas. */
  width: calc(var(--panel-left) + var(--panel-float-inset));
  background: var(--bg-panel);
  border-radius: 0;
  box-shadow: none;
  overflow: visible;
  /* The same stage default (canvas.css's own .stage) rather than the plain
     browser arrow — a thumbnail, a layer row and "New slide" all set their
     own pointer cursor directly, which still wins over this inherited one,
     so only the column's own background picks it up. */
  cursor: var(--cursor-canvas);
}
/* The one edge this chrome-less column has: with no card of its own —
   no fill, no shadow — there is nothing else to tell it apart from the stage
   behind it. Anchored to the panel's own box, not the viewport, so it runs
   exactly as tall as the column does, full height included. */
.panel--left::after {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  right: 0;
  width: 1px;
  background: var(--line);
}
/* Slides in from off-screen right / out past it. The empty (hidden) state is
   inspector.css's — this is just the resting state and the timing both share,
   so showing and hiding move at the same speed. Flush to the top, right and
   bottom of the window the same reason .panel--left is flush to its own
   three outer edges: only the inner edge, facing the canvas, is worth a
   seam — the other three are the window's own, and a floating card's inset,
   radius and shadow were only ever there to read as a card against a canvas
   that runs behind it on every side, which isn't true of a window edge. */
.panel--right {
  /* Flush to the top (inherited from .panel's own top: 0 above), same as
     .panel--left — there used to be a header row above the workspace this
     reached up past to reach the actual top of the window; with that row
     gone, the workspace's own top edge already is the window's, so there is
     nothing left to reach past. */
  right: 0;
  bottom: 0;
  /* canvas.css's own stage padding and toolbar.css's own right inset both
     still read --panel-right and --panel-float-inset as one combined
     reservation (see .panel--left's own width for the same move on the
     other side), so folding the dropped `right` inset into the width here
     is what keeps this column's own left edge exactly where those two
     already expect it, rather than opening a gap between it and the canvas. */
  width: calc(var(--panel-right) + var(--panel-float-inset));
  border-radius: 0;
  box-shadow: none;
  overflow: visible;
  transform: translateX(0);
  opacity: 1;
  visibility: visible;
  transition: transform 0.2s ease, opacity 0.2s ease, visibility 0s linear 0s;
}
/* The one edge this now-flush panel still needs told apart from the canvas
   behind it — the mirror of .panel--left::after on the opposite side. */
.panel--right::before {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 1px;
  background: var(--line);
}
.panel[hidden] { display: none; }

.panel__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  height: 36px;
  flex: 0 0 36px;
  padding: 0 8px 0 12px;
  border-bottom: 1px solid var(--line);
}
.panel__title { font-weight: 600; font-size: 12px; letter-spacing: 0.02em; text-transform: uppercase; color: var(--ink-soft); }
.panel__body { overflow-y: auto; padding: 4px 0 24px; }

/* Every scrollbar in the app, the way macOS draws an overlay one: a bare
   thumb, no track. Global rather than per list, so a page or panel added
   later reads the same without restyling its own — a sidebar list only
   reserves the room (see --scrollbar-gutter above). It began scoped to the
   editor's .panel sidebars; the other pages kept the browser's own bar,
   which read as a different app.

   The clear space is a transparent border on every side of the thumb, not
   just the side facing the edge. Chrome scales a 999px radius down to fit
   the thumb's full box, then takes each border's width off that side's
   curve — so a border on one side alone leaves that end of the pill
   squared off, which reads as the thumb being clipped. Equal borders leave
   an exact half-circle at both ends. Chrome ignores these
   pseudo-elements once the standard properties are set, so those are kept
   to browsers that have no pseudo-elements to style. */
*::-webkit-scrollbar {
  width: var(--scrollbar-gutter);
  height: var(--scrollbar-gutter);
  background: transparent;
}
*::-webkit-scrollbar-track,
*::-webkit-scrollbar-corner { background: transparent; }
*::-webkit-scrollbar-thumb {
  background-color: color-mix(in srgb, var(--ink) 22%, transparent);
  background-clip: padding-box;
  border: var(--scrollbar-edge) solid transparent;
  border-radius: 999px;
}
*::-webkit-scrollbar-thumb:hover { background-color: color-mix(in srgb, var(--ink) 38%, transparent); }
@supports not selector(::-webkit-scrollbar) {
  * {
    scrollbar-width: thin;
    scrollbar-color: color-mix(in srgb, var(--ink) 22%, transparent) transparent;
  }
}

/* ------------------------------------------------------------------ controls */
.btn {
  font: inherit;
  height: 28px;
  padding: 0 10px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  background: var(--bg-panel);
  color: var(--ink);
  cursor: var(--cursor-pointer);
}
.btn:hover { background: var(--bg-hover); }
.btn:active { background: var(--bg-active); }
.btn--icon { width: 28px; padding: 0; display: inline-grid; place-items: center; border-color: transparent; }
.btn--icon .icon { --icon-size: 15px; }
.btn--icon:hover { border-color: var(--line-strong); }
.btn--danger:hover { color: var(--danger); }

.input {
  font: inherit;
  height: 28px;
  padding: 0 8px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  background: var(--bg-panel);
  color: var(--ink);
}
.input:focus, .btn:focus-visible { outline: 2px solid var(--accent); outline-offset: -1px; }
.input--num { width: 68px; text-align: center; }

/* ------------------------------------------------------------- popover */
/* One drop-down shape, shared: menu.js mounts these on <body> for whichever
   panel asked, so the rules cannot live in one panel's sheet. */
/* -------------------------------------------------------------- palette */
/* Above every other UI layer this app stacks, including the comment
   composer's own 1000 (comments.css) — a menu opened from *inside* one of
   those (the comment mode toolbar's Emoji/Mention tools, most concretely)
   still has to paint over whatever raised it, not under it. */
.palette {
  position: fixed;
  z-index: 1001;
  padding: 8px;
  border-radius: 10px;
  background: var(--bg-panel);
  box-shadow: var(--shadow-panel);
}
