/* purz.ai — portfolio grid
   No framework, no build step. Custom properties carry the theme. */

:root {
  --bg:          #0b0b0c;
  --bg-raise:    #141416;
  --fg:          #ececee;
  --fg-dim:      #8a8a92;
  --fg-faint:    #55555c;
  --line:        #232326;
  --accent:      #e8e8ea;
  --accent-ink:  #0b0b0c;

  --sidebar-w:   244px;
  --inspector-w: 280px;
  --gap:         10px;
  --pad:         16px;
  --radius:      3px;

  --font: ui-monospace, "SF Mono", "JetBrains Mono", Menlo, Consolas, monospace;
  --ease: cubic-bezier(.22,.61,.36,1);
}

@media (prefers-color-scheme: light) {
  :root {
    --bg:       #fbfbfa;
    --bg-raise: #ffffff;
    --fg:       #17171a;
    --fg-dim:   #6b6b74;
    --fg-faint: #a3a3ad;
    --line:     #e6e6e4;
    --accent:      #17171a;
    --accent-ink:  #ffffff;
  }
}

* { box-sizing: border-box; }

/* The `hidden` attribute only sets `display: none` in the UA stylesheet, so any
   author rule with an explicit display (grid/flex) silently beats it. Every
   element toggled via `.hidden = true` needs this or it stays on screen. */
[hidden] { display: none !important; }

html, body {
  margin: 0;
  height: 100dvh;
  min-height: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--font);
  font-size: 12px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

body { overflow: hidden; }

a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }

kbd {
  display: inline-block;
  min-width: 16px;
  padding: 1px 4px;
  border: 1px solid var(--line);
  border-radius: 2px;
  background: var(--bg-raise);
  color: var(--fg-dim);
  font-size: 10px;
  text-align: center;
}

/* ------------------------------------------------------------------ layout */

/* Every rung of this ladder needs `min-height: 0`.
   Flex and grid items default to `min-height: auto`, meaning they refuse to
   shrink below their content. Without it the `overflow-y: auto` on #scroller
   never engages -- the column just grows to fit all 1,600 tiles and the excess
   renders below the viewport. */
#app {
  display: grid;
  grid-template-columns: var(--sidebar-w) 1fr;
  grid-template-rows: 100%;
  height: 100dvh;
  min-height: 0;
  overflow: hidden;
  transition: grid-template-columns .28s var(--ease);
}
#app.no-sidebar { grid-template-columns: 0 1fr; }
#app > * { min-height: 0; }

/* ----------------------------------------------------------------- sidebar */

#sidebar {
  overflow: hidden;
  border-right: 1px solid var(--line);
  background: var(--bg);
}
.sidebar-inner {
  width: var(--sidebar-w);
  height: 100%;
  padding: var(--pad);
  overflow-y: auto;
  overflow-x: hidden;
  display: flex;
  flex-direction: column;
  gap: 18px;
  scrollbar-width: thin;
  scrollbar-color: var(--line) transparent;
}
#app.no-sidebar .sidebar-inner { opacity: 0; }
.sidebar-inner { transition: opacity .18s var(--ease); }

.brand-name {
  font-size: 15px;
  font-weight: 600;
  letter-spacing: -.02em;
}
.brand-sub {
  margin: 2px 0 0;
  color: var(--fg-faint);
  font-size: 11px;
}

.search input {
  width: 100%;
  padding: 6px 8px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--bg-raise);
  color: var(--fg);
  font: inherit;
  outline: none;
}
.search input:focus { border-color: var(--fg-faint); }
.search input::placeholder { color: var(--fg-faint); }

.active-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-bottom: 7px;
}
.active-label { color: var(--fg-faint); font-size: 10px; text-transform: uppercase; letter-spacing: .08em; }
.linkish {
  padding: 0;
  color: var(--fg-dim);
  font-size: 10px;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.linkish:hover { color: var(--fg); }

.tag-row { display: flex; flex-wrap: wrap; gap: 4px; }

/* facet groups */
.facet + .facet { margin-top: 14px; }
.facet-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin-bottom: 6px;
  color: var(--fg-faint);
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: .08em;
}
.facet-list { display: flex; flex-direction: column; gap: 1px; }

.tag {
  display: flex;
  align-items: center;
  gap: 6px;
  width: 100%;
  padding: 3px 6px;
  border-radius: var(--radius);
  color: var(--fg-dim);
  text-align: left;
  transition: background .12s var(--ease), color .12s var(--ease);
}
.tag:hover { background: var(--bg-raise); color: var(--fg); }
.tag[aria-pressed="true"] { background: var(--accent); color: var(--accent-ink); }
.tag[aria-pressed="true"] .tag-count { color: var(--accent-ink); opacity: .65; }
.tag[disabled] { opacity: .3; cursor: default; }
.tag[disabled]:hover { background: none; color: var(--fg-dim); }

.tag-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tag-count { color: var(--fg-faint); font-size: 10px; font-variant-numeric: tabular-nums; }

/* chips shown in the "filtering" block */
.chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 2px 5px 2px 7px;
  border-radius: var(--radius);
  background: var(--accent);
  color: var(--accent-ink);
  font-size: 11px;
}
.chip-facet { opacity: .55; font-size: 9px; text-transform: uppercase; }
.chip-x { padding: 0 2px; opacity: .6; }
.chip-x:hover { opacity: 1; }

.legend {
  margin-top: auto;
  padding-top: 14px;
  border-top: 1px solid var(--line);
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.legend-row { display: flex; align-items: center; gap: 5px; color: var(--fg-faint); font-size: 10px; }
.legend-row span { margin-left: 2px; }

/* -------------------------------------------------------------------- main */

#main { display: flex; flex-direction: column; min-width: 0; min-height: 0; height: 100%; }

#toolbar {
  display: flex;
  align-items: center;
  gap: 10px;
  height: 40px;
  padding: 0 var(--pad);
  border-bottom: 1px solid var(--line);
  flex: none;
}
.spacer { flex: 1; }
.count { color: var(--fg-faint); font-variant-numeric: tabular-nums; }

.icon-btn {
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  border-radius: var(--radius);
  color: var(--fg-dim);
}
.icon-btn:hover { background: var(--bg-raise); color: var(--fg); }

.cols { display: flex; align-items: center; gap: 2px; }
.cols-value {
  min-width: 18px;
  color: var(--fg-dim);
  text-align: center;
  font-variant-numeric: tabular-nums;
}

#scroller {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;          /* see #app — without this it never scrolls */
  overflow-y: auto;
  overflow-x: hidden;
  padding: var(--pad);
  padding-bottom: calc(var(--pad) * 3);
  overscroll-behavior: contain;
  scrollbar-width: thin;
  scrollbar-color: var(--line) transparent;
}

#grid { position: relative; width: 100%; }

/* ------------------------------------------------------------------- tiles */

.tile {
  position: absolute;
  top: 0;
  left: 0;
  display: block;
  overflow: hidden;
  border-radius: var(--radius);
  background: var(--bg-raise);
  /* transform is set per-tile by the layout engine */
  will-change: transform;
}

.tile-media {
  position: relative;
  width: 100%;
  height: 100%;
  overflow: hidden;
  background-size: cover;
  background-position: center;
}

.tile-media img,
.tile-media video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.tile-media img {
  opacity: 0;
  transition: opacity .35s var(--ease);
}
.tile-media img.loaded { opacity: 1; }

.tile-media video {
  opacity: 0;
  transition: opacity .2s var(--ease);
  z-index: 1;
}
.tile-media video.playing { opacity: 1; }

.tile::after {
  content: "";
  position: absolute;
  inset: 0;
  border: 1px solid transparent;
  border-radius: var(--radius);
  pointer-events: none;
  transition: border-color .15s var(--ease);
  z-index: 3;
}
.tile:hover::after { border-color: color-mix(in srgb, var(--fg) 28%, transparent); }
.tile:focus-visible { outline: 2px solid var(--fg); outline-offset: 2px; }

/* duration / gallery badge */
.badge {
  position: absolute;
  right: 6px;
  bottom: 6px;
  z-index: 2;
  padding: 1px 5px;
  border-radius: 2px;
  background: rgba(0,0,0,.62);
  color: #fff;
  font-size: 10px;
  font-variant-numeric: tabular-nums;
  backdrop-filter: blur(6px);
  opacity: 0;
  transition: opacity .15s var(--ease);
}
.tile:hover .badge { opacity: 1; }

/* captions, toggled with `c` */
.tile-caption {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 2;
  padding: 16px 8px 6px;
  background: linear-gradient(to top, rgba(0,0,0,.78), transparent);
  color: #fff;
  font-size: 11px;
  line-height: 1.35;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  opacity: 0;
  transition: opacity .18s var(--ease);
  pointer-events: none;
}
body.captions .tile-caption { opacity: 1; }

/* ---- batch selection (studio) ---- */

.selbar { display: flex; align-items: center; gap: 6px; }
.sel-tools { display: flex; align-items: center; gap: 4px; }

.selbtn {
  padding: 3px 8px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  color: var(--fg-dim);
  font-size: 11px;
}
.selbtn:hover { color: var(--fg); border-color: var(--fg-faint); }
.selbtn[disabled] { opacity: .4; cursor: default; }
.selbtn[disabled]:hover { color: var(--fg-dim); border-color: var(--line); }
.selbtn.is-active { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.selbtn-go:not([disabled]) { background: #4ade80; border-color: #4ade80; color: #06240f; }

/* In select mode tiles are targets, not links. */
body.selecting .tile { cursor: pointer; }
body.selecting .tile-caption { opacity: 1; }

.tile[data-selected="1"]::after {
  border-color: #4ade80;
  border-width: 2px;
}
.tile-check {
  position: absolute;
  top: 6px;
  left: 6px;
  z-index: 3;
  display: none;
  place-items: center;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  border: 1.5px solid rgba(255,255,255,.8);
  background: rgba(0,0,0,.45);
  color: #06240f;
  font-size: 11px;
  line-height: 1;
}
body.selecting .tile-check { display: grid; }
.tile[data-selected="1"] .tile-check {
  background: #4ade80;
  border-color: #4ade80;
}

/* -------------------------------------------------------------- studio mode */

/* A dot per tile: filled means live on purz.ai, hollow means it is not. */
.tile-status {
  position: absolute;
  top: 6px;
  right: 6px;
  z-index: 2;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  border: 1px solid rgba(255,255,255,.7);
  background: transparent;
  box-shadow: 0 0 0 1px rgba(0,0,0,.45);
}
.tile[data-status="published"] .tile-status { background: #4ade80; border-color: #4ade80; }
.tile[data-status="review"]    .tile-status { border-color: #e0a34a; }

body.studio .brand-name::after {
  content: "studio";
  margin-left: 6px;
  padding: 1px 5px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  color: var(--fg-faint);
  font-size: 9px;
  text-transform: uppercase;
  letter-spacing: .06em;
  vertical-align: middle;
}

.v-publish {
  padding: 2px 9px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  color: var(--fg-dim);
  font-size: 11px;
}
.v-publish:hover { border-color: var(--fg-faint); color: var(--fg); }
.v-publish[data-live="1"] { background: #4ade80; border-color: #4ade80; color: #06240f; }

.empty {
  display: grid;
  place-items: center;
  gap: 8px;
  padding: 80px 0;
  color: var(--fg-faint);
}

/* ------------------------------------------------------------------ viewer */

.viewer {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: grid;
  /* minmax(0,…) for the same reason: a bare 1fr row will not shrink below the
     intrinsic size of a large image, pushing the meta bar off screen. */
  grid-template-rows: minmax(0, 1fr) auto;
  background: color-mix(in srgb, var(--bg) 92%, black);
  animation: fade .18s var(--ease);
}
.viewer[hidden] { display: none; }
@keyframes fade { from { opacity: 0 } to { opacity: 1 } }

/* The stage is a positioned box that clips; the media sits in an absolutely
   positioned layer inside it. Percentage max-height only resolves against a
   definite height, and a grid/flex track alone does not reliably provide one --
   which is why tall portraits were overflowing behind the meta bar. */
.v-stage {
  position: relative;
  min-height: 0;
  overflow: hidden;
  touch-action: none;
}

.v-media {
  position: absolute;
  inset: 0;
  display: grid;
  /* The tracks must be sized explicitly. `place-items: center` only sets
     alignment — the implicit row still auto-sizes to the image's intrinsic
     height, so `max-height: 100%` on the child resolves against the image's own
     3072px rather than the stage. minmax(0, 1fr) pins the track to the
     container, which is what makes the percentage mean anything. */
  grid-template: minmax(0, 1fr) / minmax(0, 1fr);
  place-items: center;
  padding: 32px 56px 16px;
}

.v-media img,
.v-media video {
  display: block;
  /* Exact px size is assigned by fitMedia() in app.js from the manifest's known
     dimensions. Intrinsic sizing is unreliable here: a <video> reports the
     poster's size until metadata loads, so `max-height: 100%` + `width: auto`
     renders an ultrawide clip at the 800px poster width and then jumps.
     Sizing the box ourselves is deterministic and keeps native video controls
     attached to the video rather than floating in letterbox space. */
  object-fit: contain;
  border-radius: 2px;
  /* Zoom and pan ride on top of the CSS-fitted size, so scale 1 is always
     "fits the viewport" no matter the source dimensions. */
  transform-origin: center center;
  will-change: transform;
  cursor: zoom-in;
}

.v-stage[data-zoomed="1"] .v-media { padding: 0; }
.v-stage[data-zoomed="1"] img,
.v-stage[data-zoomed="1"] video {
  cursor: grab;
  border-radius: 0;
}
.v-stage[data-dragging="1"] img,
.v-stage[data-dragging="1"] video { cursor: grabbing; }

.v-zoom {
  position: absolute;
  left: 12px;
  bottom: 12px;
  z-index: 3;
  padding: 3px 8px;
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--bg) 76%, transparent);
  color: var(--fg-dim);
  font-size: 10px;
  font-variant-numeric: tabular-nums;
  backdrop-filter: blur(8px);
  opacity: 0;
  transition: opacity .18s var(--ease);
  pointer-events: none;
}
.v-stage[data-zoomed="1"] .v-zoom,
.v-stage:hover .v-zoom { opacity: 1; }

.v-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 6px 14px;
  padding: 10px var(--pad) 12px;
  border-top: 1px solid var(--line);
  /* Capped so a long prompt and a big tag list cannot starve the image of
     height — this row is `auto` in the grid, so whatever it takes, the stage
     loses. */
  max-height: 22vh;
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
  scrollbar-color: var(--line) transparent;
}
.v-title { font-size: 13px; font-weight: 600; }
.v-dim { color: var(--fg-faint); font-variant-numeric: tabular-nums; }
.v-tags { display: flex; flex-wrap: wrap; gap: 4px; width: 100%; }
.v-tag {
  padding: 1px 6px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  color: var(--fg-dim);
  font-size: 10px;
}
.v-tag:hover { border-color: var(--fg-faint); color: var(--fg); }
.v-prompt {
  width: 100%;
  margin: 2px 0 0;
  color: var(--fg-dim);
  font-size: 11px;
  line-height: 1.5;
  max-height: 4.5em;
  overflow-y: auto;
}

.v-caption {
  width: 100%;
  margin: 0;
  font-size: 12px;
  line-height: 1.5;
}
.v-caption:empty { display: none; }
.v-actions { display: flex; gap: 10px; margin-left: auto; }

.v-btn {
  position: absolute;
  z-index: 2;
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border-radius: var(--radius);
  color: var(--fg-dim);
  background: color-mix(in srgb, var(--bg) 70%, transparent);
  backdrop-filter: blur(8px);
}
.v-btn:hover { color: var(--fg); background: var(--bg-raise); }
.v-close { top: 12px; right: 12px; }
.v-prev  { top: 50%; left: 12px;  transform: translateY(-50%); font-size: 22px; }
.v-next  { top: 50%; right: 12px; transform: translateY(-50%); font-size: 22px; }

/* ---- video scrubbing (touch) ---- */

/* Playback position. Always faintly present, brightens while scrubbing. */
.v-seek {
  position: absolute;
  left: 0; right: 0;
  bottom: 0;
  height: 2px;
  z-index: 4;
  background: rgba(255,255,255,.18);
  opacity: 0;
  transition: opacity .18s var(--ease), height .12s var(--ease);
  pointer-events: none;
}
.v-seek i {
  display: block;
  height: 100%;
  width: 0;
  background: #fff;
  transform-origin: left center;
}
.viewer.has-video .v-seek { opacity: .85; }
.viewer.scrubbing .v-seek { height: 4px; opacity: 1; }
.viewer.chrome-hidden:not(.scrubbing) .v-seek { opacity: .3; }

/* Timecode, only while actively scrubbing. */
.v-time {
  position: absolute;
  left: 50%;
  bottom: 28px;
  z-index: 5;
  transform: translateX(-50%);
  padding: 5px 12px;
  border-radius: 999px;
  background: rgba(0,0,0,.66);
  color: #fff;
  font-size: 13px;
  font-variant-numeric: tabular-nums;
  backdrop-filter: blur(8px);
  opacity: 0;
  transition: opacity .14s var(--ease);
  pointer-events: none;
}
.viewer.scrubbing .v-time { opacity: 1; }

/* Held-to-accelerate badge. */
.v-rate {
  position: absolute;
  top: 14px;
  left: 50%;
  z-index: 5;
  transform: translateX(-50%) scale(.9);
  padding: 4px 12px;
  border-radius: 999px;
  background: rgba(0,0,0,.66);
  color: #fff;
  font-size: 12px;
  font-weight: 600;
  backdrop-filter: blur(8px);
  opacity: 0;
  transition: opacity .14s var(--ease), transform .14s var(--ease);
  pointer-events: none;
}
.viewer.fast .v-rate { opacity: 1; transform: translateX(-50%) scale(1); }

/* Transient clone used to animate the viewer image back into its tile. */
.flip-ghost {
  position: fixed;
  z-index: 49;
  object-fit: contain;
  border-radius: var(--radius);
  transform-origin: top left;
  pointer-events: none;
  will-change: transform;
}

/* ------------------------------------------------------------------ editor */

.viewtoggle { display: flex; gap: 4px; }
.selbtn-danger { color: #f87171; border-color: color-mix(in srgb, #f87171 40%, transparent); }
.selbtn-danger:hover { background: #f87171; border-color: #f87171; color: #fff; }

/* ---- dense table ---- */

.table {
  width: 100%;
  border-collapse: collapse;
  font-size: 11px;
  /* Shift-click is a range selection here, not a text selection — without this
     every range drag smears highlighted text across the rows it touched. */
  user-select: none;
}

.table thead th {
  position: sticky;
  top: 0;
  z-index: 2;
  padding: 8px 10px;
  background: var(--bg);
  border-bottom: 1px solid var(--line);
  color: var(--fg-faint);
  font-weight: 400;
  text-align: left;
  text-transform: uppercase;
  letter-spacing: .07em;
}

.th-check, .td-check { width: 28px; }
.th-thumb, .td-thumb { width: 56px; }

.table tbody tr {
  border-bottom: 1px solid color-mix(in srgb, var(--line) 55%, transparent);
  cursor: pointer;
}

.table tbody tr:hover { background: var(--bg-raise); }
.table tbody tr[data-selected="1"] { background: color-mix(in srgb, #4ade80 12%, transparent); }

.table td { padding: 5px 10px; vertical-align: middle; }

.td-thumb img {
  display: block;
  width: 44px;
  height: 30px;
  object-fit: cover;
  border-radius: 2px;
  background: var(--bg-raise);
}

.td-title {
  max-width: 34vw;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.td-dim, .td-tags { color: var(--fg-faint); }
.td-dim { white-space: nowrap; }
.td-tags {
  max-width: 26vw;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.pill {
  padding: 1px 6px;
  border-radius: 999px;
  border: 1px solid var(--line);
  font-size: 10px;
}
.pill[data-st="published"] { color: #4ade80; border-color: color-mix(in srgb, #4ade80 45%, transparent); }
.pill[data-st="draft"] { color: var(--fg-faint); }

/* ---- inspector ---- */

/* A third column rather than an overlay: it never covers the work, and the
   masonry recomputes around it like any other width change. */
#app.no-sidebar { grid-template-columns: 0 1fr; }
body.inspecting #app { grid-template-columns: var(--sidebar-w) 1fr var(--inspector-w); }
body.inspecting #app.no-sidebar { grid-template-columns: 0 1fr var(--inspector-w); }

.inspector {
  display: flex;
  flex-direction: column;
  min-height: 0;              /* see #app — or the list never scrolls */
  border-left: 1px solid var(--line);
  background: var(--bg);
  overflow: hidden;
}

.insp-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  padding: 14px var(--pad);
  border-bottom: 1px solid var(--line);
}

.insp-count { font-size: 12px; }

.insp-scroll {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding: 4px var(--pad) var(--pad);
  scrollbar-width: thin;
  scrollbar-color: var(--line) transparent;
}

.insp-sec { margin-top: 18px; }

.insp-sec h3 {
  margin-bottom: 8px;
  color: var(--fg-faint);
  font-size: 10px;
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: .08em;
}

.insp-row { display: flex; flex-wrap: wrap; gap: 5px; }
.insp-mix { margin-top: 6px; color: var(--fg-faint); font-size: 10px; }
.insp-list { display: flex; flex-direction: column; gap: 1px; }

.insp-item {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 5px 6px;
  border-radius: var(--radius);
  color: var(--fg-dim);
  font-size: 11px;
  text-align: left;
}

.insp-item:hover { background: var(--bg-raise); color: var(--fg); }

.insp-box {
  flex: 0 0 auto;
  width: 11px;
  height: 11px;
  border: 1px solid var(--fg-faint);
  border-radius: 2px;
}

/* Three states, because two would be a lie: applied to all, to some, or none.
   The dash is what stops a click silently stripping the ones that had it. */
.insp-item[data-state="all"] .insp-box { background: var(--accent); border-color: var(--accent); }
.insp-item[data-state="some"] .insp-box {
  background: linear-gradient(to bottom, transparent 42%, var(--fg-dim) 42%, var(--fg-dim) 58%, transparent 58%);
}

.insp-name { flex: 1 1 auto; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
.insp-n { flex: 0 0 auto; color: var(--fg-faint); font-size: 10px; }

.insp-add { display: flex; gap: 5px; margin-top: 8px; }

.insp-add input {
  flex: 1 1 auto;
  min-width: 0;
  padding: 5px 8px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--bg-raise);
  color: var(--fg);
  font: inherit;
  font-size: 11px;
}

.insp-add input:focus { outline: none; border-color: var(--fg-faint); }
.insp-danger { margin-top: 26px; padding-top: 14px; border-top: 1px solid var(--line); }

@media (max-width: 900px) {
  /* No room for a third column — float it over instead. */
  body.inspecting #app { grid-template-columns: var(--sidebar-w) 1fr; }
  body.inspecting #app.no-sidebar { grid-template-columns: 0 1fr; }
  .inspector {
    position: fixed;
    right: 0;
    top: 0;
    bottom: 0;
    z-index: 40;
    width: min(320px, 88vw);
    box-shadow: -8px 0 28px rgb(0 0 0 / .4);
  }
  .td-title, .td-tags { max-width: 30vw; }
}

/* ------------------------------------------------------------------ boards */

/* The curated rail above the grid. It scrolls away with the work rather than
   pinning: boards are a way in, not permanent chrome. */
.boards {
  margin-bottom: calc(var(--pad) * 1.25);
}

.boards-head {
  display: flex;
  align-items: baseline;
  gap: 10px;
  margin-bottom: 8px;
}

.boards-label {
  color: var(--fg-faint);
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: .08em;
}

.boards-rail {
  display: flex;
  gap: var(--gap);
  overflow-x: auto;
  overflow-y: hidden;
  /* Cards are a fixed size, so a long shelf scrolls sideways instead of
     wrapping into rows that push the grid off the first screen. */
  padding-bottom: 6px;
  scrollbar-width: thin;
  scrollbar-color: var(--line) transparent;
}

.board-card {
  position: relative;
  flex: 0 0 auto;
  width: 150px;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--bg-raise);
  color: var(--fg-dim);
  text-align: left;
  overflow: hidden;
  transition: border-color .16s var(--ease), color .16s var(--ease);
}

.board-card:hover { color: var(--fg); border-color: var(--fg-faint); }

.board-card[aria-pressed="true"] {
  border-color: var(--accent);
  color: var(--fg);
}

.board-cover {
  display: block;
  height: 84px;
  background-color: var(--bg);
  background-size: cover;
  background-position: center;
}

/* A board with nothing in it yet still has to read as a board. */
.board-cover.is-empty {
  background-image: repeating-linear-gradient(
    45deg, transparent, transparent 5px, var(--line) 5px, var(--line) 6px
  );
}

.board-meta {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  padding: 7px 9px;
}

.board-name {
  font-size: 11px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.board-count { color: var(--fg-faint); font-size: 10px; }

/* Public boards are the ones strangers can see — worth being able to tell at a
   glance, since that is the consequential bit of state on a board. */
.board-card.is-public .board-cover::after {
  content: "";
  position: absolute;
  top: 7px;
  left: 7px;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: #4ade80;
}

.board-edit {
  position: absolute;
  top: 4px;
  right: 6px;
  padding: 0 5px;
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--bg) 70%, transparent);
  color: var(--fg-dim);
  font-size: 12px;
  line-height: 16px;
  opacity: 0;
  transition: opacity .16s var(--ease);
}

.board-card:hover .board-edit { opacity: 1; }
.board-edit:hover { color: var(--fg); }

.board-card.is-dragging { opacity: .4; }

/* -------------------------------------------------------------- board menu */

.board-menu {
  position: fixed;
  z-index: 70;
  min-width: 180px;
  padding: 4px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--bg-raise);
  box-shadow: 0 8px 28px rgb(0 0 0 / .35);
}

.menu-item {
  display: block;
  width: 100%;
  padding: 6px 9px;
  border-radius: var(--radius);
  color: var(--fg-dim);
  font-size: 11px;
  text-align: left;
}

.menu-item:hover { background: var(--bg); color: var(--fg); }
.menu-item.is-danger:hover { color: #f87171; }
.menu-sep { height: 1px; margin: 4px 2px; background: var(--line); }

/* ------------------------------------------------------------------- toast */

.toast {
  position: fixed;
  left: 50%;
  bottom: 24px;
  z-index: 60;
  padding: 7px 14px;
  border-radius: var(--radius);
  background: var(--accent);
  color: var(--accent-ink);
  transform: translate(-50%, 8px);
  opacity: 0;
  transition: opacity .18s var(--ease), transform .18s var(--ease);
  pointer-events: none;
}
.toast.show { opacity: 1; transform: translate(-50%, 0); }

/* ------------------------------------------------------------------ mobile */

/* ------------------------------------------------------------------ mobile */

/* Backdrop behind the sidebar drawer. */
.scrim {
  position: fixed;
  inset: 0;
  z-index: 39;
  background: rgba(0,0,0,.55);
  backdrop-filter: blur(2px);
  opacity: 0;
  transition: opacity .24s var(--ease);
}
.scrim.show { opacity: 1; }

@media (max-width: 720px) {
  :root { --gap: 6px; --pad: 12px; }

  /* The sidebar is position:fixed here, so it leaves grid flow entirely and
     #main auto-places into the FIRST column — which was 0 width, collapsing the
     grid to a sliver. One column, one in-flow child. Both selectors are needed:
     the base #app.no-sidebar rule outranks a bare #app in a media query. */
  #app,
  #app.no-sidebar { grid-template-columns: 1fr; }

  #sidebar {
    position: fixed;
    z-index: 40;
    top: 0; bottom: 0; left: 0;
    width: min(84vw, 300px);
    transform: translateX(-100%);
    transition: transform .28s var(--ease);
    box-shadow: 0 0 40px rgba(0,0,0,.5);
    background: var(--bg);
  }
  #app:not(.no-sidebar) #sidebar { transform: none; }
  #app:not(.no-sidebar) .sidebar-inner { opacity: 1; }
  .sidebar-inner { width: 100%; }

  /* Fingers need more than 22px. */
  .icon-btn { width: 34px; height: 34px; }
  .tag { padding: 7px 8px; }
  .facet-list { gap: 2px; }
  #toolbar { height: 46px; }
  .legend { display: none; }

  /* ---- viewer becomes a full-bleed swipe deck ---- */
  .viewer { grid-template-rows: minmax(0, 1fr); }

  .v-stage { padding: 0; }
  .v-media { padding: 0; }
  .v-media img, .v-media video { border-radius: 0; }

  .v-prev, .v-next { display: none; }   /* replaced by vertical swipe */

  .v-close {
    top: max(10px, env(safe-area-inset-top));
    right: 10px;
    width: 40px;
    height: 40px;
    background: rgba(0,0,0,.45);
    color: #fff;
  }

  /* Metadata overlays the media rather than stealing height from it. */
  .v-meta {
    position: absolute;
    left: 0; right: 0;
    bottom: 0;
    z-index: 2;
    max-height: 46vh;
    padding: 40px var(--pad) calc(14px + env(safe-area-inset-bottom));
    border-top: 0;
    background: linear-gradient(to top, rgba(0,0,0,.86) 55%, transparent);
    color: #fff;
    transition: opacity .2s var(--ease), transform .2s var(--ease);
  }
  /* Read like a social post: title, then the caption, then tags. Stacked in a
     single column rather than the desktop's inline row. */
  .v-meta {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 6px;
    overflow: visible;
  }
  .v-meta .v-title { font-size: 15px; line-height: 1.3; }
  .v-meta .v-dim,
  .v-meta .v-prompt { color: rgba(255,255,255,.6); }
  .v-meta .v-tag { border-color: rgba(255,255,255,.28); color: rgba(255,255,255,.8); }
  .v-meta .linkish { color: rgba(255,255,255,.85); }

  .v-meta .v-caption {
    color: rgba(255,255,255,.92);
    font-size: 13px;
    /* Long captions must not eat the artwork. */
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }

  /* Tags scroll sideways instead of wrapping into a wall of chips. */
  .v-meta .v-tags {
    flex-wrap: nowrap;
    overflow-x: auto;
    max-width: 100%;
    padding-bottom: 2px;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }
  .v-meta .v-tags::-webkit-scrollbar { display: none; }
  .v-meta .v-tag { flex: none; }

  .v-meta .v-prompt {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    max-height: none;
  }

  .v-meta .v-actions { margin-left: 0; }

  /* Hint that the overlay is dismissable, shown only while it is up. */
  .v-meta::after {
    content: "tap to hide";
    position: absolute;
    top: 14px;
    right: var(--pad);
    color: rgba(255,255,255,.34);
    font-size: 9px;
    letter-spacing: .06em;
    text-transform: uppercase;
  }

  /* Tap toggles chrome, so the work can be seen unobstructed. */
  .viewer.chrome-hidden .v-meta,
  .viewer.chrome-hidden .v-close,
  .viewer.chrome-hidden .v-progress {
    opacity: 0;
    pointer-events: none;
  }
  .viewer.chrome-hidden .v-meta { transform: translateY(12px); }

  .v-zoom { display: none; }
}

/* Position in the deck — a quiet hint that swiping goes somewhere. */
.v-progress {
  position: absolute;
  top: 0; left: 0;
  height: 2px;
  width: 0;
  z-index: 3;
  background: var(--fg);
  opacity: .5;
  transition: width .25s var(--ease), opacity .2s var(--ease);
  pointer-events: none;
}
@media (min-width: 721px) { .v-progress { display: none; } }

/* The media layer follows the finger during a swipe. */
.v-media.dragging { transition: none; }
.v-media.settling { transition: transform .22s var(--ease), opacity .22s var(--ease); }

@media (prefers-reduced-motion: reduce) {
  * { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}
