/* ==========================================================================
   views.css — the six views plus the two full-bleed surfaces that only ever
   appear inside a view (the image stage and the lightbox).

   WHY this is separate from components.css: everything here is *composition* —
   how the shared components are arranged inside a particular screen. Nothing
   below redefines a component (.btn, .card, .thumb, .modal, .skeleton,
   .progress, .chip …); it only positions them and adds view-local scaffolding.

   Two rules govern every reveal in this file, both inherited from
   animations.css: (1) a class that carries an entrance animation always
   restates the final value too, so the motion kill-switch degrades to
   "already visible" instead of "invisible forever"; (2) grid images are
   revealed by *adding* an animation on .is-loaded and are never hidden first,
   because a peer module that forgets the flag must still show pictures.

   Declarations are packed onto shared lines where a rule is trivial — this
   file covers six screens and readability here means "one rule per glance",
   not "one property per line".

   Contract: ARCHITECTURE.md §12 (DOM ids) / §13 (tokens, class names).
   Every @media in the app lives in responsive.css.
   ========================================================================== */

/* ==========================================================================
   1. View shell — the router's switching surface
   ========================================================================== */

.view { display: none; flex-direction: column; height: 100%; min-height: 0; min-width: 0; }
.view--active { display: flex; }

/* Sticky glass header, so a toolbar survives a long scroll in .view__body. */
.view__header {
  position: sticky; top: 0; z-index: 2;
  display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--space-4);
  padding: var(--space-5) var(--space-6) var(--space-4);
  background: linear-gradient(var(--bg-0) 62%, transparent);
}
.view__heading { flex: 1 1 240px; min-width: 0; }
.view__title { font-size: 21px; font-weight: 650; letter-spacing: -0.02em; }
.view__subtitle { margin-top: 2px; font-size: 13px; color: var(--text-2); }
.view__body { flex: 1 1 auto; min-height: 0; padding: 0 var(--space-6) var(--space-7); }
.toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }

/* ==========================================================================
   2. Empty states — the variants index.html uses; base.css owns the base
   ========================================================================== */

.empty-state__hint { max-width: 42ch; font-size: 12.5px; line-height: 1.6; color: var(--text-2); }
.empty-state--sm { gap: var(--space-2); min-height: 0; padding: var(--space-5) var(--space-3); }
.empty-state--sm .empty-state__icon,
.empty-state--sm > svg { width: 36px; height: 36px; }
.empty-state--inline { flex-direction: row; min-height: 0; padding: var(--space-4) var(--space-5); font-size: 12.5px; }

/* Pure-CSS backstop for the JS `.hidden` toggle: a filled list hides its own
   empty state, so a module that forgets to toggle can never show both. */
#gallery-grid:not(:empty) + #gallery-empty,
#favorites-grid:not(:empty) + #favorites-empty,
#collections-list:not(:empty) + #collections-empty,
#history-grid:not(:empty) + #history-empty,
#tag-list:not(:empty) + #tag-empty,
#api-list:not(:empty) + #api-empty { display: none; }

/* ==========================================================================
   3. Explorer
   ========================================================================== */

.explorer {
  display: grid; grid-template-columns: minmax(0, 1fr) 304px;
  gap: var(--space-5); align-items: start;
  /* Its own scroll region: the explorer has no .view__body wrapper, so it must
     both fill the flex view and be allowed to shrink below its content. */
  flex: 1 1 auto; min-height: 0;
  padding: var(--space-5) var(--space-6) var(--space-6);
  overflow-y: auto; overscroll-behavior: contain;
}
.explorer__main,
.explorer__side { display: flex; flex-direction: column; gap: var(--space-4); min-width: 0; }

/* ---- Image stage ------------------------------------------------------
   A checkerboard well (so transparent PNGs read as transparent) under a very
   wide accent wash. overflow:hidden + the radius clip the reveal blur. */
.image-stage {
  --stage-check: color-mix(in srgb, var(--text-0) 4%, transparent);
  position: relative; display: grid; place-items: center;
  min-height: clamp(300px, 54vh, 640px); padding: var(--space-5);
  overflow: hidden;
  border: 1px solid var(--border); border-radius: var(--radius-lg);
  background-color: var(--bg-inset);
  background-image:
    radial-gradient(72% 82% at 50% -8%, var(--accent-soft), transparent 70%),
    repeating-conic-gradient(var(--stage-check) 0% 25%, transparent 0% 50%);
  background-size: 100% 100%, 22px 22px;
  background-position: 50% 0, 50% 50%;
  box-shadow: var(--shadow-2), inset 0 1px 0 var(--glass-border);
}

.image-stage__img {
  max-width: 100%; max-height: min(66vh, 720px); width: auto;
  object-fit: contain; border-radius: var(--radius-sm);
  /* Above #image-skeleton (z-index 1) so a partially-streamed image reveals the shimmer
     underneath rather than bare stage. */
  position: relative; z-index: 2;
  /* Safe to start hidden: the element ships with no src, and .is-loaded
     restates opacity:1 so killing the animation cannot strand it. */
  opacity: 0; transition: opacity var(--dur) var(--ease);
}

/* While bytes are arriving, hold the element fully visible with no transition so the
   browser's progressive render is actually seen building up. Fading it in would hide
   exactly the feedback this is here to provide. */
.image-stage.is-painting .image-stage__img { opacity: 1; transition: none; }
/* No reveal animation: the picture has already painted in progressively, so animating it
   again at the end reads as a flash. Opacity only. */
.image-stage__img.is-loaded { opacity: 1; }

/* NSFW veil (settings.blurNsfw); lifts on hover or keyboard focus. */
.image-stage__img.is-blurred,
.thumb.is-blurred .thumb__img { filter: blur(22px) saturate(0.7); transition: filter var(--dur-slow) var(--ease); }
.image-stage:hover .image-stage__img.is-blurred,
.image-stage:focus-within .image-stage__img.is-blurred,
.thumb.is-blurred:hover .thumb__img,
.thumb.is-blurred:focus-within .thumb__img { filter: none; }

/* The skeleton overlays the whole well rather than sitting beside the image. */
#image-skeleton { position: absolute; inset: 0; z-index: 1; border-radius: inherit; }

/* Hairline determinate bar pinned to the stage's top edge. The track is
   transparent, so a zero-width bar is simply invisible — no JS flag needed. */
#load-progress {
  position: absolute; inset: 0 0 auto; z-index: 4;
  height: 3px; border-radius: 0; background: transparent; box-shadow: none;
}
.image-stage.is-loading > #load-progress { background: color-mix(in srgb, var(--text-0) 8%, transparent); }

#stage-badges {
  position: absolute; top: var(--space-3); left: var(--space-3); z-index: 3;
  display: flex; flex-wrap: wrap; gap: var(--space-2);
  max-width: calc(100% - 92px);
  pointer-events: none; /* re-enabled per child so future chips stay clickable */
}
#stage-badges > * { pointer-events: auto; backdrop-filter: var(--blur); }

/* Appears on stage hover / keyboard focus; never on a cold stage. */
.image-stage__fullscreen {
  position: absolute; top: var(--space-3); right: var(--space-3); z-index: 3;
  opacity: 0; transform: translateY(-4px);
  background: var(--glass); border: 1px solid var(--glass-border);
  backdrop-filter: var(--blur);
  transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.image-stage:hover .image-stage__fullscreen,
.image-stage:focus-within .image-stage__fullscreen,
.image-stage__fullscreen:focus-visible { opacity: 1; transform: none; }

.image-error {
  position: absolute; inset: 0; z-index: 5;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: var(--space-4); padding: var(--space-6); text-align: center;
  background: color-mix(in srgb, var(--bg-inset) 78%, transparent);
  backdrop-filter: blur(6px);
}
.image-error__icon {
  width: 46px; height: 46px; padding: 11px;
  color: var(--danger); background: var(--danger-soft); border-radius: var(--radius-full);
}
.image-error__text { max-width: 46ch; font-size: 13.5px; color: var(--text-1); }

/* ---- Action bar: wrapping row on a recessed panel --------------------- */
.action-bar {
  display: flex; flex-wrap: wrap; gap: var(--space-2); padding: var(--space-3);
  background: var(--bg-inset); border: 1px solid var(--border); border-radius: var(--radius-md);
  box-shadow: inset 0 1px 2px rgb(0 0 0 / 0.18);
}

/* ---- Info panel: key/value grid, monospace values --------------------- */
.info-panel {
  display: grid; gap: 1px; padding: var(--space-2) var(--space-3);
  background: var(--bg-2); border: 1px solid var(--border);
  border-radius: var(--radius-md); box-shadow: var(--shadow-1);
}
.info-panel__row {
  display: grid; grid-template-columns: minmax(0, 96px) minmax(0, 1fr);
  align-items: baseline; gap: var(--space-3);
  padding: var(--space-2) 0; border-bottom: 1px solid var(--border);
}
.info-panel__row:last-child { border-bottom: 0; }
.info-panel__row--wide { grid-template-columns: minmax(0, 1fr); gap: 2px; }
.info-panel__key {
  font-size: 11.5px; font-weight: 600; letter-spacing: 0.04em;
  text-transform: uppercase; color: var(--text-2);
}
.info-panel__val { min-width: 0; font-size: 13px; color: var(--text-0); overflow-wrap: anywhere; }

/* Click-to-copy affordance; viewer.js promotes it to a real control. */
#info-url { cursor: copy; color: var(--text-1); transition: color var(--dur-fast) var(--ease); }
#info-url:hover { color: var(--accent); text-decoration: underline; text-underline-offset: 3px; }
#info-url.is-copied { color: var(--success); }

/* ==========================================================================
   4. History dock — one horizontal, snapping row
   ========================================================================== */

.history-strip__grid {
  --history-thumb: 104px;
  display: flex; flex-direction: row; align-items: stretch; gap: var(--space-3);
  /* Beats .scroll-area (same specificity, later file): this axis is inverted. */
  overflow-x: auto; overflow-y: hidden;
  padding: var(--space-3) var(--space-5);
  scroll-snap-type: x proximity; scroll-padding-inline: var(--space-5);
  /* Cards dissolve into the dock edges instead of being sliced off. */
  mask-image: linear-gradient(90deg, transparent, #000 26px, #000 calc(100% - 26px), transparent);
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 26px, #000 calc(100% - 26px), transparent);
}
.history-strip__grid > * {
  flex: 0 0 auto; width: var(--history-thumb); height: var(--history-thumb);
  scroll-snap-align: center;
}

/* ==========================================================================
   5. Gallery — true masonry via CSS columns
   ========================================================================== */

.masonry { --cols: 4; column-count: var(--cols); column-gap: var(--space-4); padding-top: var(--space-1); }
.masonry__item {
  break-inside: avoid; -webkit-column-break-inside: avoid;
  margin-bottom: var(--space-4); border-radius: var(--radius-md); overflow: hidden;
  transition: transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.masonry__item:hover,
.masonry__item:focus-within { transform: translateY(-3px); box-shadow: var(--shadow-2); }

.masonry .thumb__img,
.media-grid .thumb__img,
.history-strip__grid .thumb__img { width: 100%; height: 100%; object-fit: cover; background: var(--bg-inset); }

/* Masonry columns are variable-height, so here the image keeps its own ratio. */
.masonry .thumb__img { height: auto; object-fit: contain; }

/* Additive reveal only: a missing .is-loaded flag still shows the picture. */
.masonry .thumb__img.is-loaded,
.media-grid .thumb__img.is-loaded { animation: fade-in var(--dur-slow) var(--ease) both; }

.gallery-sentinel { display: grid; place-items: center; min-height: 68px; padding: var(--space-5); color: var(--text-2); }

/* ==========================================================================
   6. Favorites & Collections
   ========================================================================== */

.media-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(var(--thumb), 1fr));
  gap: var(--space-4); align-content: start;
}
/* The grid owns the geometry so the thumb component needs no aspect-ratio. */
.media-grid > * { aspect-ratio: 1 / 1; min-width: 0; }

.collections-list {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(248px, 1fr));
  gap: var(--space-4); align-content: start;
}

/* JS sets --collection-color inline from Collection.color; the accent is the
   documented default so an un-coloured card still looks intentional. */
.collection-card,
#collections-list > .card {
  --collection-color: var(--accent);
  position: relative; display: flex; flex-direction: column;
  overflow: hidden; text-align: left;
  background: var(--bg-2); border: 1px solid var(--border);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-1);
  transition: transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease),
              border-color var(--dur) var(--ease);
}
.collection-card:hover,
.collection-card:focus-within,
#collections-list > .card:hover {
  transform: translateY(-3px);
  border-color: color-mix(in srgb, var(--collection-color) 44%, var(--border));
  box-shadow: var(--shadow-2);
}
.collection-card.is-selected { border-color: var(--collection-color); box-shadow: var(--shadow-glow); }

/* Colour stripe as a pseudo-element (no extra node needed) plus an explicit
   class for modules that render one anyway. */
.collection-card::before,
.collection-card__stripe {
  content: ""; display: block; height: 4px; flex: none;
  background: linear-gradient(90deg, var(--collection-color),
    color-mix(in srgb, var(--collection-color) 30%, transparent));
}
.collection-card__mosaic {
  display: grid; grid-template-columns: 1fr 1fr; grid-template-rows: 1fr 1fr;
  gap: 2px; aspect-ratio: 4 / 3; background: var(--bg-inset);
}
.collection-card__cell {
  position: relative; overflow: hidden;
  background: var(--bg-inset)
    repeating-conic-gradient(color-mix(in srgb, var(--text-0) 3%, transparent) 0% 25%, transparent 0% 50%)
    50% / 16px 16px;
}
.collection-card__cell > img { width: 100%; height: 100%; object-fit: cover; }
.collection-card__body { display: flex; align-items: center; gap: var(--space-3); padding: var(--space-3) var(--space-4); }
.collection-card__name {
  flex: 1 1 auto; min-width: 0; font-size: 14px; font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.collection-card__meta { font-size: 12px; color: var(--text-2); }
.collection-card__actions { display: flex; gap: var(--space-1); opacity: 0; transition: opacity var(--dur) var(--ease); }
.collection-card:hover .collection-card__actions,
.collection-card:focus-within .collection-card__actions { opacity: 1; }

.collection-detail { margin-top: var(--space-6); padding-top: var(--space-5); border-top: 1px solid var(--border); }
.collection-detail__head { display: flex; align-items: center; gap: var(--space-3); margin-bottom: var(--space-4); }
.collection-detail__title { font-size: 17px; font-weight: 650; }

/* ==========================================================================
   7. Stats
   ========================================================================== */

.stat-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(148px, 1fr));
  gap: var(--space-3); margin-bottom: var(--space-5);
}
.chart-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: var(--space-4); }
.chart-card {
  display: flex; flex-direction: column; gap: var(--space-3); padding: var(--space-4);
  background: var(--bg-2); border: 1px solid var(--border);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-1);
}
.chart-card__title { font-size: 12.5px; font-weight: 600; letter-spacing: 0.02em; color: var(--text-1); }

/* LOAD-BEARING: Chart.js with responsive:true measures its parent. Without a
   *resolved* height here it re-measures its own last size on every resize tick
   and the canvas grows without bound. A fixed-height, position:relative
   wrapper ends that loop and also gives microcharts.js a deterministic box. */
.chart-card__canvas {
  position: relative; height: var(--chart-h, 252px); overflow: hidden;
  border-radius: var(--radius-sm); background: var(--bg-inset);
}
.chart-card__canvas > canvas {
  position: absolute; inset: 0;
  width: 100% !important; height: 100% !important;
  max-width: 100%; max-height: 100%;
}

.compare-card {
  display: flex; flex-direction: column; gap: var(--space-3);
  margin-top: var(--space-5); padding: var(--space-4);
  background: var(--bg-2); border: 1px solid var(--border);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-1);
}
.table-wrap { overflow-x: auto; overflow-y: auto; max-height: 420px; border: 1px solid var(--border); border-radius: var(--radius-md); }

#compare-table { font-size: 13px; }
#compare-table th {
  position: sticky; top: 0; z-index: 1;
  padding: var(--space-3) var(--space-4);
  font-size: 11px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--text-2); white-space: nowrap;
  background: var(--bg-3); border-bottom: 1px solid var(--border);
}
#compare-table td { padding: var(--space-3) var(--space-4); border-bottom: 1px solid var(--border); vertical-align: middle; }
#compare-table tbody tr { transition: background-color var(--dur-fast) var(--ease); }
#compare-table tbody tr:hover { background: var(--bg-3); }
#compare-table tbody tr:last-child td { border-bottom: 0; }
/* Latency + Attempts are numeric by contract (the <thead> in index.html is static). */
#compare-table td:nth-child(3),
#compare-table td:nth-child(4) {
  font-family: var(--font-mono); font-variant-numeric: tabular-nums;
  text-align: right; white-space: nowrap;
}

/* ==========================================================================
   8. Settings
   ========================================================================== */

.settings-body { display: flex; flex-direction: column; gap: var(--space-5); max-width: 760px; }
.settings-group {
  padding: var(--space-2) var(--space-5) var(--space-3);
  background: var(--bg-2); border: 1px solid var(--border);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-1);
}
.settings-group__title {
  display: flex; align-items: center; gap: var(--space-2);
  padding: var(--space-4) 0 var(--space-2);
  font-size: 11px; font-weight: 600; letter-spacing: 0.09em; text-transform: uppercase;
  color: var(--text-2);
}
.settings-group__hint { padding-bottom: var(--space-2); font-size: 12.5px; color: var(--text-2); }

/* Label + description left, control hard-right, hairline between rows.
   Authored to work whether the two text nodes are siblings of the control or
   wrapped in a .settings-row__text block. */
.settings-row {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, auto);
  align-items: center; column-gap: var(--space-5); row-gap: 2px;
  padding: var(--space-4) 0; border-bottom: 1px solid var(--border);
}
.settings-row:last-child { border-bottom: 0; }
.settings-row__text,
.settings-row__label,
.settings-row__desc { grid-column: 1; min-width: 0; }
.settings-row__label { font-size: 13.5px; font-weight: 600; color: var(--text-0); }
.settings-row__desc { font-size: 12.5px; line-height: 1.55; color: var(--text-2); }
.settings-row__control {
  grid-column: 2; grid-row: 1 / span 2; justify-self: end;
  display: flex; align-items: center; gap: var(--space-2); min-width: 0;
}
.settings-row__control .input,
.settings-row__control .select { min-width: 168px; }

/* ==========================================================================
   9. Lightbox
   ========================================================================== */

.lightbox {
  position: fixed; inset: 0; z-index: var(--z-lightbox);
  display: none; place-items: center; overflow: hidden;
  padding: calc(var(--space-8) + var(--space-4)) var(--space-5) var(--space-7);
  background: color-mix(in srgb, var(--bg-inset) 84%, transparent);
  backdrop-filter: blur(22px) saturate(120%);
  -webkit-backdrop-filter: blur(22px) saturate(120%);
}
.lightbox.is-open { display: grid; animation: fade-in var(--dur) var(--ease) both; }

.lightbox__toolbar {
  position: absolute; top: var(--space-4); left: 50%; transform: translateX(-50%);
  display: flex; align-items: center; gap: var(--space-1); padding: var(--space-2);
  background: var(--glass); border: 1px solid var(--glass-border);
  border-radius: var(--radius-full); box-shadow: var(--shadow-3);
  backdrop-filter: var(--blur); -webkit-backdrop-filter: var(--blur);
}
.lightbox__sep { width: 1px; height: 22px; margin: 0 var(--space-1); background: var(--glass-border); }

.lightbox__figure {
  display: flex; flex-direction: column; align-items: center; gap: var(--space-3);
  max-width: 100%; max-height: 100%; min-height: 0;
}

/* Pan/zoom is transform-only so it never triggers layout. lightbox.js may
   drive it through these custom properties or by writing style.transform —
   both work, since an inline transform simply wins over this declaration. */
.lightbox__img {
  max-width: 100%; max-height: 100%; width: auto; object-fit: contain;
  border-radius: var(--radius-md); box-shadow: var(--shadow-3);
  cursor: grab; touch-action: none; user-select: none; -webkit-user-drag: none;
  transform: translate3d(var(--pan-x, 0px), var(--pan-y, 0px), 0) scale(var(--zoom, 1));
  transform-origin: center center; will-change: transform;
  transition: transform var(--dur) var(--ease-out);
}
/* Dragging must feel instantaneous — no easing to fight the pointer. */
.lightbox__img.is-dragging,
.lightbox.is-dragging .lightbox__img { cursor: grabbing; transition: none; }

.lightbox__caption {
  flex: none; max-width: min(880px, 100%); padding: var(--space-2) var(--space-4);
  font-size: 12.5px; color: var(--text-1); text-align: center;
  background: var(--glass); border: 1px solid var(--glass-border);
  border-radius: var(--radius-full);
  backdrop-filter: var(--blur); -webkit-backdrop-filter: var(--blur);
}
.lightbox__caption:empty { display: none; }

/* --------------------------------------------------------------------------
   NSFW veil, remaining surfaces.
   The stage and .thumb hooks are defined above; gallery and favorite cards use
   the same .is-blurred convention so the view modules only ever toggle one class.
   -------------------------------------------------------------------------- */
.gallery-card.is-blurred .gallery-card__img,
.fav-card.is-blurred .fav-card__media img,
.collection-card.is-blurred .collection-card__mosaic img {
  filter: blur(18px) saturate(0.7);
  transition: filter var(--dur-slow) var(--ease);
}
.gallery-card.is-blurred:hover .gallery-card__img,
.gallery-card.is-blurred:focus-within .gallery-card__img,
.fav-card.is-blurred:hover .fav-card__media img,
.fav-card.is-blurred:focus-within .fav-card__media img {
  filter: none;
}

/* ==========================================================================
   Sidebar: source cards + tag rows.
   These are built in js/ui/sidebar.js and had no rules at all, which is why
   badge/name/meta/latency collapsed into one run-on line.
   ========================================================================== */

#api-list { display: flex; flex-direction: column; gap: var(--space-2); }

.api-card {
  display: flex; align-items: center; gap: var(--space-3); inline-size: 100%;
  padding: var(--space-2) var(--space-3);
  background: var(--bg-2); border: 1px solid var(--border);
  border-radius: var(--radius-md); cursor: pointer; text-align: start;
  color: var(--text-1);
  transition: background var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease),
              transform var(--dur-fast) var(--ease);
}
.api-card:hover { background: var(--bg-3); border-color: var(--border-strong); transform: translateY(-1px); }
.api-card.is-active {
  border-color: var(--accent); background: var(--accent-soft); color: var(--text-0);
  box-shadow: inset 0 0 0 1px var(--accent);
}
.api-card.is-disabled { opacity: .5; cursor: not-allowed; }
.api-card.is-disabled:hover { transform: none; background: var(--bg-2); }

.api-card__badge {
  flex: 0 0 auto; display: grid; place-items: center;
  inline-size: 26px; block-size: 26px; border-radius: var(--radius-sm);
  font-size: .68rem; font-weight: 700; color: #fff; letter-spacing: .02em;
}
.api-card__body { display: flex; flex-direction: column; gap: 1px; min-inline-size: 0; flex: 1 1 auto; }
.api-card__name { font-size: .9rem; font-weight: 600; color: inherit; }
.api-card__meta { font-size: .72rem; color: var(--text-2); }
.api-card__latency { flex: 0 0 auto; font-size: .72rem; color: var(--text-2); font-variant-numeric: tabular-nums; }

/* --- saved tags --- */
#tag-list { display: flex; flex-direction: column; gap: 2px; list-style: none; margin: 0; padding: 0; }

.tag-row {
  display: flex; align-items: center; gap: var(--space-1);
  border-radius: var(--radius-sm); padding-inline-end: var(--space-1);
  transition: background var(--dur-fast) var(--ease);
}
.tag-row:hover { background: var(--bg-3); }
.tag-row.is-selected { background: var(--accent-soft); box-shadow: inset 2px 0 0 var(--accent); }

.tag-row__select {
  flex: 1 1 auto; min-inline-size: 0; text-align: start; cursor: pointer;
  background: none; border: 0; color: var(--text-1);
  padding: var(--space-2) var(--space-2); font-size: .88rem; border-radius: var(--radius-sm);
}
.tag-row.is-selected .tag-row__select { color: var(--text-0); font-weight: 600; }
.tag-row__label { display: block; }

.tag-row__pin, .tag-row__del {
  flex: 0 0 auto; display: grid; place-items: center;
  inline-size: 26px; block-size: 26px; padding: 0;
  background: none; border: 0; cursor: pointer; border-radius: var(--radius-xs);
  color: var(--text-2); opacity: 0;
  transition: opacity var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease),
              background var(--dur-fast) var(--ease);
}
.tag-row:hover .tag-row__pin, .tag-row:hover .tag-row__del,
.tag-row__pin:focus-visible, .tag-row__del:focus-visible { opacity: 1; }
/* A pinned tag keeps its marker visible even when the row is not hovered. */
.tag-row__pin.is-active { opacity: 1; color: var(--accent); }
.tag-row__pin:hover { color: var(--text-0); background: var(--bg-2); }
.tag-row__del:hover { color: var(--danger); background: color-mix(in srgb, var(--danger) 14%, transparent); }
.tag-row__pin .icon, .tag-row__del .icon { inline-size: 15px; block-size: 15px; }

/* --- recent searches --- */
.search-history { display: none; flex-direction: column; gap: 2px; margin-block-start: var(--space-2); }
.search-history.is-open { display: flex; }
.search-history__item, .search-history__clear {
  text-align: start; background: none; border: 0; cursor: pointer;
  padding: var(--space-1) var(--space-2); border-radius: var(--radius-xs);
  color: var(--text-2); font-size: .82rem;
}
.search-history__item:hover { background: var(--bg-3); color: var(--text-0); }
.search-history__clear { color: var(--danger); align-self: flex-start; }

/* ==========================================================================
   Add-tag dialog
   ========================================================================== */
.add-tag { display: flex; flex-direction: column; gap: var(--space-4); }
.add-tag__groups { display: flex; flex-direction: column; gap: var(--space-4); max-block-size: 46vh; overflow-y: auto; }
.add-tag__group { display: flex; flex-direction: column; gap: var(--space-2); }
.add-tag__chips { display: flex; flex-wrap: wrap; gap: var(--space-2); }

/* ==========================================================================
   Gallery + favourite cards
   ========================================================================== */
.gallery-card__media { position: relative; overflow: hidden; border-radius: var(--radius-md); }
.gallery-card__actions {
  position: absolute; inset-block-end: var(--space-2); inset-inline-end: var(--space-2);
  display: flex; gap: var(--space-1);
  opacity: 0; transform: translateY(6px);
  transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.gallery-card:hover .gallery-card__actions,
.gallery-card:focus-within .gallery-card__actions { opacity: 1; transform: none; }
.gallery-card__meta {
  display: flex; justify-content: space-between; gap: var(--space-2);
  padding: var(--space-2) var(--space-1) 0; font-size: .78rem; color: var(--text-1);
}

.fav-card { position: relative; }
.fav-card__check { position: absolute; inset-block-start: var(--space-2); inset-inline-start: var(--space-2); z-index: 2; }
.fav-card__check input { accent-color: var(--accent); inline-size: 17px; block-size: 17px; cursor: pointer; }
.fav-card__body { display: flex; flex-direction: column; gap: 2px; padding: var(--space-2); min-inline-size: 0; }
.fav-card__tag { font-size: .86rem; font-weight: 600; color: var(--text-0); }
.fav-card__meta, .fav-card__date { font-size: .74rem; }
.fav-card__dots { display: flex; gap: 4px; margin-block-start: var(--space-1); }
.fav-card.is-dragging { opacity: .5; }

/* Empty cell keeps a sparse collection cover square instead of collapsing. */
.collection-card__blank { background: var(--bg-inset, var(--bg-3)); border-radius: var(--radius-xs); }

/* ==========================================================================
   Settings + stats extras
   ========================================================================== */
.settings-panel { display: flex; flex-direction: column; gap: var(--space-6); }
.settings-row__hint { font-size: .78rem; line-height: 1.4; }

.compare-thumb {
  inline-size: 52px; block-size: 52px; object-fit: cover;
  border-radius: var(--radius-sm); border: 1px solid var(--border);
}

/* Tag caption inside a history thumbnail's hover overlay. */
.thumb__tag {
  display: block; font-size: .7rem; font-weight: 600; color: #fff;
  text-shadow: 0 1px 3px rgb(0 0 0 / .8); line-height: 1.2;
}
