/* ----------------------------------------------------------------- fonts */

/* Inter, self-hosted: 130 KB for the latin subsets, one variable file covering
   400-700. No CDN, so the wall keeps its type with the network unplugged. */
@font-face {
  font-family: 'InterVariable';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('../fonts/inter-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'InterVariable';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('../fonts/inter-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* ==========================================================================
   Xars — a wall for pictures and the things they remind you of
   Frosted glass floating over a full-bleed wall. The pictures are the
   interface; every panel blurs out of the way.
   Themes: paper (warm daylight) · dusk (warm dark) · bloom (soft rose)
   ========================================================================== */

/* --------------------------------------------------------------- themes */

/* ------------------------------------------------------------------- theme */

/* One theme: black. Glass is defined by what it transmits, so the wall carries
   a fine dot field — it gives the surface depth, and gives the lens something
   with real detail to bend wherever the chrome floats over empty space. */
:root {
  --wall-1:      #0a0a0c;
  --wall-2:      #0e0e11;
  --wall-3:      #121116;
  --wall-4:      #07070a;
  --dot:         rgba(255, 255, 255, .085);
  --dot-size:    22px;

  --ink:         #f1f1f3;
  --ink-2:       #a8a8b1;
  --ink-3:       #6e6e78;
  --line:        rgba(255, 255, 255, .10);
  --line-soft:   rgba(255, 255, 255, .06);
  --line-strong: rgba(255, 255, 255, .2);

  --accent:      #e0879a;
  --accent-hi:   #f2a9b8;
  --accent-soft: rgba(224, 135, 154, .15);
  --rose:        #e0879a;
  --sage:        #8fbfae;
  --amber:       #e0b070;
  --plum:        #b294c4;

  /* Glass picks up the colour behind it and is lit only softly: the highlight
     ramps down to a whisper instead of ringing the shape in white, because a
     bright edge over empty dark reads as a drawn outline, not a material. */
  --glass-bg:     linear-gradient(160deg, rgba(255,255,255,.062), rgba(255,255,255,.02));
  --glass-bg-2:   linear-gradient(160deg, rgba(30,30,35,.88), rgba(18,18,22,.8));
  --glass-line:   rgba(255, 255, 255, .1);
  --glass-edge:   rgba(0, 0, 0, .22);      /* kept for the fallback path only */
  --glass-blur:   2px;
  --glass-shadow: 0 10px 26px rgba(0,0,0,.5), 0 2px 8px rgba(0,0,0,.34);
  --glass-inset:  inset 0 1px 0 rgba(255,255,255,.06);
  --rim-hi:       rgba(255, 255, 255, .17);
  --rim-mid:      rgba(255, 255, 255, .05);
  --rim-lo:       rgba(255, 255, 255, .08);
  --lens-bg:      linear-gradient(160deg, rgba(255,255,255,.15), rgba(255,255,255,.05));
  --lens-shadow:  0 4px 14px rgba(0,0,0,.45), inset 0 1px 0 rgba(255,255,255,.14);
  --sheen:        radial-gradient(118% 100% at 24% -22%, rgba(255,255,255,.11), rgba(255,255,255,0) 60%);
  --thick-top:    rgba(255, 255, 255, .07);
  --thick-bottom: rgba(0, 0, 0, .42);
  --note-glass:   linear-gradient(160deg, rgba(22,22,28,.36), rgba(12,12,17,.22));
  --note-ink:     #eae8e4;
  --frame-tint:   rgba(255, 255, 255, .03);
  --danger:       #e08b8b;
  --ok:           #8fbfae;

  --font-display: "InterVariable", "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
  --font-body:    "InterVariable", "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
  --font-mono:    "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

  --r-sm: 9px; --r-md: 13px; --r-lg: 18px; --r-xl: 24px; --r-pill: 999px;
  --ease: cubic-bezier(.22, .68, .24, 1);
  --grain: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='140' height='140'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/></filter><rect width='140' height='140' filter='url(%23n)' opacity='.35'/></svg>");
  --grain-opacity: .15;
  --grain-blend: screen;
  --shadow-1: 0 1px 3px rgba(0,0,0,.4);
  --shadow-2: 0 12px 30px rgba(0,0,0,.5);
  --shadow-pop: 0 26px 64px rgba(0,0,0,.66);

  /* names the shelf and older inline styles still use */
  --bg: var(--wall-1);
  --surface: #15151a;
  --surface-2: #1c1c22;
  --surface-3: #26262e;
  --fg: var(--ink);
  --fg-dim: var(--ink-2);
  --fg-faint: var(--ink-3);
  --accent-line: var(--accent);
  --mono: var(--font-mono);
  --panel-3: var(--surface-3);
  --note: var(--amber);
  --note-bg: rgba(24, 24, 29, .72);
  --note-line: rgba(255, 255, 255, .1);
}

/* ------------------------------------------------------------- foundations */

* { box-sizing: border-box; }
html, body { height: 100%; }
body {
  margin: 0;
  font-family: var(--font-body);
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--ink);
  background: var(--wall-1);
  overflow: hidden;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
button { font: inherit; color: inherit; background: none; border: 0; padding: 0; cursor: pointer; }
input, select, textarea { font: inherit; color: inherit; }
kbd {
  font-family: var(--font-mono); font-size: 10.5px; padding: 1px 5px;
  border-radius: 5px; background: var(--accent-soft); color: var(--accent-hi);
}
.sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }

/* ------------------------------------------------------------------- glass */

/* Liquid glass: a translucent lens with a gradient rim light and a specular
   sheen. Rim and sheen are static paints (no animation on the blurred element
   itself) so the compositor never has to re-blur a moving backdrop. */
.glass {
  position: absolute;
  z-index: 30;
  background: var(--glass-bg);
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(2) brightness(1.02);
  backdrop-filter: blur(var(--glass-blur)) saturate(2) brightness(1.02);
  border-radius: var(--r-pill);
  /* No hard ring. A 1px near-black outline is invisible on the black wall and
     reads as a drawn edge the moment something bright is behind it (a playing
     video on a phone was the report). Depth comes from the soft shadow, the lit
     top edge and the dark underside instead. */
  box-shadow: var(--glass-shadow), inset 0 1px 0 rgba(255, 255, 255, .09), inset 0 -1px 0 rgba(0, 0, 0, .34);
  color: var(--ink);
  isolation: isolate;
}
/* Real refraction: the backdrop is bent, not only blurred. Static surfaces only
   - a gesture drops straight back to blur (html.is-interacting). */
@supports (backdrop-filter: url(#lg-refract)) {
  .glass {
    -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(2) brightness(1.02) url(#lg-refract);
    backdrop-filter: blur(var(--glass-blur)) saturate(2) brightness(1.02) url(#lg-refract);
  }
}
html.is-interacting .glass {
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(2) brightness(1.02);
  backdrop-filter: blur(var(--glass-blur)) saturate(2) brightness(1.02);
}
/* Machines that held the frame budget with the lens on keep it for the whole
   gesture, so nothing about the glass moves when the pointer goes down. */
html.lens-always.is-interacting .glass,
html.lens-measure.is-interacting .glass { backdrop-filter: blur(var(--glass-blur)) saturate(2) brightness(1.02) url(#lg-refract); }
html.lens-always.is-interacting .item--note, html.lens-always.is-interacting .item--text,
html.lens-measure.is-interacting .item--note, html.lens-measure.is-interacting .item--text { backdrop-filter: blur(14px) saturate(1.5) brightness(1.04) url(#lg-refract-soft); }
.glass::before {
  content: ''; position: absolute; inset: 0; border-radius: inherit; padding: 1.5px;
  background: linear-gradient(150deg,
    var(--rim-hi) 0%, var(--rim-mid) 34%, rgba(255,255,255,0) 58%, var(--rim-lo) 100%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
  pointer-events: none;
}
/* the soft bloom just inside the top edge: the cue that the surface curves */
.glass::after {
  content: ''; position: absolute; inset: 0; border-radius: inherit;
  background: var(--sheen); pointer-events: none;
  box-shadow:
    inset 0 4px 12px -5px rgba(255, 255, 255, .9),
    inset 0 -4px 12px -5px rgba(70, 50, 35, .45),
    inset 0 -1px 0 rgba(130, 150, 255, .14);
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .glass { background: var(--glass-bg-2); }
}

/* The travelling lens that marks the active control inside a dock. It is a
   child of an already-blurred panel, so moving it costs one transform. */
.lens {
  position: absolute; top: 0; left: 0; z-index: 0;
  border-radius: var(--r-pill);
  background: var(--lens-bg);
  box-shadow: var(--lens-shadow);
  opacity: 0;
  transition: transform .26s var(--ease), width .26s var(--ease), height .26s var(--ease), opacity .2s var(--ease);
  pointer-events: none;
}
.glass > * { position: relative; z-index: 1; }
.glass > .lens { position: absolute; top: 0; left: 0; z-index: 0; }

/* ------------------------------------------------------------------- stage */

.stage { position: fixed; inset: 0; }

.float { display: flex; align-items: center; gap: 2px; padding: 6px; }

/* page title: centred, on its own, nothing else up there */
.float--title {
  top: calc(14px + env(safe-area-inset-top)); left: 50%; transform: translateX(-50%);
  padding: 7px 14px; gap: 9px; max-width: min(76vw, 620px);
}

/* the island: brand + the few numbers worth knowing, dead centre at the bottom */
.float--island {
  bottom: calc(16px + env(safe-area-inset-bottom)); left: 50%; transform: translateX(-50%);
  padding: 8px 16px; gap: 14px;
}
.island__brand { display: flex; align-items: center; gap: 8px; }
.island__name { font-family: var(--font-display); font-weight: 560; font-size: 15px; color: var(--ink); letter-spacing: -.012em; }
.island__rule { width: 1px; height: 20px; background: var(--line); }
.island__stat { display: flex; align-items: center; gap: 6px; font-size: 12.5px; color: var(--ink-2); white-space: nowrap; }
.island__stat svg { width: 14px; height: 14px; color: var(--ink-2); }
.island__stat b { font-weight: 600; color: var(--ink); font-variant-numeric: tabular-nums; }
/* The `hidden` attribute loses to a class that sets display, so the view-only
   badge showed on every page, editable ones included. */
.island__stat[hidden] { display: none; }
.island__stat--muted b { font-weight: 500; color: var(--ink-2); }
.island__dwell { display: flex; align-items: center; gap: 6px; }

.float--tools {
  left: calc(14px + env(safe-area-inset-left)); top: 50%; transform: translateY(-50%);
  flex-direction: column; padding: 7px; gap: 1px;
}
.float--actions {
  top: calc(14px + env(safe-area-inset-top)); right: calc(14px + env(safe-area-inset-right));
  padding: 6px; gap: 4px;
}
.float--zoom {
  right: calc(14px + env(safe-area-inset-right)); bottom: calc(16px + env(safe-area-inset-bottom));
}
.float--vitals {
  left: calc(14px + env(safe-area-inset-left)); bottom: calc(16px + env(safe-area-inset-bottom));
  padding: 6px 12px; gap: 12px; font-size: 11.5px; color: var(--ink);
}
.vitals { display: flex; align-items: center; gap: 12px; }
.vitals__item { white-space: nowrap; }
.vitals__item:empty, .vitals__item + .vitals__item:empty { display: none; }
.vitals__mono { font-family: var(--font-mono); font-size: 10.5px; color: var(--ink-2); }
/* Coordinates are only worth showing while the pointer is actually on the wall. */
#cursorPos { opacity: 0; transition: opacity .18s var(--ease); }
.workspace:hover ~ .float--vitals #cursorPos { opacity: 1; }
#selInfo:empty { display: none; }
#renderedInfo:empty { display: none; }

/* --------------------------------------------------------------- workspace */

.workspace {
  position: absolute; inset: 0;
  overflow: hidden;
  /* Black with a fine dot field. The dots are not decoration: they are the
     high-frequency detail the lens bends, so the glass reads as glass even
     where it floats over empty wall. Static, so it costs one paint. */
  background-image:
    radial-gradient(circle at 50% -10%, rgba(255, 255, 255, .045), transparent 60%),
    radial-gradient(circle, var(--dot) 1.05px, transparent 1.5px),
    linear-gradient(180deg, var(--wall-1), var(--wall-4));
  background-size: 100% 100%, var(--dot-size) var(--dot-size), 100% 100%;
  background-repeat: no-repeat, repeat, no-repeat;
  touch-action: none;
}
.workspace__wash { position: absolute; inset: 0; pointer-events: none; }
.workspace::before {
  content: ''; position: absolute; inset: 0; pointer-events: none; z-index: 2;
  background-image: var(--grain);
  opacity: var(--grain-opacity);
  mix-blend-mode: var(--grain-blend);
}
.workspace.is-pan { cursor: grab; }
.workspace.is-panning { cursor: grabbing; user-select: none; }
/* ------------------------------------------------------------- GPU wall --- */
/* Pictures drawn by the graphics card. Sits under the items (DOM order), above
   the CSS wash, and takes no pointer events: all interaction stays with the DOM
   layer that already handles it. */
#glWall { position: absolute; inset: 0; width: 100%; height: 100%; display: block; pointer-events: none; }
#glWall[hidden] { display: none; }
/* a picture the GPU is drawing: its DOM copy must not paint a second time */
.gl-covered { visibility: hidden; }
/* the layer draws its own dot field, so the CSS one would be a wasted paint */
html.gl-wall .workspace { background-image: none; }   /* the layer draws its own dot field */
html.gl-wall .workspace__wash { display: none; }

.workspace.is-draw { cursor: crosshair; }
.workspace.is-text { cursor: text; }
.workspace.is-note { cursor: copy; }

.world { position: absolute; top: 0; left: 0; width: 0; height: 0; transform-origin: 0 0; }
/* Only promote the world while a gesture is actually running: a permanently
   promoted layer is what made text and pictures look soft at other zooms. */
.workspace.is-interacting .world { will-change: transform; }

.overlay { position: absolute; inset: 0; pointer-events: none; z-index: 10; }

/* ------------------------------------------------------------------ items */

.item { position: absolute; user-select: none; }
.item__media { display: block; width: 100%; height: 100%; object-fit: contain; pointer-events: none; }
/* a video is a control surface: clicking it plays or pauses. Dragging still
   moves it — the click handler ignores a click that followed a drag. */
.item--video > video.item__media { pointer-events: auto; cursor: pointer; }

.item--image, .item--video { border-radius: var(--r-md); }
/* resting hairline for photos only; a cut-out is not a card (see .item--cutout) */
.item--image::after, .item--video::after {
  content: ''; position: absolute; inset: 0; border-radius: inherit;
  box-shadow: inset 0 0 0 1px var(--line-soft);
  pointer-events: none;
}
.item--image, .item--video { box-shadow: 0 10px 26px rgba(0, 0, 0, .5); }

/* A cut-out (transparent png/gif) is not a card: no box, no hairline, no card
   shadow — the silhouette gets the shadow instead, so a sticker sits on the
   wall instead of floating inside an invisible rectangle. */
.item--cutout { box-shadow: none !important; border-radius: 0; }
.item--cutout::after { content: none; }
.item--cutout .item__media { filter: drop-shadow(0 8px 18px rgba(0, 0, 0, .6)); }
.item--cutout.is-selected { filter: none; }

/* notes: the same liquid glass as the chrome, sitting on the wall */
.item--note, .item--text {
  background: var(--note-glass);
  border-radius: var(--r-lg);
  -webkit-backdrop-filter: blur(14px) saturate(1.5) brightness(1.04);
  backdrop-filter: blur(14px) saturate(1.5) brightness(1.04);
  box-shadow: 0 18px 44px rgba(0, 0, 0, .55), inset 0 1px 0 rgba(255,255,255,.1), inset 0 -1px 0 var(--thick-bottom);
  overflow: hidden;
}
@supports (backdrop-filter: url(#lg-refract-soft)) {
  .item--note, .item--text {
    -webkit-backdrop-filter: blur(14px) saturate(1.5) brightness(1.04) url(#lg-refract-soft);
    backdrop-filter: blur(14px) saturate(1.5) brightness(1.04) url(#lg-refract-soft);
  }
}
html.is-interacting .item--note, html.is-interacting .item--text {
  -webkit-backdrop-filter: blur(14px) saturate(1.5) brightness(1.04);
  backdrop-filter: blur(14px) saturate(1.5) brightness(1.04);
}
.item--note::before, .item--text::before {
  content: ''; position: absolute; inset: 0; border-radius: inherit; padding: 1px;
  background: linear-gradient(150deg,
    var(--rim-hi) 0%, var(--rim-mid) 36%, rgba(255,255,255,0) 60%, var(--rim-lo) 100%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
  pointer-events: none;
}
.item--note::after, .item--text::after {
  content: ''; position: absolute; inset: 0; border-radius: inherit;
  background: var(--sheen); pointer-events: none;
}

/* state is an outer ring, so it never fights the glass or a cut-out */
.item.is-selected { box-shadow: 0 0 0 2px var(--accent), 0 12px 30px rgba(0, 0, 0, .5); }
.item.is-hover:not(.is-selected) { box-shadow: 0 0 0 1.5px var(--line-strong), 0 8px 20px rgba(0, 0, 0, .42); }
.item--cutout.is-selected, .item--cutout.is-hover { box-shadow: none !important; }

/* the words inside a glass card */
.item--note .item__text {
  font-family: var(--font-display);
  color: var(--note-ink);
  font-size: 14px;
  line-height: 1.45;
  letter-spacing: .002em;
  padding: 15px 16px 16px;
  white-space: pre-wrap; word-break: break-word;
  overflow: hidden;
}
.item--text .item__text {
  font-family: var(--font-display);
  color: var(--note-ink);
  line-height: 1.4;
  padding: 12px 14px;
  white-space: pre-wrap; word-break: break-word; overflow: hidden;
}
.item__text[contenteditable="true"] { outline: 0; cursor: text; }
.item--note:has(.item__text[contenteditable="true"]) { box-shadow: 0 0 0 2px var(--accent), 0 14px 34px rgba(70,48,34,.22); }
.item--text.is-empty .item__text::before { content: 'Text'; color: var(--ink-3); }
.item--note.is-empty .item__text::before { content: 'Write something…'; color: var(--ink-3); }

/* a note that is nothing but a link becomes the player itself */
.item--note .item__embed, .item--embed .item__embed { position: absolute; inset: 0; overflow: hidden; border-radius: inherit; }
.item--note .item__embed iframe, .item--embed .item__embed iframe {
  width: 100%; height: 100%; border: 0; display: block; background: rgba(0,0,0,.06);
  pointer-events: none;
}
/* selected = you can touch the player; not selected = you can drag the card */
.item--note.is-selected .item__embed iframe, .item--embed.is-selected .item__embed iframe { pointer-events: auto; }
/* Handed over by the badge: the page inside is scrollable and clickable, and the
   item itself is not draggable until you take it back. */
/* Above the frame: the badge sits inside the embed wrapper, and the iframe with
   inset:0 otherwise wins the hit test, so clicks on it went to the page instead. */

.item__embed-badge:hover { background: var(--accent-soft); }
.item--note.has-embed .item__text {
  position: absolute; left: 0; right: 0; bottom: 0;
  font-size: 11.5px; padding: 7px 10px; background: var(--note-bg);
}
.item--note.has-embed .item__embed { bottom: 0; }
.item--note.has-caption .item__embed { bottom: 28px; }
/* A sibling of the frame wrapper, so this is the topmost element where it sits.
   Live means the page inside takes clicks and scroll; click again to take the
   item back under the cursor. */
.item__embed-badge {
  position: absolute; top: calc(8px * var(--inv, 1)); right: calc(8px * var(--inv, 1));
  z-index: 6; cursor: pointer; pointer-events: auto;
}
.item--embed.is-live .item__embed iframe, .item--note.has-embed.is-live .item__embed iframe { pointer-events: auto; }
.item__embed-badge.is-live { background: var(--accent-soft); color: var(--accent-hi); }

  position: absolute; top: 8px; left: 8px; z-index: 3;
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3px 8px; border-radius: var(--r-pill);
  background: rgba(20, 14, 12, .55); color: #fff; font-size: 10.5px;
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
  pointer-events: none;
}
.item__embed-badge svg { width: 12px; height: 12px; }
.item__embed-badge:hover { background: var(--accent-soft); }

.item--drawing svg { overflow: visible; }
.item--drawing .shape { pointer-events: none; }
.item--drawing .hit { fill: none; stroke: transparent; stroke-width: 16; pointer-events: stroke; }
.item--drawing .fillhit { fill: transparent; pointer-events: all; }

.item--frame {
  border: 1.5px dashed var(--line-strong);
  border-radius: var(--r-lg);
  background: var(--frame-tint);
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
}
.frame__label {
  position: absolute; top: -13px; left: 12px;
  font-size: 11.5px; font-family: var(--font-display);
  padding: 2px 9px; border-radius: var(--r-pill);
  background: var(--glass-bg-2); border: 1px solid var(--glass-line);
  color: var(--ink-2); white-space: nowrap;
}
.item--drawing .hit, .item--drawing .fillhit { cursor: inherit; }

/* video transport, unveiled on hover or selection */
.vctl {
  /* The bar spans the picture exactly (its layout width follows the item, which
     is already scaled by the board zoom) while its *parts* are sized by 1/zoom,
     so the controls stay screen-sized at any zoom. Scaling the whole bar instead
     made it either stick out of narrow videos or cover only half of wide ones. */
  /* A floating pill, capped in *screen* pixels, centred on the picture. It used
     to be full-bleed to the item's edges, so a video shown large (1408px wide)
     got a 1408px bar carrying 26px controls: the glyphs sat marooned against the
     rounded ends, ten pixels from the edge, and read as clipped and unfinished.
     Narrow videos still get the full width because the cap is a max, not a size. */
  position: absolute; left: 50%; bottom: 0; z-index: 4;
  transform: translateX(-50%);
  width: min(calc(100% - (20px * var(--inv, 1))), calc(520px * var(--inv, 1)));
  margin-bottom: calc(8px * var(--inv, 1));
  display: none; align-items: center;
  gap: calc(7px * var(--inv, 1));
  height: calc(38px * var(--inv, 1));
  padding: 0 calc(10px * var(--inv, 1));
  border-radius: var(--r-pill);
  background: linear-gradient(180deg, rgba(14, 12, 12, .46), rgba(10, 9, 9, .8));
  box-shadow: 0 2px calc(10px * var(--inv, 1)) rgba(0, 0, 0, .45);
  color: #fff;
}
.item--video.is-hover .vctl, .item--video.is-selected .vctl { display: flex; }
/* a paused video always offers a way to play it — hiding the transport until
   hover meant a video on a touch screen had no control at all */
.item--video:not(.is-playing) .vctl { display: flex; }
/* just touched or hovered: show the transport even while playing (touch has no hover) */
.item--video.is-controls .vctl { display: flex; }
/* too small to hold a bar: the play disc is the whole interface */
.item--video.is-small .vctl { display: none !important; }
/* too narrow for the full set: keep play, scrub and sound, drop the clock and
   the fullscreen button rather than squeezing everything into a sliver */
.item--video.is-compact .vctl__time, .item--video.is-compact [data-act="full"] { display: none; }
.vctl button {
  display: grid; place-items: center; flex: none;
  width: calc(26px * var(--inv, 1)); height: calc(26px * var(--inv, 1));
  border-radius: 50%; color: #fff;
}
.vctl button:hover { background: rgba(255, 255, 255, .18); }
.vctl svg {
  width: calc(15px * var(--inv, 1)); height: calc(15px * var(--inv, 1));
  filter: drop-shadow(0 1px 1px rgba(0, 0, 0, .55));
}
.vctl__track {
  flex: 1; min-width: calc(40px * var(--inv, 1));
  height: calc(5px * var(--inv, 1)); border-radius: calc(3px * var(--inv, 1));
  background: rgba(255, 255, 255, .3); cursor: pointer;
}
.vctl__fill { height: 100%; border-radius: inherit; background: #fff; width: 0; }
.vctl__time {
  font-family: var(--font-mono); font-size: calc(10.5px * var(--inv, 1));
  opacity: .92; white-space: nowrap; flex: none;
}
.vctl__play {
  position: absolute; left: 50%; top: 50%; z-index: 3;
  /* screen px (the scale above renders this 1:1), and proportional to the
     video's on-screen size so it never dwarfs the pictures around it */
  width: clamp(20px, calc(var(--vid-screen, 120) * 0.34px), 58px);
  height: clamp(20px, calc(var(--vid-screen, 120) * 0.34px), 58px);
  transform: translate(-50%, -50%) scale(var(--inv, 1));
  display: grid; place-items: center;
  background: none; border: 0; padding: 0; cursor: pointer;
}
.vctl__play svg {
  width: 100%; height: 100%; color: #fff; padding: 22%; box-sizing: border-box;
  border-radius: 50%;
  background: linear-gradient(160deg, rgba(255,255,255,.2), rgba(255,255,255,.07));
  border: 1px solid rgba(255,255,255,.22);
  box-shadow: 0 14px 34px rgba(0,0,0,.55), inset 0 1px 0 rgba(255,255,255,.3);
  -webkit-backdrop-filter: blur(5px) saturate(1.8); backdrop-filter: blur(5px) saturate(1.8);
  animation: playpulse 3.4s ease-in-out infinite;
}
@keyframes playpulse {
  0%, 100% { transform: scale(1); opacity: .96; }
  50% { transform: scale(1.06); opacity: 1; }
}
.item--video.is-playing .vctl__play { display: none; }
.item--video.is-selected .vctl__play { display: none; }

/* ------------------------------------------------------------------ chrome */

.selbox {
  position: absolute; border: 1.5px solid var(--accent); border-radius: var(--r-md);
  pointer-events: none;
  box-shadow: 0 0 0 1px rgba(255, 255, 255, .5), 0 6px 20px rgba(0, 0, 0, .45);
}
.selbox--group { border-style: dashed; }
/* Move mode is for looking and panning. A selection box and its handles there are
   noise — the selection is still held (the context menu needs it) but nothing is
   highlighted until you switch back to pick things up. */
body.is-move-mode .selbox { display: none !important; }
.chrome-badge {
  position: absolute; pointer-events: none; white-space: nowrap;
  padding: 3px 8px; border-radius: var(--r-pill);
  background: var(--glass-bg-2);
  -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
  border: 1px solid var(--glass-line);
  font-size: 10.5px; font-family: var(--font-mono); color: var(--ink-2);
  box-shadow: var(--shadow-1);
}
.handle {
  position: absolute; width: 11px; height: 11px;
  background: #14141a; border: 1.5px solid var(--accent); border-radius: 50%;
  pointer-events: auto;
  box-shadow: 0 1px 4px rgba(60, 40, 30, .3);
}
/* The turning grip is deliberately visible and comfortable to hit: the old one
   was a 9px square 18px above the frame, which read as a stray resize handle. */
.handle--rot {
  width: 22px; height: 22px; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--glass-bg-2);
  border: 1.5px solid var(--accent);
  box-shadow: 0 4px 12px rgba(60, 40, 30, .24), inset 0 1px 0 rgba(255, 255, 255, .75);
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
  cursor: grab;
}
.handle--rot:active { cursor: grabbing; }
.handle--rot svg { width: 12px; height: 12px; color: var(--accent); }
.workspace.is-rotating { cursor: grabbing; }
.marquee {
  position: absolute; border: 1px solid var(--accent); background: var(--accent-soft);
  border-radius: var(--r-sm); pointer-events: none;
}
.draw-preview { position: absolute; pointer-events: none; overflow: visible; }

.minimap {
  position: absolute; right: calc(16px + env(safe-area-inset-right)); bottom: calc(74px + env(safe-area-inset-bottom));
  z-index: 25; width: 168px; height: 110px;
  background: var(--glass-bg);
  -webkit-backdrop-filter: blur(14px); backdrop-filter: blur(14px);
  border: 1px solid var(--glass-line); border-radius: var(--r-lg);
  box-shadow: var(--glass-shadow);
  cursor: crosshair; overflow: hidden;
}
.minimap[hidden] { display: none; }
.minimap canvas { display: block; width: 100%; height: 100%; }

/* --------------------------------------------------------------- controls */

.tool {
  width: 38px; height: 38px; border-radius: 50%;
  display: grid; place-items: center;
  color: var(--ink); position: relative;
  transition: color .18s var(--ease), transform .18s var(--ease);
}
.tool:hover { color: var(--ink); transform: translateY(-1px); }
.tool.is-active { color: var(--accent-hi); }
.tool svg { width: 19px; height: 19px; }
.tool__key {
  position: absolute; right: 3px; bottom: 1px;
  font-size: 8px; color: var(--ink-3); font-family: var(--font-mono);
  opacity: 0; transition: opacity .16s var(--ease);
}
.tool:hover .tool__key { opacity: 1; }
.tool.is-active .tool__key { color: var(--accent-hi); opacity: 0; }
.tool.is-active:hover .tool__key { opacity: 1; }
.rail__sep { width: 20px; height: 1px; background: var(--line); opacity: .8; margin: 6px 0; }

.btn {
  display: inline-flex; align-items: center; gap: 7px;
  height: 34px; padding: 0 13px; border-radius: var(--r-pill);
  color: var(--ink); white-space: nowrap;
  transition: color .16s var(--ease), transform .16s var(--ease);
}
.btn:hover { color: var(--ink); transform: translateY(-1px); }
.btn svg { width: 15px; height: 15px; }
.btn--sm { height: 31px; padding: 0 11px; font-size: 12.5px; }
.btn--sm svg { width: 14px; height: 14px; }
.btn--icon { width: 34px; height: 34px; padding: 0; justify-content: center; border-radius: 50%; }
.btn--ghost { color: var(--ink); }
.btn--ghost:hover { color: var(--ink); }
.btn--primary {
  background: linear-gradient(150deg, var(--accent-hi), var(--accent));
  color: #fff; font-weight: 500; padding: 0 15px;
  box-shadow: 0 6px 16px rgba(140, 70, 80, .3), inset 0 1px 0 rgba(255,255,255,.34);
}
.btn--primary:hover { filter: brightness(1.05); }
.btn--danger { color: var(--danger); }
.btn[disabled] { opacity: .45; pointer-events: none; }
.zoomval { min-width: 52px; justify-content: center; font-family: var(--font-mono); font-size: 11.5px; }

.brand { display: grid; place-items: center; }
.brand__mark {
  display: grid; place-items: center; width: 26px; height: 26px;
  border-radius: 9px; color: #fff; font-family: var(--font-display); font-size: 15px;
  background: linear-gradient(145deg, var(--accent-hi), var(--accent));
  box-shadow: 0 3px 10px rgba(140, 70, 80, .3);
}
.doc { display: flex; align-items: center; gap: 8px; min-width: 0; }
.doc__name {
  font-family: var(--font-display); font-size: 15px; color: var(--ink);
  padding: 2px 4px; border-radius: var(--r-sm); cursor: text;
  max-width: 40vw; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.doc__name:hover { background: var(--accent-soft); }
.doc__name.is-editing {
  background: var(--glass-bg-2); outline: none;
  box-shadow: inset 0 0 0 1.5px var(--accent); color: var(--ink);
  cursor: text; min-width: 90px;
}
.doc__slug {
  font-family: var(--font-mono); font-size: 10.5px; color: var(--ink-3);
  padding: 3px 8px; border-radius: var(--r-pill); border: 1px solid var(--line-soft);
  white-space: nowrap; cursor: pointer;
}
.doc__slug:hover { color: var(--ink-2); border-color: var(--line); }
.presence { display: flex; align-items: center; }
.avatar {
  width: 22px; height: 22px; border-radius: 50%; margin-left: -6px;
  display: grid; place-items: center; font-size: 10px; font-weight: 600; color: #fff;
  border: 1.5px solid var(--glass-line);
}
.livedot { display: flex; align-items: center; gap: 5px; margin-left: 2px; }
.dot { width: 7px; height: 7px; border-radius: 50%; background: var(--ink-3); }
.dot.is-live { background: var(--ok); box-shadow: 0 0 0 3px rgba(95,145,121,.18); }
.dot.is-off { background: var(--danger); }

/* ------------------------------------------------------------------ drawer */

.drawer {
  top: calc(12px + env(safe-area-inset-top)); right: calc(12px + env(safe-area-inset-right));
  bottom: calc(12px + env(safe-area-inset-bottom));
  width: 356px; z-index: 40;
  display: flex; flex-direction: column;
  padding: 12px; border-radius: var(--r-xl);
  animation: drawer-in .26s var(--ease);
}
/* a panel full of text needs a heavier frost than the floating pills: clear
   glass over a photograph leaves small type unreadable */
#inspector {
  background: linear-gradient(160deg, rgba(14, 14, 19, .78), rgba(9, 9, 13, .66));
  -webkit-backdrop-filter: blur(22px) saturate(1.45) brightness(1.04);
  backdrop-filter: blur(22px) saturate(1.45) brightness(1.04);
}
#inspector .panel__empty { font-size: 13px; }
#inspector .noteitem__body { -webkit-line-clamp: 10; line-height: 1.62; }
.drawer[hidden] { display: none; }
@keyframes drawer-in { from { opacity: 0; transform: translateX(14px) scale(.99); } to { opacity: 1; transform: none; } }
.tabs { display: flex; align-items: center; gap: 3px; padding: 2px 2px 10px; position: relative; }
.tab {
  padding: 6px 11px; border-radius: var(--r-pill); font-size: 12.5px; color: var(--ink-2);
  transition: background .16s var(--ease), color .16s var(--ease);
}
.tab:hover { background: var(--accent-soft); color: var(--ink); }
.tab.is-active { background: var(--accent); color: #fff; }
.tab span { opacity: .7; font-size: 11px; }
.drawer__close { margin-left: auto; }
.panel { flex: 1; overflow: auto; padding: 2px; }
.panel[hidden] { display: none; }
.panel__empty { color: var(--ink-3); font-size: 12.5px; line-height: 1.7; padding: 8px 4px; }

.field { margin-bottom: 12px; }
.field__label { display: block; font-family: var(--font-display); font-style: italic; font-size: 12px; color: var(--ink-3); margin-bottom: 5px; }
.input, .select {
  width: 100%; height: 34px; padding: 0 11px;
  background: var(--glass-bg-2); border: 1px solid var(--line);
  border-radius: var(--r-sm); outline: none; color: var(--ink);
}
.input:focus, .select:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.row { display: flex; align-items: center; gap: 7px; }
.row--between { justify-content: space-between; }
.grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 7px; }
.sep { height: 1px; background: var(--line-soft); margin: 13px 0; }
.hint { font-size: 11.5px; color: var(--ink-3); line-height: 1.65; }
.kv { display: flex; justify-content: space-between; gap: 10px; padding: 5px 0; font-size: 12.5px; border-bottom: 1px solid var(--line-soft); }
.kv:last-child { border-bottom: 0; }
.kv__k { color: var(--ink-3); }
.kv__v { color: var(--ink-2); text-align: right; font-family: var(--font-mono); font-size: 11px; word-break: break-all; }
.swatch { display: flex; gap: 6px; flex-wrap: wrap; }
.swatch button { width: 22px; height: 22px; border-radius: 50%; box-shadow: inset 0 0 0 1px var(--line-strong); }
.swatch button.is-on { box-shadow: 0 0 0 2px var(--accent); }
.notelist { display: flex; flex-direction: column; gap: 7px; }
.noteitem {
  text-align: left; padding: 10px 12px; border-radius: var(--r-md);
  background: var(--glass-bg-2); border: 1px solid var(--line-soft);
  font-size: 12.5px; color: var(--ink-2);
  transition: border-color .16s var(--ease), transform .16s var(--ease);
}
.noteitem:hover { border-color: var(--line-strong); transform: translateY(-1px); }
.noteitem__text { font-family: var(--font-display); color: var(--ink); margin-bottom: 4px; }
.noteitem__meta { font-size: 10.5px; color: var(--ink-3); font-family: var(--font-mono); }

/* --------------------------------------------------------- menus & dialogs */

.menu {
  position: fixed; z-index: 150; min-width: 224px; padding: 6px;
  background: var(--glass-bg-2);
  -webkit-backdrop-filter: blur(20px) saturate(1.7); backdrop-filter: blur(20px) saturate(1.7);
  border: 1px solid var(--glass-line); border-radius: var(--r-lg);
  box-shadow: var(--glass-shadow);
  display: none;
}
.menu.is-open { display: block; animation: pop-in .16s var(--ease); }
@keyframes pop-in { from { opacity: 0; transform: translateY(-5px) scale(.985); } to { opacity: 1; transform: none; } }
.menu__item {
  display: flex; align-items: center; gap: 9px; width: 100%;
  padding: 8px 10px; border-radius: var(--r-sm); font-size: 12.5px; color: var(--ink);
}
.menu__item:hover { background: var(--accent-soft); }
.menu__item svg { width: 15px; height: 15px; color: var(--ink-2); }
.menu__item--danger { color: var(--danger); }
.menu__item--danger svg { color: var(--danger); }
.menu__title { padding: 7px 10px 4px; font-family: var(--font-display); font-style: italic; font-size: 12px; color: var(--ink-3); }
.menu__key { margin-left: auto; font-family: var(--font-mono); font-size: 10.5px; color: var(--ink-3); }
.menu__sep { height: 1px; background: var(--line-soft); margin: 5px 4px; }

.dialog { position: fixed; inset: 0; z-index: 170; display: grid; place-items: center; background: rgba(48, 34, 26, .34); }
.dialog[hidden] { display: none; }
/* same trap as the island badge: `.row { display: flex }` beats `[hidden]` */
.row[hidden] { display: none; }
.dialog__card {
  width: min(560px, calc(100vw - 40px)); max-height: calc(100vh - 80px);
  display: flex; flex-direction: column;
  background: var(--glass-bg-2);
  -webkit-backdrop-filter: blur(26px) saturate(1.7); backdrop-filter: blur(26px) saturate(1.7);
  border: 1px solid var(--glass-line); border-radius: var(--r-xl);
  box-shadow: var(--shadow-pop);
  animation: pop-in .2s var(--ease);
}
.dialog__head { padding: 16px 18px 10px; }
.dialog__title { font-family: var(--font-display); font-size: 17px; }
.dialog__sub { font-size: 12.5px; color: var(--ink-3); margin-top: 3px; }
.dialog__body { padding: 4px 18px 6px; overflow: auto; flex: 1; font-size: 12.5px; }
.dialog__foot { padding: 12px 18px 16px; display: flex; gap: 8px; justify-content: flex-end; }

.palette {
  position: fixed; left: 50%; top: 14%; transform: translateX(-50%);
  width: min(580px, calc(100vw - 32px)); z-index: 160;
  background: var(--glass-bg-2);
  -webkit-backdrop-filter: blur(26px) saturate(1.7); backdrop-filter: blur(26px) saturate(1.7);
  border: 1px solid var(--glass-line); border-radius: var(--r-xl);
  box-shadow: var(--shadow-pop); overflow: hidden;
}
.palette[hidden] { display: none; }
.palette__input {
  width: 100%; height: 50px; padding: 0 18px; border: 0; border-bottom: 1px solid var(--line-soft);
  background: transparent; font-size: 14.5px; outline: none;
}
.palette__list { max-height: 46vh; overflow: auto; padding: 6px; }
.palette__item, .palette__row { display: flex; align-items: center; gap: 10px; padding: 9px 11px; border-radius: var(--r-sm); font-size: 12.5px; width: 100%; cursor: pointer; }
.palette__item.is-on, .palette__row:hover { background: var(--accent-soft); }
.palette__item svg, .palette__row svg { width: 15px; height: 15px; color: var(--ink-2); flex: none; }
.palette__row span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.palette__key, .palette__tag { margin-left: auto; font-family: var(--font-mono); font-size: 10.5px; color: var(--ink-3); flex: none; }
.palette__group { padding: 9px 12px 3px; font-family: var(--font-display); font-style: italic; font-size: 12px; color: var(--ink-3); }

/* the notes list in the panel */
.notelist { display: flex; flex-direction: column; gap: 7px; }
.noteitem {
  text-align: left; padding: 10px 12px; border-radius: var(--r-md);
  background: var(--glass-bg-2); border: 1px solid var(--line-soft);
  font-size: 12.5px; color: var(--ink-2); display: block; width: 100%;
  transition: border-color .16s var(--ease), transform .16s var(--ease);
}
.noteitem:hover { border-color: var(--line-strong); transform: translateY(-1px); }
.noteitem__head { display: flex; align-items: center; gap: 6px; margin-bottom: 5px; }
.noteitem__dot { width: 6px; height: 6px; border-radius: 50%; display: inline-block; flex: none; }
.noteitem__author { font-family: var(--font-mono); font-size: 10px; color: var(--ink-3); }
.noteitem__body {
  font-family: var(--font-display); color: var(--ink); line-height: 1.5;
  display: -webkit-box; -webkit-line-clamp: 4; -webkit-box-orient: vertical; overflow: hidden;
}
.noteitem__text { font-family: var(--font-display); color: var(--ink); margin-bottom: 4px; }
.noteitem__meta { font-size: 10.5px; color: var(--ink-3); font-family: var(--font-mono); }

/* numeric fields sit two-up, so they need to be compact */
.input--num { padding: 0 8px; font-family: var(--font-mono); font-size: 12px; }
.range { width: 100%; accent-color: var(--accent); margin: 4px 0; }
.swatches { display: flex; gap: 6px; flex-wrap: wrap; }
.swatches .swatch { width: 22px; height: 22px; border-radius: 50%; box-shadow: inset 0 0 0 1px var(--line-strong); }
.swatches .swatch.is-on { box-shadow: 0 0 0 2px var(--accent); }
.item__meta { display: none; }
.item--note .item__text[contenteditable="true"]::before { content: none; }
.item--note.has-embed .item__text:empty { display: none; }

/* ---------------------------------------------------------------- feedback */

.toasts {
  position: fixed; left: calc(16px + env(safe-area-inset-left)); bottom: calc(62px + env(safe-area-inset-bottom));
  z-index: 240; display: flex; flex-direction: column; gap: 8px; align-items: flex-start;
}
.toast {
  display: flex; align-items: center; gap: 9px;
  background: var(--glass-bg-2);
  -webkit-backdrop-filter: blur(8px) saturate(1.6); backdrop-filter: blur(8px) saturate(1.6);
  border: 1px solid var(--glass-line); border-radius: var(--r-lg);
  box-shadow: var(--glass-shadow);
  padding: 10px 14px; font-size: 12.5px; max-width: 350px;
  animation: toast-in .2s var(--ease);
}
@keyframes toast-in { from { opacity: 0; transform: translateY(8px) scale(.98); } to { opacity: 1; transform: none; } }
.toast svg { width: 15px; height: 15px; color: var(--ok); flex: none; }
.toast--err { color: var(--danger); }
.toast--err svg { color: var(--danger); }

.tip {
  position: fixed; z-index: 200; pointer-events: none;
  background: var(--glass-bg-2); border: 1px solid var(--glass-line);
  border-radius: var(--r-pill); padding: 5px 11px; font-size: 12px;
  box-shadow: var(--glass-shadow); opacity: 0; transform: translateY(3px);
  transition: opacity .14s var(--ease), transform .14s var(--ease);
  -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px);
}
.tip.is-on { opacity: 1; transform: none; }

.empty {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
  z-index: 5; text-align: center; color: var(--ink-3); pointer-events: none;
  max-width: min(520px, 74vw); padding: 26px 30px;
  background: var(--glass-bg); border: 1px solid var(--glass-line);
  border-radius: var(--r-xl); box-shadow: var(--glass-shadow);
  -webkit-backdrop-filter: blur(16px); backdrop-filter: blur(16px);
}
.empty[hidden] { display: none; }
.empty strong { display: block; font-family: var(--font-display); font-size: 20px; color: var(--ink); margin-bottom: 7px; }

.droptarget {
  position: absolute; inset: 0; z-index: 20; display: none;
  place-items: center; background: rgba(120, 80, 60, .10);
  -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px);
}
.droptarget.is-on { display: grid; }
.droptarget__card {
  padding: 20px 26px; border-radius: var(--r-xl); text-align: center;
  background: var(--glass-bg-2); border: 1px dashed var(--accent);
  box-shadow: var(--glass-shadow);
}
.droptarget__card strong { display: block; font-family: var(--font-display); font-size: 17px; margin-bottom: 4px; }
.droptarget__card span { font-size: 12px; color: var(--ink-3); }

.progress__wrap {
  position: absolute; left: 50%; bottom: calc(24px + env(safe-area-inset-bottom));
  transform: translateX(-50%); z-index: 30; width: min(420px, 70vw);
  padding: 12px 15px; border-radius: var(--r-lg);
  background: var(--glass-bg-2); border: 1px solid var(--glass-line);
  box-shadow: var(--glass-shadow);
  -webkit-backdrop-filter: blur(16px); backdrop-filter: blur(16px);
}
.progress__wrap[hidden] { display: none; }
.progress__label { display: flex; justify-content: space-between; font-size: 12px; margin-bottom: 7px; color: var(--ink-2); }
.progress__bar { height: 5px; border-radius: 3px; background: var(--line-soft); overflow: hidden; }
.progress__bar i { display: block; height: 100%; width: 0; border-radius: 3px; background: linear-gradient(90deg, var(--accent-hi), var(--accent)); transition: width .2s var(--ease); }

/* -------------------------------------------------------------------- peer */

.peer { position: absolute; pointer-events: none; z-index: 12; transition: transform .08s linear; }
.peer__name {
  font-size: 11px; padding: 2px 8px; border-radius: var(--r-pill);
  background: rgba(20, 14, 12, .62); color: #fff; white-space: nowrap;
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
}

/* ------------------------------------------------- legibility over imagery
   The chrome is white, and glass transmits whatever it floats over: a bright
   picture behind the top row left a white icon with no edge at all, legible
   only where the wall behind it happened to be dark. The video transport hit
   this first and fixed it locally with a drop shadow on its glyphs (.vctl svg);
   this is that fix applied once, to every icon and every word that rides on
   glass, so the next report does not need a rule of its own. The shadow is
   soft and dark rather than an outline: an outline would trace every stroke and
   turn the icons into diagrams. */
.glass svg,
.menu__item svg, .palette__item svg, .palette__row svg,
.dialog__card svg, .toast svg, .tip svg,
.handle--rot svg, .item__embed-badge svg {
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, .55));
}
.glass :is(.island__name, .island__stat, .doc__name, .doc__slug, .tab, .btn__label, .zoomval),
.menu__item, .menu__title, .palette__item, .palette__row,
.toast, .tip, .chrome-badge, .frame__label, .peer__name,
.item__embed-badge span {
  text-shadow: 0 1px 2px rgba(0, 0, 0, .5);
}

/* ------------------------------------------------------------------ mobile */

@media (max-width: 860px) {
  :root { --glass-blur: 2px; }

  /* A centred title cannot share the top row with two pills, so on touch
     screens the zoom widget goes (pinch is the gesture anyway) and the actions
     pill keeps only Add and the panel. */
  .float--zoom { display: none; }
  .float--actions { padding: 5px; gap: 3px; }
  .float--actions #btnShare, .float--actions #btnDownload,
  .float--actions #btnPalette, .float--actions #btnTheme { display: none; }
  .doc__slug { display: none; }
  .doc__name { max-width: min(40vw, 200px); }
  .float--title { max-width: calc(100vw - 300px); padding: 6px 11px; gap: 7px; }

  /* bottom: tools centred, the island just above them */
  .float--tools {
    left: 50%; transform: translateX(-50%); top: auto;
    bottom: calc(12px + env(safe-area-inset-bottom));
    flex-direction: row; padding: 5px; gap: 0;
  }
  .tool { width: 38px; height: 38px; }
  .rail__sep { width: 1px; height: 20px; margin: 0 4px; }
  .float--island {
    bottom: calc(68px + env(safe-area-inset-bottom)); padding: 7px 13px; gap: 11px;
  }
  .float--island .island__stat:has(#statNotes) { display: none; }
  .float--vitals { display: none; }
  .minimap { display: none; }

  .drawer {
    left: calc(10px + env(safe-area-inset-left)); right: calc(10px + env(safe-area-inset-right));
    top: auto; bottom: calc(10px + env(safe-area-inset-bottom)); width: auto; max-height: 68vh;
    animation: sheet-in .26s var(--ease);
  }
  @keyframes sheet-in { from { opacity: 0; transform: translateY(18px); } to { opacity: 1; transform: none; } }
}
@media (max-width: 480px) {
  .btn__label { display: none; }                    /* Add becomes a + bubble */
  #presence { display: none; }                      /* the count lives in the island */
  .float--title { max-width: calc(100vw - 210px); }
  .float--island { padding: 6px 12px; gap: 9px; }
  .float--island .island__stat:has(#statPics) { display: none; }
}
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}


/* ------------------------------------------------------------ view-only access
   A viewer is given nothing the server would refuse. Previously every editor
   control stayed on screen and each move came back 403, retried forever behind
   a "Change did not save" toast. */
body.is-viewonly #rail .tool:not([data-tool="hand"]),
body.is-viewonly #rail .rail__sep,
body.is-viewonly #btnDrawColor,
body.is-viewonly #btnUpload { display: none !important; }
.island__stat--ro b { color: var(--amber); }
.island__stat--ro svg { color: var(--amber); }

/* A picture whose file is gone (deleted upload, or a locked page's media): show
   that it is missing instead of an empty frame that looks like a rendering bug. */
.item--missing { position: relative; }
.item--missing::after {
  content: 'picture unavailable';
  position: absolute; inset: auto 8px 8px 8px;
  font-size: 11px; letter-spacing: .04em; text-align: center;
  color: var(--ink-3); background: var(--glass-bg); border-radius: 6px; padding: 3px 6px;
}

/* ui.js writes dot--ok / dot--off; the sheet only knew is-live/is-off, so the
   live-connection dot never changed colour. Accept both. */
.livedot .dot--ok, .livedot .dot--off { transition: background .25s ease, box-shadow .25s ease; }
.livedot .dot--ok { background: var(--ok, #7ec98d); box-shadow: 0 0 0 3px rgba(126, 201, 141, .16); }
.livedot .dot--off { background: var(--danger); box-shadow: 0 0 0 3px rgba(224, 139, 139, .16); }
/* palette keyboard navigation sets is-on on .palette__row, which the sheet styled
   only for .palette__item — so the selection was invisible */
.palette__row.is-on, .palette__item.is-on { background: var(--accent-soft); color: var(--ink); }


/* ------------------------------------------------------------- small screens
   Measured on a 390x844 dpr3 viewport: the same cast shadow that reads as a soft
   lift on a desktop reads as a dark halo around a phone-sized pill, because the
   pill is smaller and sits over imagery; the rail's travelling lens lands beside
   the single remaining tool button instead of on it, leaving an empty chip; and
   the vitals readout and minimap are desktop affordances that only crowd a phone. */
@media (max-width: 560px) {
  :root {
    --glass-shadow: 0 5px 14px rgba(0, 0, 0, .34), 0 1px 4px rgba(0, 0, 0, .22);
    /* The phone is laid out from the bottom up. The bar is 44px targets inside
       6px of padding, 10px off the bottom edge, so --rail-h is its own height
       and --above-rail is where every other bottom layer starts: a thumb resting
       on one piece of chrome must never land on the next. The offsets below are
       all written in these two numbers, so changing the bar is one edit. */
    --rail-h: 56px;
    --above-rail: calc(18px + var(--rail-h));
  }
  #rail .lens { display: none; }
  #vitals, #minimap { display: none !important; }
  .float--island { max-width: calc(100vw - 24px); overflow: hidden; }
  .island__rule { display: none; }

  /* --- the tool rail is the bottom bar ------------------------------------
     38px is a mouse target: it fits a cursor and not a thumb. On a phone every
     tool gets 44px, the separators go (they cost 9px each and the row needs air
     between the tools more than it needs rules), and the bar scrolls sideways
     instead of overflowing on a 320px screen. */
  .float--tools {
    bottom: calc(10px + env(safe-area-inset-bottom));
    padding: 6px; gap: 4px;
    max-width: calc(100vw - 12px);
    overflow-x: auto;
    scrollbar-width: none;
  }
  .float--tools::-webkit-scrollbar { display: none; }
  .float--tools .tool { width: 44px; height: 44px; }
  .float--tools .tool svg { width: 21px; height: 21px; }
  .float--tools .rail__sep { display: none; }

  /* --- the island carries two numbers -------------------------------------
     Four counts plus a badge is a spreadsheet on a 360px strip, and the two
     per-kind counts are already listed in the panel's Page tab. What is left is
     the total on the wall and how many people are here. The bar sits under it
     at --above-rail, so the pill clears the tools with 8px of air. */
  .float--island { bottom: calc(var(--above-rail) + env(safe-area-inset-bottom)); }
  .float--island .island__stat:has(#statPics),
  .float--island .island__stat:has(#statNotes) { display: none; }

  /* --- the top row --------------------------------------------------------
     Five controls strung across the top of a phone is the desktop row with its
     arms cut off. Add and the panel stay (share is the page link in the panel,
     download and the palette are desk-bound; both are already gone at 860px) and
     their labels go too, because a word each is what pushed the pill into the
     title. With the pill icon-only, the title can leave the middle and hug the
     left: it gets everything the pill does not need, which at 360px is 220px of
     name instead of the 150px the centred box used to allow. */
  .float--title {
    left: calc(12px + env(safe-area-inset-left)); transform: none;
    max-width: calc(100vw - 140px);
  }
  .float--actions {
    top: calc(12px + env(safe-area-inset-top)); right: calc(12px + env(safe-area-inset-right));
    padding: 5px; gap: 4px;
  }
  .float--actions .btn { height: 40px; }
  .float--actions .btn--icon { width: 40px; height: 40px; }
  .float--actions .btn__label { display: none; }
  #btnShare, #btnDownload, #btnPalette { display: none; }

  /* --- the panel is a sheet at the bottom ---------------------------------
     It was a right-hand drawer squeezed onto a narrow screen; it becomes a
     sheet, and the sheet stops above the bar rather than over it, so the tools
     stay reachable while the panel is open. Its tabs and close button grow to
     touch size for the same reason the rail did. */
  .drawer {
    left: calc(12px + env(safe-area-inset-left));
    right: calc(12px + env(safe-area-inset-right));
    top: auto;
    bottom: calc(var(--above-rail) + env(safe-area-inset-bottom));
    padding: 10px;
  }
  .drawer .tab { min-height: 44px; display: inline-flex; align-items: center; padding: 0 13px; }
  .drawer .drawer__close { width: 44px; height: 44px; }
  /* The sheet lands over the island: a strip of numbers smearing through the
     glass under the panel is worse than no strip, and the panel already lists
     what the island counted. */
  body:has(#inspector:not([hidden])) .float--island { display: none; }

  /* --- feedback -----------------------------------------------------------
     The upload card and the toasts sat at desktop heights, which on a phone is
     straight through the tool bar. Both move into the band above the island,
     the only stretch of a phone's bottom edge with nothing in it. */
  .toasts {
    left: 12px; right: 12px;
    bottom: calc(var(--above-rail) + 52px + env(safe-area-inset-bottom));
    align-items: stretch;
  }
  .toast { max-width: none; }
  .progress__wrap {
    bottom: calc(var(--above-rail) + 52px + env(safe-area-inset-bottom));
    width: min(420px, calc(100vw - 24px));
  }
}
