/* ---------- modals ---------- */
/* While any modal is open the body is pinned (fixed at its current scroll offset)
   so the page behind the backdrop can't scroll and the scroll position is exactly
   preserved on close -- JS sets top to -scrollY and restores it (see setBodyScrollLock). */
body.modal-open { position: fixed; width: 100%; overflow: hidden; }

.modal { position: fixed; inset: 0; background: rgba(12,8,24,0.72); backdrop-filter: blur(4px);
  display: flex; align-items: center; justify-content: center; z-index: 50; padding: 24px; }
.modal-card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg);
  width: 100%; max-width: 460px; max-height: 88vh; overflow: auto; position: relative; }
.modal-card.modal-wide { max-width: 680px; }
.modal-head { display: flex; align-items: center; justify-content: space-between;
  padding: 18px 20px 8px; font-family: 'Fredoka', sans-serif; font-size: 19px; }
.modal-close { background: none; border: none; color: var(--muted); font-size: 26px; cursor: pointer; line-height: 1; }
.modal-close:hover { color: var(--coral); }
/* profile modal: anchor the × to the top-right corner (it was sitting in-flow,
   misaligned). z-index so it stays clickable above the header content. */
.modal-profile .modal-close { position: absolute; top: 10px; right: 14px; z-index: 3; }
.modal.locked .modal-close { display: none; }
.modal-body { padding: 8px 20px 22px; }
.field-label { display: block; font-size: 12px; text-transform: uppercase; letter-spacing: 0.8px;
  color: var(--muted); margin: 12px 0 8px; font-weight: 600; }
.label-opt { text-transform: none; letter-spacing: 0; font-weight: 400; color: var(--muted); opacity: 0.7; }
/* position:relative so an absolutely-positioned autocomplete menu inserted after
   an input (attachRosterAutocomplete) anchors to THIS row, not a distant ancestor.
   Without it the persona/merge name menus mis-positioned after the profile-edit
   scroll container was added. */
.row { display: flex; gap: 8px; position: relative; }
.row input { flex: 1; padding: 10px 14px; border-radius: 999px; border: 1px solid var(--border);
  background: var(--bg); color: var(--text); font-family: inherit; font-size: 14px; outline: none; }
.row input:focus { border-color: var(--coral); }
.field-note { font-size: 12px; color: var(--muted); line-height: 1.6; margin: 10px 0 0; }
.field-note code { background: var(--bg); padding: 1px 6px; border-radius: 6px; color: var(--lavender); font-size: 11.5px; }
.source-link { color: var(--lavender); text-decoration: none; border-bottom: 1px solid transparent; }
.source-link:hover { border-bottom-color: var(--lavender); }
.source-ext { color: var(--muted); font-size: 10px; }
.scrape-limits { display: flex; gap: 10px; margin-top: 10px; flex-wrap: wrap; }
.scrape-limits label { display: flex; flex-direction: column; gap: 4px; font-size: 11px;
  text-transform: uppercase; letter-spacing: 0.5px; color: var(--muted); font-weight: 600; flex: 1 1 96px; min-width: 96px; }
.scrape-limits input, .scrape-limits select { width: 100%; padding: 8px 12px; border-radius: 10px;
  border: 1px solid var(--border); background: var(--bg); color: var(--text); font-family: inherit;
  font-size: 13.5px; text-transform: none; letter-spacing: 0; font-weight: 400; outline: none; }
.scrape-limits input:focus { border-color: var(--coral);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--coral) 18%, transparent); }

/* Rounded-rect select that matches the text inputs it sits beside (the scrape/
   backfill limit rows + the recurring-source hint). Native-appearance reset with
   a chevron drawn in, so it doesn't render as a chunky OS control. Reused so the
   media-kind dropdowns look identical everywhere. */
.styled-select { appearance: none; -webkit-appearance: none; cursor: pointer;
  padding: 8px 28px 8px 12px; border-radius: 10px; border: 1px solid var(--border);
  background-color: var(--bg); color: var(--text); font-family: inherit; font-size: 13.5px;
  outline: none; background-repeat: no-repeat; background-position: right 10px center;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23888' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E"); }
.scrape-limits .styled-select { width: 100%; }
.styled-select:hover { border-color: var(--coral); }
.styled-select:focus { border-color: var(--coral);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--coral) 18%, transparent); }
.divider { text-align: center; color: var(--muted); font-size: 12px; margin: 18px 0 6px; position: relative; }
.divider::before, .divider::after { content: ""; position: absolute; top: 50%; width: 42%; height: 1px; background: var(--border); }
.divider::before { left: 0; } .divider::after { right: 0; }
.checkbox-row { display: flex; align-items: center; gap: 8px; font-size: 13.5px; margin: 10px 0; cursor: pointer; }
input[type="file"] { width: 100%; font-size: 13px; color: var(--muted); }
.add-status { font-size: 13px; color: var(--mint); margin-top: 12px; min-height: 18px; }
.meta-line { font-size: 12px; color: var(--muted); margin: 6px 0; }

/* ---------- dedicated upload modal ---------- */
.drop-zone { display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 4px; padding: 30px 20px; border: 2px dashed var(--border); border-radius: var(--radius);
  background: var(--bg); color: var(--muted); cursor: pointer; text-align: center;
  transition: border-color 0.15s, background 0.15s, color 0.15s; outline: none; }
.drop-zone:hover, .drop-zone:focus-visible { border-color: var(--coral); color: var(--coral); }
.drop-zone.dragover { border-color: var(--coral); background: rgba(255,122,140,0.08); color: var(--coral); }
.drop-zone-title { font-size: 14px; font-weight: 600; margin: 4px 0 0; color: var(--text); }
.drop-zone.dragover .drop-zone-title { color: var(--coral); }
.drop-zone-sub { font-size: 12px; margin: 0; }

.upload-preview { display: grid; grid-template-columns: repeat(auto-fill, minmax(74px, 1fr));
  gap: 8px; margin-top: 12px; max-height: 214px; overflow-y: auto; padding: 2px; }
.up-thumb { position: relative; aspect-ratio: 1/1; border-radius: 8px; overflow: hidden;
  border: 1px solid var(--border); background: var(--surface-2); }
.up-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.up-thumb-rm { position: absolute; top: 3px; right: 3px; width: 20px; height: 20px; border: none;
  border-radius: 50%; background: rgba(12,8,24,0.72); color: #fff; font-size: 14px; line-height: 1;
  cursor: pointer; display: flex; align-items: center; justify-content: center;
  opacity: 0; transition: opacity 0.12s, background 0.12s; }
.up-thumb:hover .up-thumb-rm { opacity: 1; }
.up-thumb-rm:hover { background: var(--coral); color: var(--ink); }

.upload-actions { display: flex; align-items: center; justify-content: space-between;
  gap: 12px; margin-top: 16px; }
.upload-actions .add-status { margin: 0; flex: 1 1 auto; }
.upload-actions .btn-accent { flex: 0 0 auto; }

/* scrape progress */
.scrape-progress { margin-top: 14px; padding: 13px; border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg); }
.progress-head { display: flex; align-items: center; gap: 10px; margin-bottom: 9px; font-size: 13px; }
.progress-head .btn-cancel { margin-left: auto; padding: 5px 14px; color: var(--coral); border-color: var(--coral); }
.spinner { width: 15px; height: 15px; border: 2.5px solid var(--border); border-top-color: var(--coral);
  border-radius: 50%; animation: spin 0.8s linear infinite; display: inline-block; flex: none; }
@keyframes spin { to { transform: rotate(360deg); } }
/* recluster loader: shown in place of the cluster grid while grouping runs */
.cluster-loading { display: flex; align-items: center; justify-content: center; gap: 12px;
  padding: 60px 20px; color: var(--muted); font-size: 15px; grid-column: 1 / -1; }
.cluster-loading .spinner { width: 22px; height: 22px; }
/* spinner-only variant (stats load): centered spinner, no label */
.cluster-loading.spinner-only { padding: 48px 20px; }
.cluster-loading.spinner-only .spinner { width: 26px; height: 26px; }

/* Manage people list (stats) */
.managed-people-list { display: flex; flex-direction: column; margin-top: 8px;
  border-radius: 7px; overflow: hidden; }
.managed-person { display: flex; align-items: center; gap: 10px; padding: 4px 10px;
  font-size: 13px; }
.managed-person:nth-child(odd) { background: var(--surface-2); }
.managed-person:hover { background: var(--surface); }
.managed-person .mp-name { font-weight: 600; text-decoration: none; }
.managed-person .mp-name:hover { text-decoration: none; color: var(--coral); }
.managed-person .mp-guest { font-size: 10px; }
.managed-person .mp-count { color: var(--muted); font-size: 12px; margin-left: auto; }
.managed-person .mp-del { color: var(--coral); border-color: transparent;
  padding: 2px 8px; font-size: 12px; }
.managed-person .mp-del:hover { border-color: var(--coral); }
.ss-filter { width: 100%; margin-top: 8px; padding: 7px 10px; border-radius: 7px;
  border: 1px solid var(--border); background: var(--surface); color: var(--text);
  font-size: 14px; box-sizing: border-box; }
.progress-bar { height: 7px; background: var(--surface-2); border-radius: 999px; overflow: hidden; }
.progress-fill { height: 100%; width: 0; background: linear-gradient(90deg, var(--coral), var(--lavender));
  border-radius: 999px; transition: width 0.3s ease; }
.progress-fill.indeterminate { width: 40% !important; animation: slide 1.2s ease-in-out infinite; }
@keyframes slide { 0% { margin-left: -40%; } 100% { margin-left: 100%; } }
.progress-counts { font-size: 12px; color: var(--muted); margin-top: 7px; }

/* ---------- photo detail ---------- */
.modal-photo { max-width: 1240px; }
/* The photo modal owns its OWN two-column scrolling: the card itself does not
   scroll (that produced a 3rd, conflicting scrollbar). Instead the body is a
   fixed-height box and EACH column scrolls independently inside it, so the image
   side and the detail side no longer fight over one shared scroll (the dead-space
   / overscroll bug). When the columns wrap on a narrow screen (see below), we
   hand scrolling back to the card so it's one natural scroll again. */
.modal-photo { overflow: hidden; height: 88vh; display: flex; flex-direction: column; }
/* the close button is position:absolute (out of flow), so the body is the sole
   in-flow child and fills the card's height. NO flex-wrap here: wrapping breaks
   align-items:stretch height propagation, so the columns sized to content and the
   card (overflow:hidden) clipped everything with no scroll. The narrow-screen
   stack is handled explicitly by the media query below (flex-direction:column). */
.modal-photo-body { display: flex; gap: 0; position: relative;
  flex: 1; min-height: 0; align-items: stretch; }
.photo-stage { position: relative; flex: 3 1 560px; min-height: 320px; display: flex;
  align-items: center; justify-content: center; background: var(--bg);
  overflow-y: auto; }
/* single image: fit inside the stage (which is now the fixed body height) so it
   centers without nudging the stage into its own scroll. Stacked frames override
   this (they're full-width and scroll as a column). */
.photo-stage img { max-width: 100%; max-height: 100%; object-fit: contain; display: block; cursor: zoom-in; }
@media (max-width: 880px) { .photo-stage img { max-height: 70vh; } }
/* #photoModalImg (the single-image case) needs a FIXED box, not max-width/
   max-height -- those only cap the size, they don't force it. With them alone
   the <img> renders at its own NATURAL resolution (clamped by the max), so a
   low-res thumbnail rendered small even though its aspect-ratio matched the
   eventual higher-res image -- then jumped larger once the bigger image
   loaded. width/height:100% (object-fit:contain still letterboxes correctly)
   makes the box always exactly the container's size regardless of which
   image -- thumb or display -- currently occupies it, so the thumb->display
   swap sharpens in place with truly zero size change. Doesn't touch stacked
   frames: .stack-frame img is more specific and already overrides height. */
#photoModalImg { width: 100%; height: 100%; }
@media (max-width: 880px) { #photoModalImg { height: 70vh; } }
/* stacked sequence frames: scroll through them, no per-frame nav */
.photo-stage:has(.stack-frame) { flex-direction: column; align-items: stretch; justify-content: flex-start; }
.stack-frame { position: relative; display: flex; align-items: center; justify-content: center;
  border-bottom: 2px solid var(--bg); }
.stack-frame:last-child { border-bottom: none; }
.stack-frame img { max-height: none; width: 100%; object-fit: contain; }
/* the frame that matched the search gets a subtle highlight ring */
.stack-frame.is-cover { box-shadow: inset 0 0 0 2px var(--coral); }
.stack-num { position: absolute; top: 8px; left: 8px; font-size: 11px; font-weight: 700;
  padding: 3px 9px; border-radius: 999px; background: rgba(23,18,42,0.75); color: var(--text);
  pointer-events: none; }
.stack-frame.is-cover .stack-num { background: var(--coral); color: var(--ink); }
/* per-frame tools on a stacked sequence frame (hover to reveal) */
.stack-tools { position: absolute; top: 6px; right: 6px; display: flex; gap: 6px;
  opacity: 0; transition: opacity 0.12s ease; }
.stack-frame:hover .stack-tools { opacity: 1; }
.stack-tool { border: none; border-radius: 999px; cursor: pointer; font-family: inherit;
  font-size: 11px; font-weight: 600; padding: 4px 10px; background: rgba(23,18,42,0.85);
  color: var(--text); }
.stack-tool:hover { background: var(--lavender); color: var(--ink); }
.stack-tool.danger:hover { background: var(--coral); color: var(--ink); }
.detail-danger { display: flex; align-items: center; gap: 10px; margin-top: 16px; flex-wrap: wrap; }
.detail-danger .btn-danger { margin: 0; }

/* photo-modal labeling layer */
/* leave room on the right so the Label-people button never sits under the
   absolutely-positioned modal close (×) button */
.people-header { display: flex; align-items: flex-start; justify-content: space-between;
  gap: 10px; margin-bottom: 10px; padding-right: 44px; }
#labelToggle { flex: none; }
#labelToggle.active { background: var(--coral); color: var(--ink); border-color: var(--coral); }
.label-panel { border: 1px solid var(--border); border-radius: var(--radius); padding: 12px;
  margin-bottom: 14px; background: var(--bg); }
/* one section per frame; multi-frame sequences get a divider + frame header */
.label-frame { display: flex; flex-direction: column; }
.label-frame.multi { border-top: 1px solid var(--border); padding-top: 12px; margin-top: 12px; }
.label-frame.multi:first-child { border-top: none; padding-top: 0; margin-top: 0; }
.label-frame-head { display: flex; align-items: center; gap: 10px; margin-bottom: 10px;
  font-size: 12px; font-weight: 600; color: var(--lavender); text-transform: uppercase; letter-spacing: 0.5px; }
.label-frame-thumb { width: 34px; height: 34px; border-radius: 8px; object-fit: cover;
  background: var(--surface-2); flex: none; }
.label-hint { font-size: 12px; color: var(--muted); margin: 0 0 10px; line-height: 1.5; }
.label-linked { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 10px; }
.label-person-chip { display: inline-flex; align-items: center; gap: 5px; font-size: 12.5px;
  padding: 3px 6px 3px 11px; border-radius: 999px; background: color-mix(in srgb, var(--coral) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--coral) 40%, transparent); color: var(--coral); }
.label-person-chip button { background: none; border: none; color: var(--muted); cursor: pointer;
  font-size: 15px; line-height: 1; padding: 0; }
.label-person-chip button:hover { color: var(--coral); }
/* source-suggested (unconfirmed) person chip: tentative dashed look, distinct
   from a solid confirmed chip. Confirm = coral ✓, remove = muted ×. */
.label-person-chip.likely { background: transparent; border-style: dashed;
  border-color: var(--border); color: var(--muted); }
.label-person-chip .likely-tag { font-size: 9.5px; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; color: var(--muted); background: var(--surface-2);
  padding: 1px 5px; border-radius: 999px; }
.label-person-chip .chip-confirm { color: var(--coral); font-size: 13px; font-weight: 700; }
.label-person-chip .chip-confirm:hover { color: var(--text); }
/* "likely X" badge on a review card (bottom-left, tentative dashed) */
.media-badge.likely-badge { top: auto; bottom: 9px; left: 9px; right: auto;
  background: rgba(0,0,0,0.62); color: #ffd6a6; border: 1px dashed rgba(255,214,166,0.6);
  max-width: calc(100% - 18px); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.label-faces { display: flex; flex-direction: column; gap: 10px; margin-bottom: 10px; }
.label-face-row { display: flex; gap: 10px; align-items: center; margin-bottom: 10px; }
.label-face-crop { width: 52px; height: 52px; border-radius: 10px; object-fit: cover;
  background: var(--surface-2); flex: none; }
.label-face-body { flex: 1; display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.lf-named { font-size: 14px; font-weight: 600; }
.lf-mini { background: none; border: 1px solid var(--border); border-radius: 999px; color: var(--muted);
  font-family: inherit; font-size: 11px; padding: 3px 10px; cursor: pointer; }
.lf-mini:hover { border-color: var(--coral); color: var(--coral); }
.lf-suggest { background: color-mix(in srgb, var(--mint) 12%, transparent); border: 1px solid color-mix(in srgb, var(--mint) 45%, transparent);
  color: var(--mint); font-family: inherit; font-size: 12.5px; padding: 5px 12px; border-radius: 999px; cursor: pointer; }
.lf-suggest:hover { background: var(--mint); color: var(--ink); }
.lf-suggest b { font-weight: 700; }
.label-face-body .name-wrap { flex: 1 1 100%; min-width: 0; }
/* long suggestion names shouldn't force the row wider than the panel */
.lf-suggest { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.label-add-lbl { display: block; font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px;
  color: var(--muted); font-weight: 600; margin: 4px 0 6px; }

/* prev / next: pinned to the viewport edges, OUTSIDE the modal card, so they
   never overlap the image or detail panel. #photoModal (.modal) has a
   backdrop-filter, which makes it the containing block for these fixed
   children — so they anchor to the modal overlay (full viewport). */
.photo-nav { position: fixed; top: 50%; transform: translateY(-50%); z-index: 60;
  width: 52px; height: 52px; border-radius: 50%; border: none; cursor: pointer;
  background: rgba(23,18,42,0.72); color: #fff; font-size: 30px; line-height: 1;
  display: flex; align-items: center; justify-content: center; padding: 0 0 3px;
  box-shadow: 0 4px 16px rgba(0,0,0,0.35);
  transition: background 0.14s ease, opacity 0.14s ease; }
.photo-nav:hover { background: var(--coral); color: var(--ink); }
.photo-nav.prev { left: 20px; }
.photo-nav.next { right: 20px; }
.photo-nav:disabled { opacity: 0.25; cursor: default; background: rgba(23,18,42,0.6); color: #fff; }
/* on narrow screens where the card fills the width, tuck them tighter to the
   edge and shrink slightly so they still read as "outside" the content */
@media (max-width: 900px) {
  .photo-nav { width: 44px; height: 44px; }
  .photo-nav.prev { left: 6px; }
  .photo-nav.next { right: 6px; }
}
.photo-loading { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; }
.photo-detail { flex: 1 1 300px; padding: 22px; min-width: 280px; box-sizing: border-box;
  overflow-x: hidden; overflow-y: auto; }
/* Narrow screens: STACK the two columns and hand scrolling back to the card as
   ONE natural scroll (independent-scroll columns make no sense stacked). Since we
   removed flex-wrap, stack explicitly with flex-direction:column. */
@media (max-width: 880px) {
  .modal-photo { height: auto; max-height: 88vh; overflow: auto; display: block; }
  .modal-photo-body { flex-direction: column; }
  .photo-stage, .photo-detail { overflow-y: visible; flex: 0 0 auto; }
  .photo-stage { max-height: 70vh; }   /* keep a single tall image from filling the viewport */
}
.detail-people { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 10px; }

/* sequence detail: overall header + one block per frame */
.seq-header { display: flex; flex-direction: column; gap: 10px; padding-bottom: 14px;
  margin-bottom: 4px; border-bottom: 1px solid var(--border); }
.seq-header #seqUngroupBtn { align-self: flex-start; }
.frame-block { padding: 16px 0; border-bottom: 1px solid var(--border); }
.frame-block:last-child { border-bottom: none; }
.frame-block-head { display: flex; align-items: center; justify-content: space-between; gap: 10px;
  font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.6px;
  color: var(--lavender); margin-bottom: 10px; }
.frame-block .photo-buttons { margin: 12px 0 8px; }
.frame-block .media-type-row { margin: 4px 0; }
.frame-block .row input { flex: 1; }
/* source caption = the real post text (twitter/IG). Primary, normal size. */
.photo-caption { font-size: 15px; line-height: 1.5; margin: 0 0 14px; white-space: pre-wrap; }
/* clamp long post text to ~3 lines with a show more/less toggle */
.photo-caption.clamped { display: -webkit-box; -webkit-line-clamp: 3; line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; margin-bottom: 2px; }
.caption-toggle { background: none; border: none; color: var(--muted); cursor: pointer; font-size: 12px; padding: 0; margin: 0 0 14px; }
.caption-toggle:hover { color: var(--fg); }
/* AI-generated scene description = secondary. Smaller, muted, sits above tags. */
.photo-desc { font-size: 12.5px; line-height: 1.5; color: var(--muted); margin: 0 0 12px; }

/* ---------- duplicate finder ---------- */
.dup-controls { display: flex; align-items: center; gap: 8px; margin: 4px 0 10px; }
.dup-results { display: flex; flex-direction: column; gap: 18px; }
.dup-cluster { border: 1px solid var(--border); border-radius: 10px; padding: 10px; }
.dup-cluster-head { font-size: 12px; color: var(--muted); margin: 0 0 8px; }
.dup-grid { display: flex; flex-wrap: wrap; gap: 10px; }
.dup-item { width: 192px; display: flex; flex-direction: column; gap: 5px; margin-bottom: 6px; }
.dup-item img { width: 192px; height: 192px; object-fit: cover; border-radius: 8px;
  border: 1px solid var(--border); background: rgba(0,0,0,0.15); }
.dup-item.keep img { outline: 2px solid var(--accent, #49d6c0); outline-offset: 1px; }
.dup-item-meta { font-size: 11px; color: var(--muted); line-height: 1.35; }
.dup-item-actions { display: flex; gap: 6px; }
.dup-item-actions .btn-sm { font-size: 11px; padding: 2px 7px; }
.dup-keep-btn { width: 100%; font-size: 11px; padding: 3px 7px; white-space: normal; line-height: 1.25; }
.dup-item.removed { opacity: 0.4; pointer-events: none; }
.tag-editor { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 12px; }
.tag-pill { display: inline-flex; align-items: center; gap: 5px; font-size: 12px; padding: 3px 6px 3px 11px;
  border-radius: 999px; border: 1px solid var(--border); }
.tag-pill.ai { background: color-mix(in srgb, var(--mint) 10%, transparent); border-color: color-mix(in srgb, var(--mint) 40%, transparent); }
.tag-pill.manual { background: color-mix(in srgb, var(--coral) 10%, transparent); border-color: color-mix(in srgb, var(--coral) 40%, transparent); }
.tag-pill button { background: none; border: none; color: var(--muted); cursor: pointer; font-size: 15px; line-height: 1; padding: 0; }
.tag-pill button:hover { color: var(--coral); }
/* low-signal tags (posing, wrestling-ring, ...) stay clickable/removable but
   dimmed, mirroring is_low_signal_tag's "hidden from discovery, not deleted". */
.tag-pill-low-signal { opacity: 0.45; }
.tag-pill-low-signal:hover { opacity: 1; }
.photo-buttons { display: flex; gap: 10px; margin: 16px 0 6px; }
.media-type-row { display: flex; align-items: center; gap: 10px; margin: 14px 0 4px; }
.media-type-label { font-size: 12px; text-transform: uppercase; letter-spacing: 0.8px;
  color: var(--muted); font-weight: 600; }
.media-type-select { font-family: inherit; font-size: 13px; color: var(--text);
  background: var(--bg); border: 1px solid var(--border); border-radius: 999px;
  padding: 7px 14px; cursor: pointer; outline: none; }
.media-type-select:hover, .media-type-select:focus { border-color: var(--coral); }
.btn-danger { font-family: inherit; font-size: 12.5px; font-weight: 600; cursor: pointer;
  margin-top: 16px; padding: 8px 16px; border-radius: 999px; background: transparent;
  color: var(--coral); border: 1px solid color-mix(in srgb, var(--coral) 45%, transparent); }
.btn-danger:hover { background: var(--coral); color: var(--ink); border-color: var(--coral); }
.btn-danger:disabled { opacity: 0.6; cursor: default; }

/* ---------- tagging progress toast ---------- */
/* back-to-top button (high-contrast accent, chunky arrow). Lives in the
   bottom-right toast stack (not independently fixed), so it sits at the corner
   and any visible toasts stack ABOVE it -- and it drops back the instant a toast
   clears, no fixed offset. flex:none so the circle keeps its size in the column. */
.scroll-top { flex: none; width: 50px; height: 50px; border-radius: 50%; border: none;
  background: var(--coral); color: var(--ink); font-size: 18px; font-weight: 900;
  line-height: 1; cursor: pointer; box-shadow: 0 6px 20px rgba(0,0,0,0.45);
  display: flex; align-items: center; justify-content: center; padding: 0 0 1px;
  transition: transform 0.18s cubic-bezier(.5,1.4,.5,1), box-shadow 0.15s ease,
    opacity 0.18s ease; }
.scroll-top:hover { transform: translateY(-3px); box-shadow: 0 10px 26px rgba(0,0,0,0.55); }
/* Appear/disappear via opacity+scale rather than snapping. Two-phase (see
   setScrollTopVisible in app.js): showing removes .hidden (re-enters layout) then
   clears .scroll-top-out on the next frame so it eases in; hiding adds
   .scroll-top-out (fade+shrink in place) and, after the transition, adds .hidden
   to collapse it out of the flex column so toasts drop to the corner. */
.scroll-top.scroll-top-out { opacity: 0; transform: scale(0.6); pointer-events: none; }

/* ---------- toast stacks (auto-stacking anchors) ----------
   Three fixed anchors -- top-right (appToastHost, above), bottom-right, and
   bottom-center. Each bottom stack is a flex column-REVERSE so its children stack
   upward FROM the corner (first child nearest the corner), with a gap between
   them. Children are normal in-flow blocks; a .hidden child is display:none, so
   it occupies no space and never offsets its siblings. pointer-events:none on the
   container + auto on children so the empty gap doesn't eat clicks. */
.toast-stack { position: fixed; z-index: 60; display: flex; flex-direction: column-reverse;
  gap: 14px; pointer-events: none; }
.toast-stack > * { pointer-events: auto; }
.toast-stack-br { right: 22px; bottom: 22px; align-items: flex-end; }
.toast-stack-bc { left: 50%; bottom: 22px; transform: translateX(-50%); align-items: center; }

.tag-toast { width: 320px;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
  padding: 14px 16px; box-shadow: 0 12px 30px rgba(0,0,0,0.45);
  animation: stack-toast-in 0.25s cubic-bezier(.5,1.5,.5,1); }
/* slide UP from just below the corner on appear. (Named distinctly from the
   app-toast's toast-in, which slides in horizontally from the top-right -- two
   different anchors, so two different entrances.) */
@keyframes stack-toast-in { from { transform: translateY(14px); opacity: 0; } to { transform: none; opacity: 1; } }
/* slide-out: fade + drop back toward the corner. Applied by toastHide() in app.js
   just before the element is hidden; on animationend .hidden is set so it
   collapses out of the stack and siblings/scroll-top reflow into place. */
.tag-toast.toast-leaving { animation: stack-toast-out 0.2s ease forwards; }
@keyframes stack-toast-out { from { transform: none; opacity: 1; }
  to { transform: translateY(14px); opacity: 0; } }
.tag-toast .toast-head { display: flex; align-items: center; gap: 10px; font-size: 14px;
  font-weight: 600; margin-bottom: 10px; }
.tag-toast .toast-cancel { margin-left: auto; background: none; border: none; color: var(--muted);
  font-size: 22px; line-height: 1; cursor: pointer; padding: 0 2px; }
.tag-toast .toast-cancel:hover { color: var(--coral); }
.tag-toast .toast-sub { font-size: 12px; color: var(--muted); margin-top: 7px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.tag-toast.done .spinner { display: none; }
/* the media-type re-check toast shares .tag-toast's look; the bottom-right stack
   places it (no manual offset). A failed pass tints title + fill coral. */
.recheck-toast.error .toast-head { color: var(--coral); }
.recheck-toast.error .progress-fill { background: var(--coral); }

/* Local-only persistent indicator for background scrape/sync activity. Lives in
   the bottom-center toast stack (#toastStackBC) so it never competes with the
   bottom-right toasts or the theme-song player (bottom-left) for a corner. */
.bg-activity-toast { display: flex; align-items: center; gap: 10px;
  background: var(--surface); border: 1px solid var(--border); border-radius: 999px;
  padding: 9px 18px; box-shadow: 0 12px 30px rgba(0,0,0,0.45);
  font-size: 13px; font-weight: 600; color: var(--text);
  animation: stack-toast-in 0.25s cubic-bezier(.5,1.5,.5,1); }
.bg-activity-toast.toast-leaving { animation: stack-toast-out 0.2s ease forwards; }
.bg-activity-toast.hidden { display: none; }
