/* =============================================================== library-ui
   The library, shared by both apps that show one. One source, two deliveries:
   the API serves this directory over HTTP, and the pod image copies the same
   files in.

   THIS FILE DEFINES NO TOKENS. Both hosts already declare an identical token
   layer (--bg, --ink-3, --t-13, --r-media, --ease, --shadow-lift and the rest),
   and the only difference between them is which theme is the default. So every
   colour, radius and duration below is a var() read out of the host, and theme
   switching works in both without this file knowing either exists.

   The knobs a host may want to set are the --lib-* customs at the top, all of
   which carry a fallback. The F3 grounds (--card, --input, --well, --line-card) are
   read with a fallback to the token they replaced, so a host that has not declared
   them yet looks exactly as it did.

   Every class is lib- prefixed. One of the hosts already has .grid, .shot and
   .btn of its own, and they must not collide while it migrates.

   See DESIGN.md. The rules broken most often, and honoured here: sections
   separate by a change of ground and never by a rule, controls still carry a 1px
   edge, mono is for measurements only, and only transform and opacity animate.  */

/* .lib-tiles: a grid of the library's tiles outside the library (the pod's Video page), so
   one tile size and one picture shape are written once. */
.lib,
.lib-tiles {
  --lib-tile-min: 196px;
  --lib-gap: 16px;
  --lib-ratio: 4 / 3;
}

/* =================================================================== chrome */

.lib-bar {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 20px;
}

/* The trail starts where the page's words start: its first button's padding hangs outside. */
.lib-crumb {
  display: flex;
  align-items: center;
  gap: 4px;
  min-width: 0;
  flex-wrap: wrap;
  margin-left: -8px;
}
.lib-crumb button {
  border: 0;
  background: none;
  padding: 0 8px;
  border-radius: 8px;
  color: var(--ink-2);
  font: inherit;
  font-weight: 600;
  font-size: var(--t-14);
  cursor: pointer;
  max-width: 22ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);height:32px}
.lib-crumb button:hover { background: var(--sunken); color: var(--ink); }
.lib-crumb button[aria-current="page"] { color: var(--ink); cursor: default; }
.lib-crumb button[aria-current="page"]:hover { background: none; }
.lib-crumb-sep { color: var(--ink-3); flex: 0 0 auto; display: flex; }

/* The account's storage, beside Refresh. On the page ground, where clay text passes
   (4.62:1 light, 7.29:1 dark); numbers are .lib-mono. */
.lib-storage {
  font-size: var(--t-12);
  color: var(--ink-2);
  white-space: nowrap;
}
/* v4.1.3 (E5). In the bar it is a meter: its words, and a thin track under them. */
.lib-storage.lib-meter { display: inline-flex; flex-direction: column; gap: 4px; min-width: 128px; }
.lib-storage[hidden] { display: none; }
.lib-storage .lib-storage-track { display: block; height: 4px; }
.lib-empty-actions { display: flex; flex-wrap: wrap; justify-content: center; gap: 8px; }
/* The warning is two sentences; it wraps inside its own width rather than pushing New
   folder onto a line of its own. */
.lib-storage--warn { color: var(--clay); white-space: normal; flex: 0 1 280px; min-width: 0; line-height: 1.45; }

/* The back arrow only exists on narrow screens, where the trail is collapsed. */
.lib-crumb-back { display: none; }

/* A crumb above the current folder takes a drop, and says so the way a folder
   tile does: blue when it will accept, clay when it will not. */
.lib-crumb button.lib-crumb--over {
  background: var(--blue-wash);
  color: var(--blue-text);
  box-shadow: inset 0 0 0 1px var(--blue);
}
.lib-crumb button.lib-crumb--refuse {
  background: var(--clay-wash);
  color: var(--clay);
  cursor: not-allowed;
}

/* All, Images, Videos. v4.1.3 (E7): the hosts' ONE segmented control (shared/controls.css
   names .lib-kind beside .seg and .pick); it had a copy here. Only its place is the bar's. */
.lib-kind { margin-left: auto; }

.lib-search {
  position: relative;
  flex: 0 1 280px;
  min-width: 180px;
}
.lib-search svg {
  position: absolute;
  left: 12px;
  top: 50%;
  transform: translateY(-50%);
  color: var(--ink-3);
  pointer-events: none;
}
.lib-search input {
  width: 100%;
  height:40px;
  padding: 0 34px 0 38px;
  border: 1px solid var(--line-2);
  border-radius: var(--r-input);
  background: var(--input, var(--sunken));
  color: var(--ink);
  font-size: var(--t-13);
  transition: border-color var(--dur) var(--ease);
}
.lib-search input::placeholder { color: var(--ink-2); }
.lib-search input:hover { border-color: var(--ink-3); }
.lib-search-clear {
  position: absolute;
  right: 6px;
  top: 50%;
  transform: translateY(-50%);
  width: 26px;
  height: 26px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: 7px;
  background: none;
  color: var(--ink-3);
  cursor: pointer;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.lib-search-clear:hover { background: var(--surface); color: var(--ink); }

/* ================================================================== folders */

.lib-folders {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(212px, 1fr));
  gap: 12px;
  margin-bottom: 24px;
}

.lib-folder {
  position: relative;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 44px 12px 14px;
  border: 1px solid var(--line-card, var(--line));
  border-radius: var(--r-tile);
  background: var(--surface);
  color: inherit;
  text-align: left;
  font: inherit;
  cursor: pointer;
  min-width: 0;
  /* The ring is an outline so a drop state never changes the box and never
     reflows the row underneath it. */
  outline: 2px solid transparent;
  outline-offset: -2px;
  transition: transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease),
              outline-color var(--dur) var(--ease);
}
.lib-folder:hover { transform: translateY(-3px); box-shadow: var(--shadow-lift); }
/* Neutral (P45): blue is for what is active, selected or primary, and a folder's icon is
   none of them. A folder being dropped on still turns (.lib-folder--drop, --refuse). */
.lib-folder-icon {
  width: 34px;
  height: 34px;
  flex: 0 0 34px;
  display: grid;
  place-items: center;
  border-radius: 10px;
  background: var(--well, var(--sunken));
  color: var(--ink-2);
}
.lib-folder-text { min-width: 0; flex: 1; }
.lib-folder-text b {
  display: block;
  font-family: var(--body);
  font-weight: 600;
  font-size: var(--t-13);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Direct child only. A bare `span` here also caught the .lib-mono wrapping the
   count, turned it into a block, and put the number and its unit on separate
   lines, which left the two cards in a row different heights. */
.lib-folder-text > span {
  display: block;
  font-size: var(--t-12);
  color: var(--ink-3);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Accepting a drop. */
.lib-folder--over {
  outline-color: var(--blue);
  transform: translateY(-3px);
  box-shadow: var(--shadow-lift);
}
/* Refusing one. A folder cannot be dropped into itself or a descendant, and the
   target has to say so: doing nothing silently reads as a broken drag. */
.lib-folder--refuse {
  outline-color: var(--clay);
  cursor: not-allowed;
}
.lib-folder--refuse .lib-folder-icon { background: var(--clay-wash); color: var(--clay); }
.lib-folder--refuse .lib-folder-text span { color: var(--clay); }

.lib-folder--dragging { opacity: .4; }

/* ===================================================================== grid */

.lib-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(var(--lib-tile-min), 1fr));
  gap: var(--lib-gap);
}

.lib-tile {
  position: relative;
  display: block;
  border-radius: var(--r-media);
  overflow: hidden;
  background: var(--surface);
  border: 1px solid var(--line-card, var(--line));
  color: inherit;
  /* Selection is an outline, drawn inside the border box, so turning it on adds
     no width and the grid cannot reflow when selection mode starts. */
  outline: 2px solid transparent;
  outline-offset: -2px;
  transition: transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease),
              outline-color var(--dur) var(--ease);
}
.lib-tile:hover { transform: translateY(-3px); box-shadow: var(--shadow-lift); }
.lib-tile--sel { outline-color: var(--blue); }

.lib-tile-open {
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  color: inherit;
  text-align: left;
}
.lib-tile img {
  display: block;
  width: 100%;
  aspect-ratio: var(--lib-ratio, 4 / 3);
  object-fit: cover;
  background: var(--stage);
}
/* A portrait picture in a landscape tile is cropped, and cropped from the middle it loses
   the head of whoever is in it. So a tall one is held nearer its top (library-ui.js markTall
   puts the class on when the picture has loaded and says how tall it is). A wide one stays
   centred. */
.lib-tile img.lib-tall,
.lib-tiles img.lib-tall {
  object-position: center 30%;
}

/* The picture area. Relative, so a video's play mark and length can sit on it. */
.lib-tile-media { position: relative; display: block; }
/* v4.1.3. The clip that plays while the tile is under the pointer: over the thumbnail, the
   same box. It fades in when it has a frame; until then the thumbnail shows. */
.lib-tile-clip {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
  background: transparent; pointer-events: none;
}
/* v4.1.3. A list on its way: quiet tiles, the shape of the ones that are coming. No shimmer. */
.lib-skel-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(var(--lib-tile-min), 1fr));
  gap: var(--lib-gap);
}
.lib-skel-grid[hidden] { display: none; }
.lib-skel {
  display: block; aspect-ratio: var(--lib-ratio, 4 / 3);
  border-radius: var(--r-media); background: var(--surface); border: 1px solid var(--line-card, var(--line));
  animation: lib-skel 1.6s ease-in-out infinite;
}
@keyframes lib-skel { 50% { opacity: .55; } }
/* A tile that has just been added arrives, 160 ms, without moving. */
.lib-tile { animation: lib-fade var(--dur-enter) ease-out; }

/* No thumbnail yet: a quiet ground and the kind's icon, never the full file. */
.lib-tile-ph {
  display: grid;
  place-items: center;
  aspect-ratio: var(--lib-ratio, 4 / 3);
  background: var(--well, var(--sunken));
  color: var(--ink-2);
}

.lib-tile-play {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 36px;
  height: 36px;
  margin: -18px 0 0 -18px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: rgba(8, 9, 11, .7);
  color: #FFFFFF;
  pointer-events: none;
}
.lib-tile-play svg { fill: currentColor; stroke: none; margin-left: 2px; }

/* A length is a measurement, so it is mono; the badge is a dark scrim because it
   sits on a picture of any colour, in either theme. */
.lib-tile-dur {
  position: absolute;
  right: 8px;
  bottom: 8px;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  height: 22px;
  padding: 0 8px;
  border-radius: 7px;
  background: rgba(8, 9, 11, .74);
  color: #FFFFFF;
  font-size: var(--t-12);
  pointer-events: none;
}
.lib-tile-dur svg { fill: currentColor; stroke: none; }

.lib-tile-meta { display: block; padding: 12px; }
.lib-tile-meta b {
  display: block;
  font-family: var(--body);
  font-weight: 600;
  font-size: var(--t-13);
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Direct child, for the same reason as the folder text above. */
.lib-tile-meta > span {
  display: block;
  font-size: var(--t-12);
  color: var(--ink-3);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Sizes, counts and dates only. Never a label, a button or a status word. */
.lib-mono {
  font-family: var(--mono);
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0;
}

/* A run that is still saving, or that failed to. There is no image to show and
   no URL to open, so it does not pretend to be a picture. */
.lib-tile--ghost .lib-ghost {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  aspect-ratio: var(--lib-ratio, 4 / 3);
  background: var(--well, var(--sunken));
  /* ink-2, not ink-3: ink-3 on the light well is 4.29:1. */
  color: var(--ink-2);
  padding: 16px;
  text-align: center;
  font-size: var(--t-12);
}
/* The word stays ink-2 and only the icon takes clay: clay text on the light well is
   4.06:1, under the 4.5 every word needs. The icon is 3:1 or better, which is its bar. */
.lib-tile--ghost.lib-tile--failed .lib-ghost { color: var(--ink-2); }
.lib-tile--ghost.lib-tile--failed .lib-ghost svg { color: var(--clay); }

/* The checkbox is always in the DOM at zero opacity. Adding it on hover would
   move the tile, which is exactly the layout jump B4 forbids. */
.lib-check {
  position: absolute;
  top: 10px;
  left: 10px;
  z-index: 2;
  width: 26px;
  height: 26px;
  display: grid;
  place-items: center;
  border-radius: 8px;
  border: 1px solid var(--line-2);
  background: var(--surface);
  color: transparent;
  cursor: pointer;
  opacity: 0;
  transition: opacity var(--dur) var(--ease), background var(--dur) var(--ease),
              color var(--dur) var(--ease);
}
.lib-tile:hover .lib-check,
.lib-check:focus-visible,
.lib-tile--sel .lib-check,
.lib--selecting .lib-check { opacity: 1; }
.lib-tile--sel .lib-check {
  background: var(--blue);
  border-color: var(--blue);
  color: var(--on-blue);
}

.lib-dots {
  position: absolute;
  top: 8px;
  right: 8px;
  z-index: 2;
  width:32px;
  height:32px;
  display: grid;
  place-items: center;
  border-radius: 10px;
  border: 1px solid var(--line-2);
  background: var(--surface);
  color: var(--ink-2);
  cursor: pointer;
  opacity: 0;
  transition: opacity var(--dur) var(--ease), background var(--dur) var(--ease),
              color var(--dur) var(--ease);
}
.lib-tile:hover .lib-dots,
.lib-folder:hover .lib-dots,
.lib-folder:focus-within .lib-dots,
.lib-dots[aria-expanded="true"],
.lib-dots:focus-visible { opacity: 1; }
.lib-dots:hover { background: var(--well, var(--sunken)); color: var(--ink); }

/* v4.1.3 (D5). A bar that starts with the host's own control (the dashboard's channel
   picker): that, the kind, then the search at the far end. The folder trail has its own
   line under it, and only inside a folder or a search. */
.lib-bar--lead .lib-kind { margin-left: 0; }
.lib-bar--lead .lib-search { margin-left: auto; }
.lib-crumb--row { margin: -4px 0 16px -8px; }
.lib-crumb--row[hidden] { display: none; }
/* The viewer's More: a square button, and its menu above the viewer it belongs to. */
.lib-light-more { width: 32px; padding: 0; display: inline-grid; place-items: center; }
.lib-light ~ .lib-scrim { z-index: 71; }
.lib-light ~ .lib-menu { z-index: 72; }

/* A folder's row is shorter than a tile, so its button centres rather than
   sitting in a corner. */
.lib-folder .lib-dots { top: 50%; margin-top: -16px; right: 8px; }

/* Touch has no hover, so the affordances that reveal on it are simply present. */
@media (hover: none) {
  .lib-dots, .lib-check { opacity: 1; }
}

/* ===================================================================== menu */

.lib-scrim { position: fixed; inset: 0; z-index: 40; }

.lib-menu {
  position: fixed;
  z-index: 41;
  min-width: 194px;
  max-height: calc(100vh - 16px);
  overflow-y: auto;
  padding: 6px;
  background: var(--raised);
  border: 1px solid var(--line);
  border-radius: var(--r-tile);
  box-shadow: var(--shadow-lift);
  animation: lib-pop var(--dur-enter) var(--ease);
}
@keyframes lib-pop { from { opacity: 0; transform: translateY(6px) scale(.97); } }

.lib-menu-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 9px 10px;
  border: 0;
  background: none;
  border-radius: 9px;
  color: var(--ink);
  font: inherit;
  font-size: var(--t-13);
  cursor: pointer;
  text-align: left;
  transition: background var(--dur) var(--ease);
}
.lib-menu-item:hover, .lib-menu-item:focus-visible { background: var(--sunken); }
.lib-menu-item svg { color: var(--ink-3); flex: 0 0 auto; }
.lib-menu-item--danger { color: var(--clay); }
.lib-menu-item--danger svg { color: var(--clay); }
/* Between a menu's groups: room, where there was a line (P47). */
.lib-menu-sep { height: 8px; }
.lib-meter { display: flex; flex-direction: column; gap: 8px; }
.lib-meter-top { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; }
.lib-meter-amount { font-family: var(--display); font-weight: 600; font-size: var(--t-16); letter-spacing: -.02em; }
.lib-meter-of { font-size: var(--t-12); color: var(--ink-3); white-space: nowrap; }
.lib-meter-track { height: 6px; border-radius: 99px; background: var(--sunken); overflow: hidden; }
.lib-meter-track i {
  display: block;
  height: 100%;
  width: 100%;
  border-radius: 99px;
  background: var(--meter-ok);
  transform-origin: left;
  transform: scaleX(0);
  transition: transform var(--dur) var(--ease), background var(--dur) var(--ease);
}
.lib-meter[data-state="near"] .lib-meter-track i { background: var(--meter-near); }
.lib-meter[data-state="over"] .lib-meter-track i { background: var(--meter-over); }
/* Never colour alone: at "over" the number itself changes and a line appears below. */
.lib-meter[data-state="over"] .lib-meter-amount { color: var(--clay); }
.lib-meter-note, .lib-meter-warn { margin: 0; font-size: var(--t-12); color: var(--ink-3); }
.lib-meter-warn { color: var(--clay); font-weight: 600; }

.lib-menu-head-row {
  padding: 8px 10px 4px;
  font-size: var(--t-12);
  letter-spacing: .09em;
  text-transform: uppercase;
  font-weight: 600;
  color: var(--ink-3);
}

/* F3a: rows that pick one of several (Length, Shape), a hint at the end of a row, and
   a row with a field. The check is the selected state; blue-text on --raised passes. */
.lib-menu-mark { width: 16px; flex: 0 0 16px; display: grid; place-items: center; }
.lib-menu-item .lib-menu-mark svg { color: var(--blue-text); }
.lib-menu-item[aria-checked="true"] { font-weight: 600; }
.lib-menu-hint { margin-left: auto; padding-left: 16px; font-size: var(--t-12); color: var(--ink-2); white-space: nowrap; }
.lib-menu-field {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 10px 4px 36px;
  font-size: var(--t-13);
  color: var(--ink);
}
.lib-menu-field-label { margin-right: auto; }
.lib-menu-input {
  width: 64px;
  height: 32px;
  padding: 0 8px;
  border: 1px solid var(--line-2);
  border-radius: var(--r-input);
  background: var(--input, var(--sunken));
  color: var(--ink);
  font-family: var(--mono);
  font-weight: 500;
  font-size: var(--t-13);
  text-align: right;
}
.lib-menu-input:hover { border-color: var(--ink-2); }
.lib-menu-suffix { color: var(--ink-2); }
.lib-menu-note { padding: 0 10px 8px 36px; font-size: var(--t-12); color: var(--ink-2); }
.lib-menu-note:empty { display: none; }

/* ================================================================ info mark */

/* F5. A small "i" beside a line with a little more to say, and its two-line note. The
   mark is ink-3 at rest, a 24 px target around a 16 px glyph, and never a coloured dot. */
.lib-info {
  display: inline-grid;
  place-items: center;
  width: 24px;
  height: 24px;
  margin: -4px 0;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: none;
  color: var(--ink-3);
  cursor: help;
  vertical-align: middle;
  transition: color var(--dur) var(--ease), background var(--dur) var(--ease);
}
.lib-info:hover, .lib-info[aria-expanded="true"] { color: var(--ink); background: var(--sunken); }
.lib-info--row { margin: -4px -4px -4px 4px; flex: 0 0 auto; }
.lib-menu-item .lib-info--row svg { color: currentColor; }
.lib-info-note {
  position: fixed;
  z-index: 70;
  max-width: min(400px, calc(100vw - 16px));
  padding: 9px 12px;
  background: var(--raised);
  border: 1px solid var(--line);
  border-radius: var(--r-input);
  box-shadow: var(--shadow-lift);
  color: var(--ink);
  font-family: var(--body);
  font-weight: 400;
  font-size: var(--t-12);
  line-height: 1.5;
  letter-spacing: 0;
  text-align: left;
  pointer-events: none;
  animation: lib-pop 140ms var(--ease);
}
.lib-info-note span { display: block; }
.lib-info-note span + span { color: var(--ink-2); }
.lib-sr {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

/* =================================================================== dialog */

.lib-dialog {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: grid;
  place-items: center;
  padding: 24px;
  background: rgba(0, 0, 0, .42);
  animation: lib-fade var(--dur-enter) var(--ease);
}
@keyframes lib-fade { from { opacity: 0; } }

/* F11. A dialog opened from the viewer (Rename) is above it, not behind it: the viewer
   is z-index 70 and a dialog 60, which keeps every other dialog under the sign-in gate. */
.lib-light ~ .lib-dialog { z-index: 75; }

.lib-dialog-box {
  width: 100%;
  max-width: 452px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  padding: 24px;
  box-shadow: var(--shadow-lift);
  animation: lib-rise var(--dur-enter) var(--ease);
}
@keyframes lib-rise { from { opacity: 0; transform: translateY(4px) scale(.99); } }

/* F11. A panel with a file name for a title (openDialog's `wide`). 100% of the backdrop's
   room up to min(640px, 92vw), so it never pushes past a 360 px screen, and the name
   breaks anywhere and stops at two lines (the whole name is its tooltip). */
.lib-dialog-box.lib-dialog-wide { max-width: min(640px, 92vw); }
.lib-dialog-wide h2 {
  overflow-wrap: anywhere;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
}
@media (max-width: 420px) {
  .lib-dialog:has(.lib-dialog-wide) { padding: 12px; }
  .lib-dialog-box.lib-dialog-wide { padding: 20px; }
}

.lib-dialog-box h2 {
  margin: 0 0 8px;
  font-family: var(--display);
  font-weight: 600;
  font-size: var(--t-20);
  letter-spacing: -.025em;
  line-height: 1.15;
}
.lib-dialog-box p {
  margin: 0;
  font-size: var(--t-13);
  line-height: 1.55;
  color: var(--ink-2);
}
.lib-dialog-field { margin-top: 16px; }
.lib-dialog-field label {
  display: block;
  font-size: var(--t-12);
  font-weight: 600;
  color: var(--ink-2);
  margin-bottom: 7px;
}
.lib-dialog-field input, .lib-dialog-field select {
  width: 100%;
  height:40px;
  padding:0 12px;
  border: 1px solid var(--line-2);
  border-radius: var(--r-input);
  background: var(--input, var(--sunken));
  color: var(--ink);
  font-size: var(--t-14);
  appearance: none;
}
.lib-dialog-field input:hover, .lib-dialog-field select:hover { border-color: var(--ink-3); }
.lib-dialog-note {
  margin-top: 8px;
  font-size: var(--t-12);
  color: var(--ink-3);
  min-height: 1.4em;
}
.lib-dialog-err {
  margin-top: 12px;
  font-size: var(--t-12);
  color: var(--clay);
  min-height: 1px;
}
.lib-dialog-actions {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
  margin-top: 24px;
  flex-wrap: wrap;
}
/* v4.1.12. One quiet line under the buttons (openDialog `foot`). */
.lib-dialog-box .lib-dialog-foot {
  margin-top: 12px;
  font-size: var(--t-12);
  color: var(--ink-3);
  text-align: right;
}

/* ================================================================== buttons */

.lib-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  height: 40px;
  padding: 0 20px;
  border: 1px solid var(--line-2);
  border-radius: var(--r-pill);
  background: var(--surface);
  color: var(--ink);
  font-family: var(--display);
  font-weight: 600;
  font-size:var(--t-13);
  letter-spacing: -.01em;
  cursor: pointer;
  transition: background var(--dur) var(--ease), transform var(--dur-press) ease-out,
              box-shadow var(--dur) var(--ease), opacity var(--dur) var(--ease);
}
.lib-btn:hover:not(:disabled) { box-shadow: var(--shadow); }
/* v4.1.3. The press, as .btn's (controls.css): held, 80 ms, never on hover. */
.lib-btn:active:not(:disabled):not([aria-disabled="true"]) { transform: scale(.98); }
.lib-btn:disabled { opacity: .4; cursor: not-allowed; }
.lib-btn[aria-busy="true"], .lib-btn[aria-busy="true"]:disabled { opacity: 1; cursor: progress; }
.lib-tile-media img[hidden] { display: none; }
.lib-btn--primary { background: var(--blue); border-color: var(--blue); color: var(--on-blue); }
.lib-btn--primary:hover:not(:disabled) { background: var(--blue-hover); }
/* Destructive, and it has to look it. Clay fill, not a red borrowed from
   nowhere, and never the same shape as the safe path beside it. */
.lib-btn--danger { background: var(--clay); border-color: var(--clay); color: #FFFFFF; }
.lib-btn--quiet { background: transparent; border-color: transparent; color: var(--ink-2); }
.lib-btn--quiet:hover:not(:disabled) {
  background: var(--sunken);
  color: var(--ink);
  transform: none;
  box-shadow: none;
}
.lib-btn--small { height:32px; padding:0 16px; font-size:var(--t-13); }

/* ========================================================== selection bar */

/* A zero-height sticky slot. The bar inside it floats above the grid, so it can
   appear and disappear without ever changing the height of anything. */
.lib-selbar-slot {
  position: sticky;
  bottom: 20px;
  height: 0;
  overflow: visible;
  z-index: 12;
}
/* Compact and centred: as wide as its buttons, not as wide as the page, so it
   covers as little of the grid as it can while a selection is being made. */
.lib-selbar {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  margin-inline: auto;
  width: max-content;
  max-width: 100%;
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  padding: 8px 8px 8px 16px;
  background: var(--raised);
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  box-shadow: var(--shadow-lift);
  animation: lib-rise 220ms var(--ease);
}
.lib-selbar-count {
  font-family: var(--display);
  font-weight: 600;
  font-size: var(--t-13);
  margin-right: 8px;
}
.lib-selbar-count .lib-mono { font-family: var(--mono); }
.lib-selbar-progress {
  font-size: var(--t-12);
  color: var(--ink-3);
  margin-right: 8px;
}

/* ================================================================ lightbox */

/* The caption bar used to be position:fixed at bottom:18px, pinned to the viewport and
   completely decoupled from the media's box, while the media was centred inside this
   element's padding. The clearance between them was therefore an accident between two
   unrelated numbers - 64px reserved above against 18px plus an undeclared bar height below -
   so it overlapped tall portrait media and left a big, aspect-dependent gap on landscape.
   What kept it positive at all was that .lib-light-cap b is nowrap.

   It is a two-row grid now: stage, then cap. The cap is IN FLOW, so it reserves its own
   space and the gap is the same at every aspect ratio. The four numbers below are the single
   source - the media's max-height derives from them, and fitVideo reads these very
   properties rather than carrying a second copy of the arithmetic in JavaScript. */
.lib-light {
  --lib-light-pad-y: 48px;
  --lib-light-pad-x: 24px;
  --lib-light-cap-h: 40px;
  --lib-light-gap: 16px;
  /* A video's native control strip lives in the bottom ~40px of its OWN box, over the last
     rows of the picture. The cap below can no longer cover it - it is in flow - but the
     controls still hide part of the clip, so the video is sized a strip shorter and sits that
     much higher than an image of the same shape. Reclaiming this would put the scrubber back
     over the footage. */
  --lib-light-video-extra: 44px;
  position: fixed;
  inset: 0;
  z-index: 70;
  display: grid;
  grid-template-rows: 1fr auto;
  justify-items: center;
  align-items: center;
  row-gap: var(--lib-light-gap);
  padding: var(--lib-light-pad-y) var(--lib-light-pad-x);
  background: rgba(8, 9, 11, .74);
  backdrop-filter: blur(18px);
  -webkit-backdrop-filter: blur(18px);
  animation: lib-fade 200ms var(--ease);
}
.lib-light-stage {
  grid-row: 1;
  position: relative;
  max-width: 100%;
  min-height: 0;        /* or the 1fr row refuses to shrink under its content */
  max-height: 100%;
  display: grid;
  place-items: center;
}
/* One element, the full file, image or video. Nothing is stacked under it. */
.lib-light-media {
  display: block;
  max-width: 100%;
  max-height: calc(100vh - (2 * var(--lib-light-pad-y)) - var(--lib-light-cap-h) - var(--lib-light-gap));
  width: auto;
  height: auto;
  object-fit: contain;
  border-radius: var(--r-media);
  background: var(--stage);
  grid-area: 1 / 1;
}
video.lib-light-media {
  min-width: min(640px, 100%);
  max-height: calc(100vh - (2 * var(--lib-light-pad-y)) - var(--lib-light-cap-h)
                   - var(--lib-light-gap) - var(--lib-light-video-extra));
}
.lib-light-spin {
  grid-area: 1 / 1;
  width: 22px;
  height: 22px;
  color: #FFFFFF;
}

.lib-light-close, .lib-light-nav {
  position: fixed;
  width: 44px;
  height: 44px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, .22);
  background: rgba(255, 255, 255, .1);
  color: #FFFFFF;
  cursor: pointer;
  transition: background var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.lib-light-close:hover, .lib-light-nav:hover {
  background: rgba(255, 255, 255, .2);
  transform: translateY(-2px);
}
.lib-light-close { top: 16px; left: 16px; }
.lib-light-nav--prev { top: 50%; left: 16px; margin-top: -22px; }
.lib-light-nav--next { top: 50%; right: 16px; margin-top: -22px; }
.lib-light-nav[disabled] { opacity: .3; cursor: default; }

.lib-light-cap {
  /* In flow, in the grid's second row. Its height is declared rather than left to whatever
     the padding and the line-height happen to add up to, because the stage above reserves
     exactly this much and a number nobody wrote down is a number that cannot be reserved. */
  grid-row: 2;
  min-height: var(--lib-light-cap-h);
  box-sizing: border-box;
  display: flex;
  align-items: center;
  gap: 12px;
  max-width: calc(100vw - (2 * var(--lib-light-pad-x)));
  padding: 8px 16px;
  border-radius: var(--r-pill);
  background: rgba(255, 255, 255, .1);
  border: 1px solid rgba(255, 255, 255, .18);
  color: #FFFFFF;
  font-size: var(--t-12);
}
.lib-light-cap b {
  font-family: var(--body);
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.lib-light-cap .lib-mono { color: rgba(255, 255, 255, .72); white-space: nowrap; }
/* When it was made is words, its length and size are figures (P42, here too). */
.lib-light-meta { color: rgba(255, 255, 255, .72); white-space: nowrap; }

/* On a phone the bar is wider than the screen: the name, the figures and up to four buttons
   in one row ran off the right edge, and Recreate and Upscale could not be reached at all.
   Under 600 px it wraps: the name on a line of its own, then the figures and the buttons, on
   as many rows as they need. The height the stage leaves for it is declared here as it is
   above (three rows at 390 px), and fitVideo reads the same property. */
@media (max-width: 600px) {
  .lib-light { --lib-light-cap-h: 132px; --lib-light-pad-x: 16px; }
  .lib-light-cap {
    flex-wrap: wrap;
    justify-content: center;
    align-content: center;
    gap: 8px 10px;
    width: 100%;
    padding: 12px 14px;
    border-radius: var(--r-card);
  }
  .lib-light-cap b { flex: 1 1 100%; text-align: center; }
  /* Compact here (fix round): at the size every other small button got, four of them took
     two rows in a wider font and the bar outgrew the 132 the stage leaves for it. */
  .lib-light-cap .lib-btn { flex: none; padding: 0 12px; font-size: var(--t-12); }
}

/* ================================================= empty, notices, loading */

.lib-empty {
  padding: 56px 28px;
  text-align: center;
  background: var(--surface);
  border: 1px solid var(--line-card, var(--line));
  border-radius: var(--r-card);
}
.lib-empty-tile {
  width: 44px;
  height: 44px;
  border-radius: var(--r-tile);
  background: var(--well, var(--sunken));
  color: var(--ink-2);
  display: grid;
  place-items: center;
  margin: 0 auto 16px;
}
.lib-empty b {
  display: block;
  font-family: var(--display);
  font-weight: 600;
  font-size: var(--t-16);
  letter-spacing: -.02em;
  margin-bottom: 6px;
}
.lib-empty p {
  margin: 0 auto;
  max-width: 48ch;
  font-size: var(--t-13);
  color: var(--ink-3);
  line-height: 1.55;
}
.lib-empty .lib-btn { margin-top: 20px; }

.lib-more { display: flex; justify-content: center; margin-top: 24px; }

.lib-note {
  margin: 0 0 16px;
  font-size: var(--t-12);
  color: var(--ink-3);
}
.lib-note b { font-weight: 600; color: var(--ink-2); }

.lib-spin {
  width: 14px;
  height: 14px;
  border-radius: 50%;
  border: 2px solid currentColor;
  border-top-color: transparent;
  opacity: .6;
  animation: lib-spin .8s linear infinite;
}
@keyframes lib-spin { to { transform: rotate(360deg); } }

/* ================================================================ channels */

/* v4.1.3 (D5). The picker is ONE pill in the library's bar: the channel shown, and a menu
   of the others with Manage channels inside. */
.lib-chan { display: flex; }
.lib-chan-pill {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  height: 40px;
  max-width: 240px;
  padding: 0 12px 0 16px;
  border: 1px solid var(--line-2);
  border-radius: var(--r-pill);
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-size: var(--t-13);
  font-weight: 600;
  cursor: pointer;
  transition: border-color var(--dur) var(--ease), background var(--dur) var(--ease);
}
.lib-chan-pill:hover { border-color: var(--ink-3); }
.lib-chan-pill[aria-expanded="true"] { border-color: var(--blue); }
.lib-chan-pill b {
  font-weight: inherit;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.lib-chan-caret { display: grid; place-items: center; color: var(--ink-3); }
.lib-chan-caret svg { transform: rotate(90deg); }
.lib-chan-dot {
  width: 8px;
  height: 8px;
  flex: 0 0 8px;
  border-radius: 50%;
  background: var(--ink-3);
}
.lib-chan-pill[data-on="1"] .lib-chan-dot { background: var(--blue); }
.lib-chan-n {
  font-family: var(--mono);
  font-weight: 500;
  font-size: var(--t-12);
  color: var(--ink-3);
}

.lib-chan-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 0;
}
.lib-chan-row b { font-family: var(--body); font-weight: 600; font-size: var(--t-13); }
.lib-chan-row .lib-chan-n { margin-left: auto; }
.lib-chan-row--archived b { color: var(--ink-3); }

/* v4.1.17 (B6). The picker and the kind switch's box: nothing of its own, so the bar lays them
   out as before. The dashboard on a phone makes it a row of chips (its own stylesheet). */
.lib-chips { display: contents; }

/* ============================================================== responsive */

@media (max-width: 900px) {
  .lib-folders { grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); }
}

@media (max-width: 600px) {
  .lib, .lib-tiles { --lib-tile-min: 150px; --lib-gap: 12px; }
  /* Where you are first, then what you can do, then the search on its own line. */
  /* Where you are first, then how full the storage is and Refresh on the same line; the
     storage line used to come before the trail. */
  .lib-bar { gap: 8px; }
  .lib-crumb { order: 1; margin-right: auto; }
  .lib-storage { order: 2; }
  .lib-bar > .lib-btn { order: 2; }
  .lib-kind { order: 3; margin-left: 0; }
  .lib-search { order: 4; flex-basis: 100%; margin-left: 0; }
  /* (v4.1.3 review: the picker gives way, with dots, so the kind switch is never pushed onto a
     line of its own by a wider fallback font.) */
  .lib-bar--lead > :first-child { order: 1; flex: 1 1 0; min-width: 0; }
  .lib-bar--lead .lib-chan { min-width: 0; }
  .lib-bar--lead .lib-chan-pill { max-width: 100%; min-width: 0; }
  .lib-bar--lead .lib-kind { flex: 0 0 auto; }
  .lib-bar--lead .lib-search { margin-left: 0; }
  /* The picker and the kind share a 390 screen's line. */
  .lib-bar--lead .lib-chan-pill { padding: 0 8px 0 12px; }
  .lib-bar--lead .lib-kind button { padding: 0 12px; }

  /* The trail collapses to the current folder plus a way back up. */
  .lib-crumb-back { display: inline-flex; }
  .lib-crumb-hide { display: none; }

  /* One per row. Two columns leaves about eight characters for a folder name
     once the icon and the actions button have taken their share. */
  .lib-folders { grid-template-columns: 1fr; }

  /* A popover pinned to a 28px button is unusable with a thumb, so the menu
     becomes a sheet on the bottom edge. */
  .lib-menu {
    left: 0 !important;
    right: 0 !important;
    top: auto !important;
    bottom: 0;
    width: 100%;
    min-width: 0;
    padding: 8px 8px calc(8px + env(safe-area-inset-bottom));
    border-radius: var(--r-card) var(--r-card) 0 0;
    animation: lib-sheet 220ms var(--ease);
  }
  @keyframes lib-sheet { from { opacity: 0; transform: translateY(16px); } }
  .lib-menu-item { height: 46px; padding: 0 14px; font-size: var(--t-14); }

  .lib-selbar { padding: 10px 10px 10px 16px; border-radius: var(--r-card); width: auto; }
  .lib-selbar-count { width: 100%; margin-bottom: 4px; }

  /* The PROPERTIES, not the padding. Overriding the shorthand here left every consumer -
     the media's max-height, and fitVideo - computing with the desktop numbers while the box
     had different ones, which is the decoupling this whole component was just fixed for.
     A phone has less room above and beside, and none to spare below a video's controls. */
  .lib-light {
    --lib-light-pad-y: 20px;
    --lib-light-pad-x: 12px;
  }
  .lib-light-nav--prev { left: 8px; }
  .lib-light-nav--next { right: 8px; }
}

/* The user is paying for the GPU while any of this plays. */
@media (prefers-reduced-motion: reduce) {
  .lib-tile, .lib-folder, .lib-btn, .lib-menu, .lib-dialog, .lib-dialog-box,
  .lib-light, .lib-selbar {
    animation: none !important;
    transition: none !important;
  }
  .lib-tile:hover, .lib-folder:hover, .lib-btn:hover:not(:disabled) { transform: none; }
  .lib-btn:active:not(:disabled):not([aria-disabled="true"]) { transform: none; }
  /* The things that turn or pulse for as long as something lasts: still, for somebody who
     asked for less motion. What they say is said in words beside them. */
  .lib-spin, .lib-skel { animation: none !important; }
  .lib-info-note, .lib-meter-track i, .lib-menu-item { animation: none !important; transition: none !important; }
}

/* =================================================================== how this was made

   F10, F11: moved here from the dashboard, so the pod's library draws the same panel
   (shared/recipe-dialog.js). The prompt as it was written, and the settings. */
.rec-body { display: flex; flex-direction: column; gap: 10px; max-height: 52vh; overflow: auto; }
.rec-prompt {
  margin: 0;
  padding: 10px 12px;
  background: var(--well);
  border-radius: var(--r-input, 12px);
  font-family: var(--body);
  font-size: var(--t-13);
  line-height: 1.55;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.rec-row { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 4px 16px; font-size: var(--t-13); }
.rec-row span:first-child { color: var(--ink-2); flex: none; }
.rec-row span:last-child { text-align: right; overflow-wrap: anywhere; }
.rec-note { margin: 0; font-size: var(--t-12); color: var(--ink-2); }
.rec-missing { margin: 0; padding-left: 16px; font-size: var(--t-12); color: var(--ink-2); }
/* F13. An upscaled clip's source: its name (a link where the host can show it), then what
   it was made from, on a ground of its own (P47: it was set in under a rule). That ground is
   --card: the dialog it sits in is --surface, and on --surface it did not stand apart at all. */
.rec-link {
  padding: 0; border: 0; background: none; font: inherit; color: var(--blue-text);
  text-decoration: underline; cursor: pointer; text-align: right; overflow-wrap: anywhere;
}
.rec-source { display: flex; flex-direction: column; gap: 10px; padding: 12px; border-radius: var(--r-input, 12px); background: var(--card); }

/* =================================================================== the licence popup

   F13 (shared/licence-dialog.js): the server's text as paragraphs and a list, then the box
   that has to be ticked. "Accept and continue" reads disabled until it is (it says why if
   pressed anyway). */
.lic-body { display: flex; flex-direction: column; gap: 10px; max-height: 56vh; overflow: auto; font-size: var(--t-13); line-height: 1.55; }
.lic-body p { margin: 0; }
.lic-body ul { margin: 0; padding-left: 20px; display: flex; flex-direction: column; gap: 6px; }
.lic-body a { color: var(--blue-text); text-decoration: underline; overflow-wrap: anywhere; }
.lic-body strong { font-weight: 600; }
.lic-check { display: flex; gap: 10px; align-items: flex-start; margin-top: 4px; font-weight: 500; cursor: pointer; }
.lic-check input { flex: none; width: 18px; height: 18px; margin: 2px 0 0; accent-color: var(--blue); }
.lib-btn[aria-disabled="true"] { opacity: .55; }
