/* Family Archive — warm, photographic, calm.
   The material is weddings and funerals, so the interface stays quiet and lets
   the media carry the colour. Light and dark both defined explicitly. */

:root {
  --bg:        #faf7f2;
  --bg-raised: #ffffff;
  --bg-sunken: #f1ece3;
  --ink:       #2b2724;
  --ink-soft:  #6b625a;
  --ink-faint: #9a9088;
  --line:      #e3dcd1;
  --accent:    #9a6b3f;
  --accent-in: #ffffff;
  --shadow:    0 1px 2px rgba(43,39,36,.06), 0 8px 24px rgba(43,39,36,.08);
  --radius:    14px;
  --maxw:      1180px;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg:        #171513;
    --bg-raised: #201d1a;
    --bg-sunken: #131110;
    --ink:       #f0eae2;
    --ink-soft:  #b3a99e;
    --ink-faint: #7d736a;
    --line:      #322c27;
    --accent:    #d29b62;
    --accent-in: #1a1512;
    --shadow:    0 1px 2px rgba(0,0,0,.4), 0 8px 28px rgba(0,0,0,.45);
  }
}
:root[data-theme="dark"] {
  --bg:        #171513;
  --bg-raised: #201d1a;
  --bg-sunken: #131110;
  --ink:       #f0eae2;
  --ink-soft:  #b3a99e;
  --ink-faint: #7d736a;
  --line:      #322c27;
  --accent:    #d29b62;
  --accent-in: #1a1512;
  --shadow:    0 1px 2px rgba(0,0,0,.4), 0 8px 28px rgba(0,0,0,.45);
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font: 16px/1.55 ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  -webkit-font-smoothing: antialiased;
}

a { color: inherit; }

/* ── header ─────────────────────────────────────────────────────────────── */
header {
  position: sticky; top: 0; z-index: 20;
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--line);
}
.bar {
  max-width: var(--maxw); margin: 0 auto;
  padding: 14px 20px;
  display: flex; align-items: center; gap: 16px;
}
.brand {
  font-weight: 620; letter-spacing: -.02em; font-size: 1.05rem;
  display: flex; align-items: center; gap: 9px;
  cursor: pointer; background: none; border: 0; color: inherit; padding: 0;
}
.brand .dot {
  width: 9px; height: 9px; border-radius: 50%;
  background: var(--accent); flex: none;
}
.spacer { flex: 1; }

.search {
  border: 1px solid var(--line); background: var(--bg-raised);
  color: var(--ink); border-radius: 999px;
  padding: 8px 15px; font-size: .9rem; width: 210px;
  transition: width .18s ease, border-color .18s ease;
}
.search:focus { outline: none; border-color: var(--accent); width: 260px; }
.search::placeholder { color: var(--ink-faint); }

.ghost {
  border: 1px solid var(--line); background: transparent; color: var(--ink-soft);
  border-radius: 999px; padding: 8px 15px; font-size: .86rem;
  cursor: pointer; transition: color .15s, border-color .15s;
}
.ghost:hover { color: var(--ink); border-color: var(--ink-faint); }

/* ── layout ─────────────────────────────────────────────────────────────── */
main { max-width: var(--maxw); margin: 0 auto; padding: 30px 20px 80px; }

.crumb {
  display: flex; align-items: baseline; gap: 12px;
  margin-bottom: 22px; flex-wrap: wrap;
}
.crumb h1 { font-size: 1.5rem; letter-spacing: -.02em; margin: 0; font-weight: 620; }
.crumb .meta { color: var(--ink-faint); font-size: .86rem; }
.back {
  background: none; border: 0; color: var(--accent);
  cursor: pointer; font-size: .9rem; padding: 0; font-weight: 550;
}

/* ── collection cards ───────────────────────────────────────────────────── */
.cards {
  display: grid; gap: 18px;
  grid-template-columns: repeat(auto-fill, minmax(255px, 1fr));
}
.card {
  background: var(--bg-raised); border: 1px solid var(--line);
  border-radius: var(--radius); overflow: hidden; cursor: pointer;
  box-shadow: var(--shadow);
  transition: transform .18s ease, box-shadow .18s ease;
  text-align: left; padding: 0; color: inherit; font: inherit;
  display: flex; flex-direction: column;
}
.card:hover { transform: translateY(-3px); box-shadow: 0 4px 8px rgba(0,0,0,.09), 0 16px 40px rgba(0,0,0,.13); }
.card .thumb {
  aspect-ratio: 3/2; background: var(--bg-sunken);
  display: grid; place-items: center; overflow: hidden;
}
.card .thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.card .thumb .glyph { font-size: 2rem; opacity: .3; }
.card .body { padding: 14px 16px 16px; }
.card h3 { margin: 0 0 5px; font-size: 1rem; font-weight: 600; letter-spacing: -.01em; }
.card .sub { color: var(--ink-faint); font-size: .82rem; }

/* ── item grids ─────────────────────────────────────────────────────────── */
.section-label {
  font-size: .74rem; text-transform: uppercase; letter-spacing: .11em;
  color: var(--ink-faint); margin: 32px 0 14px; font-weight: 600;
}
.section-label:first-child { margin-top: 0; }

.photos {
  display: grid; gap: 8px;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
}
.photos img {
  width: 100%; aspect-ratio: 1; object-fit: cover;
  border-radius: 8px; cursor: pointer; display: block;
  background: var(--bg-sunken);
  transition: opacity .15s;
}
.photos img:hover { opacity: .85; }

.videos { display: grid; gap: 14px; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); }
.vtile {
  background: var(--bg-raised); border: 1px solid var(--line);
  border-radius: 11px; overflow: hidden; cursor: pointer;
  padding: 0; color: inherit; font: inherit; text-align: left;
}
.vtile:hover { border-color: var(--accent); }
.vtile .ph {
  aspect-ratio: 16/9; background: var(--bg-sunken);
  display: grid; place-items: center; font-size: 1.6rem;
  overflow: hidden; color: var(--ink-faint);
}
.vtile .ph img { width: 100%; height: 100%; object-fit: cover; display: block; }
.vtile .lbl { padding: 10px 12px; font-size: .84rem; }
.vtile .lbl .n { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vtile .lbl .d { color: var(--ink-faint); font-size: .76rem; }

.tracks { border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; }
.track {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 15px; cursor: pointer; background: var(--bg-raised);
  border-bottom: 1px solid var(--line); font-size: .9rem;
}
.track:last-child { border-bottom: 0; }
.track:hover { background: var(--bg-sunken); }
.track.playing { color: var(--accent); font-weight: 560; }
.track .num { color: var(--ink-faint); font-size: .78rem; width: 26px; flex: none; }
.track .nm { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.track .dur { color: var(--ink-faint); font-size: .78rem; }

/* ── viewer overlay ─────────────────────────────────────────────────────── */
.viewer {
  position: fixed; inset: 0; z-index: 60;
  background: rgba(10,8,7,.94);
  display: none; place-items: center; padding: 24px;
}
.viewer.open { display: grid; }
.viewer video, .viewer img {
  max-width: 100%; max-height: calc(100vh - 130px);
  border-radius: 8px; display: block; background: #000;
}
.viewer .close {
  position: absolute; top: 18px; right: 20px;
  background: rgba(255,255,255,.1); color: #fff; border: 0;
  width: 40px; height: 40px; border-radius: 50%; cursor: pointer; font-size: 1.2rem;
}
.viewer .close:hover { background: rgba(255,255,255,.2); }
.viewer .nav {
  position: absolute; top: 50%; transform: translateY(-50%);
  background: rgba(255,255,255,.1); color: #fff; border: 0;
  width: 46px; height: 46px; border-radius: 50%; cursor: pointer; font-size: 1.3rem;
}
.viewer .nav:hover { background: rgba(255,255,255,.2); }
.viewer .prev { left: 18px; } .viewer .next { right: 18px; }
.viewer .caption {
  position: absolute; bottom: 22px; left: 0; right: 0; text-align: center;
  color: rgba(255,255,255,.75); font-size: .85rem; padding: 0 70px;
}
.viewer .caption a { color: var(--accent); }

/* ── audio dock ─────────────────────────────────────────────────────────── */
.dock {
  position: fixed; bottom: 0; left: 0; right: 0; z-index: 40;
  background: var(--bg-raised); border-top: 1px solid var(--line);
  padding: 10px 20px; display: none; align-items: center; gap: 14px;
  box-shadow: 0 -4px 20px rgba(0,0,0,.08);
}
.dock.open { display: flex; }
.dock .t { flex: 1; font-size: .87rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dock audio { flex: 2; max-width: 460px; height: 34px; }

.empty { color: var(--ink-faint); text-align: center; padding: 70px 20px; }
.spin { color: var(--ink-faint); text-align: center; padding: 70px 20px; }

/* ── login ──────────────────────────────────────────────────────────────── */
.login-wrap { min-height: 100vh; display: grid; place-items: center; padding: 24px; }
.login-card {
  background: var(--bg-raised); border: 1px solid var(--line);
  border-radius: 18px; padding: 34px; width: 100%; max-width: 370px;
  box-shadow: var(--shadow);
}
.login-card h1 { margin: 0 0 6px; font-size: 1.3rem; letter-spacing: -.02em; }
.login-card p { margin: 0 0 22px; color: var(--ink-faint); font-size: .88rem; }
.login-card label { display: block; font-size: .8rem; color: var(--ink-soft); margin-bottom: 6px; }
.login-card input {
  width: 100%; padding: 11px 13px; margin-bottom: 15px;
  border: 1px solid var(--line); border-radius: 9px;
  background: var(--bg); color: var(--ink); font-size: .95rem;
}
.login-card input:focus { outline: none; border-color: var(--accent); }
.login-card button {
  width: 100%; padding: 11px; border: 0; border-radius: 9px;
  background: var(--accent); color: var(--accent-in);
  font-size: .95rem; font-weight: 600; cursor: pointer;
}
.login-card button:disabled { opacity: .6; cursor: default; }
.err { color: #c0392b; font-size: .85rem; margin-bottom: 12px; min-height: 1.2em; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .err { color: #ef8a7d; }
}

@media (max-width: 620px) {
  .search { width: 130px; } .search:focus { width: 160px; }
  main { padding: 22px 14px 90px; }
  .bar { padding: 12px 14px; gap: 10px; }
}

/* ── password peekaboo + version badge ──────────────────────────────────── */
/* The wrapper carries the spacing so the toggle can centre on the input alone.
   Selectors are deliberately more specific than `.login-card button`, which
   sets width:100% and the accent background for the real submit buttons — at
   equal specificity that rule won and the toggle covered the whole field. */
.login-card .pw-wrap { position: relative; margin-bottom: 15px; }
.login-card .pw-wrap input { padding-right: 70px; margin-bottom: 0; }
.login-card .pw-wrap .peek {
  position: absolute; right: 5px; top: 50%; transform: translateY(-50%);
  width: auto; background: none; border: 0; cursor: pointer;
  color: var(--accent); font-size: .78rem; font-weight: 600;
  padding: 6px 9px; border-radius: 6px; line-height: 1;
}
.login-card .pw-wrap .peek:hover { background: var(--bg-sunken); }

.ver { margin-top: 14px; text-align: center; color: var(--ink-faint); font-size: .74rem; }
.ver-badge {
  color: var(--ink-faint); font-size: .74rem;
  border: 1px solid var(--line); border-radius: 999px; padding: 3px 9px;
}
@media (max-width: 620px) { .ver-badge { display: none; } }

/* ── v0.2.0: login states, admin dashboard ─────────────────────────────── */
.hidden { display: none !important; }
.hint { color: var(--ink-faint); font-weight: 400; }
.alt { margin-top: 16px; text-align: center; font-size: .85rem; color: var(--ink-faint); }
.alt a { color: var(--accent); font-weight: 550; text-decoration: none; }
.alt a:hover { text-decoration: underline; }
.ts { margin: 4px 0 14px; min-height: 0; }

/* waiting-for-approval pulse */
.pulse { display: flex; gap: 7px; justify-content: center; margin: 26px 0 6px; }
.pulse span {
  width: 9px; height: 9px; border-radius: 50%; background: var(--accent);
  opacity: .3; animation: pulse 1.3s ease-in-out infinite;
}
.pulse span:nth-child(2) { animation-delay: .18s; }
.pulse span:nth-child(3) { animation-delay: .36s; }
@keyframes pulse { 0%,100% { opacity: .25; transform: scale(.85); }
                   50%     { opacity: 1;   transform: scale(1); } }

/* admin: generic row */
.note { color: var(--ink-faint); font-size: .84rem; margin: -6px 0 12px; }
.empty-inline { color: var(--ink-faint); font-size: .88rem; padding: 14px 0; }
.row {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  background: var(--bg-raised); border: 1px solid var(--line);
  border-radius: 10px; padding: 11px 14px; margin-bottom: 8px;
}
.row-main { font-weight: 560; font-size: .92rem; }
.row-sub { color: var(--ink-faint); font-size: .82rem; flex: 1; }
.btn-ok, .btn-no {
  border: 1px solid var(--line); background: transparent;
  border-radius: 999px; padding: 6px 14px; font-size: .82rem;
  cursor: pointer; font-weight: 560;
}
.btn-ok { color: #1f7a4d; border-color: #1f7a4d55; }
.btn-ok:hover { background: #1f7a4d14; }
.btn-no { color: #b3452f; border-color: #b3452f55; }
.btn-no:hover { background: #b3452f14; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .btn-ok { color: #6fcf97; border-color: #6fcf9755; }
  :root:not([data-theme="light"]) .btn-no { color: #ef8a7d; border-color: #ef8a7d55; }
}

/* ── quality picker ────────────────────────────────────────────────────
   Restored in v0.24.0 exactly as it shipped in v0.21.0. The .on rule is the
   load-bearing line: without it all three tiers render identically and the
   control cannot say which one you are watching. */
.qualbar {
  position: absolute; top: 62px; right: 18px; z-index: 3;
  display: flex; align-items: center; gap: 7px;
  background: rgba(20,16,14,.82); backdrop-filter: blur(8px);
  border: 1px solid rgba(255,255,255,.14);
  border-radius: 999px; padding: 6px 11px;
}
.qualbar .qlabel {
  color: rgba(255,255,255,.55); font-size: .72rem;
  text-transform: uppercase; letter-spacing: .07em; margin-right: 2px;
}
.qualbar .sbtn.on { background: var(--accent); color: #fff; font-weight: 600; }
@media (max-width: 620px) {
  .qualbar { top: auto; bottom: 116px; right: 50%; transform: translateX(50%); }
}

/* admin: tabs.
   Three, because the page had grown to ten stacked sections and every action
   on it refetched all five endpoints. Each tab now loads only its own data,
   and only when first opened. */
.tabs {
  display: flex; gap: 4px; margin: 4px 0 20px;
  border-bottom: 1px solid var(--line); overflow-x: auto;
}
.tab {
  appearance: none; background: none; border: 0; cursor: pointer;
  font: inherit; font-size: .92rem; color: var(--ink-soft);
  padding: 9px 15px; border-radius: 10px 10px 0 0; white-space: nowrap;
  border-bottom: 2px solid transparent; margin-bottom: -1px;
}
.tab:hover { color: var(--ink); background: var(--bg-sunken); }
.tab.on {
  color: var(--accent); border-bottom-color: var(--accent); font-weight: 600;
}
.panel[hidden] { display: none; }

/* Pinned above the tabs: this is why the page gets opened. It removes itself
   when empty rather than sitting there saying "nothing", so a quiet archive
   looks quiet. */
.pending {
  background: color-mix(in srgb, var(--accent) 9%, var(--bg-raised));
  border: 1px solid color-mix(in srgb, var(--accent) 30%, var(--line));
  border-radius: var(--radius); padding: 13px 16px 15px; margin-bottom: 18px;
}
.pending .section-label { margin-top: 0; color: var(--accent); }

/* A person's devices, nested in their card rather than listed apart: 38 rows
   for 3 people, each beginning with the name of its owner, is the definition
   of data that wanted nesting. */
.devbox {
  margin-top: 12px; border-top: 1px solid var(--line); padding-top: 10px;
}
.devbox summary {
  cursor: pointer; font-size: .82rem; color: var(--ink-faint);
  list-style: none; user-select: none;
}
.devbox summary::-webkit-details-marker { display: none; }
.devbox summary::before { content: '\25B8 '; }
.devbox[open] summary::before { content: '\25BE '; }
.devbox summary:hover { color: var(--ink); }
.devbox .row { padding: 6px 0; }

/* admin: person card with folder grid */
.person {
  background: var(--bg-raised); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 15px 17px; margin-bottom: 12px;
}
.person-head { display: flex; align-items: center; gap: 9px; margin-bottom: 12px; }
.tag {
  font-size: .7rem; text-transform: uppercase; letter-spacing: .07em;
  border: 1px solid var(--line); border-radius: 999px; padding: 2px 8px;
  color: var(--ink-faint);
}
.status-approved { color: #1f7a4d; border-color: #1f7a4d55; }
.status-pending  { color: #9a6b3f; border-color: #9a6b3f55; }
.status-denied   { color: #b3452f; border-color: #b3452f55; }
.grants {
  display: grid; gap: 7px;
  grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
}
.grant {
  display: flex; align-items: center; gap: 9px;
  font-size: .87rem; padding: 7px 10px; border-radius: 8px;
  background: var(--bg-sunken); cursor: pointer;
}
.grant input { accent-color: var(--accent); width: 16px; height: 16px; }
.person-foot { display: flex; align-items: center; gap: 12px; margin-top: 13px; }

/* Coverage, folded into the grant grid rather than given its own panel: the
   checkbox that granted the access and the fact of whether it was ever used
   belong on the same control, or acting on one means scrolling to the other. */
.grant .gname { flex: 1; }
.grant .gcov {
  font-size: .74rem; color: var(--ink-faint); white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.grant.warm { background: color-mix(in srgb, #1f7a4d 8%, var(--bg-sunken)); }
.grant.warm .gcov { color: #1f7a4d; }
/* The only state that is a prompt to do something. Granted and never opened
   usually means they were never told it existed, not that they were bored. */
.grant.cold {
  background: color-mix(in srgb, var(--accent) 10%, var(--bg-sunken));
  box-shadow: inset 2px 0 0 var(--accent);
}
.grant.cold .gcov { color: var(--accent); font-weight: 600; }
.grant .gpast { font-style: italic; }
.person-seen { color: var(--ink-faint); font-size: .8rem; margin-left: 2px; }
.danger-menu { margin-left: auto; display: flex; gap: 7px; }
.tiny { font-size: .75rem; padding: 4px 9px; }
.creach { display: block; margin: 2px 0 0; }
.creach.cold { color: var(--accent); }

/* admin: archive health */
.healthstrip {
  display: grid; gap: 9px; margin-bottom: 12px;
  grid-template-columns: repeat(auto-fill, minmax(165px, 1fr));
}
.hstat {
  background: var(--bg-raised); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 11px 13px;
  display: flex; flex-direction: column; gap: 3px;
}
.hstat strong { font-size: 1.15rem; font-variant-numeric: tabular-nums; }
.hstat span { font-size: .76rem; color: var(--ink-faint); line-height: 1.3; }
.hstat.bad { border-color: #b3452f55; }
.hstat.bad strong { color: #b3452f; }
.healthmissing {
  background: var(--bg-sunken); border-radius: 10px; padding: 10px 13px;
  margin-bottom: 12px; font-size: .84rem;
}
.healthmissing summary { cursor: pointer; color: #b3452f; font-weight: 600; }
.healthmissing .row-sub { padding: 3px 0; }
.saved { color: #1f7a4d; font-size: .82rem; }

/* admin: invites */
.invite-row { display: flex; gap: 9px; margin-bottom: 12px; flex-wrap: wrap; }
.invite-row .search { flex: 1; min-width: 190px; }
.code {
  background: var(--bg-sunken); border: 1px dashed var(--accent);
  border-radius: 10px; padding: 15px; margin-bottom: 14px; text-align: center;
}
.code-val {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 1.35rem; letter-spacing: .08em; font-weight: 600;
  color: var(--accent); margin-bottom: 6px; user-select: all;
}

/* ── v0.3.0: sort control, filenames, slideshow ────────────────────────── */
.sortwrap { display: flex; align-items: center; gap: 7px; margin-left: auto; }
.sortlabel { color: var(--ink-faint); font-size: .8rem; }
.sortsel {
  border: 1px solid var(--line); background: var(--bg-raised); color: var(--ink);
  border-radius: 999px; padding: 6px 11px; font-size: .82rem; cursor: pointer;
}
.sortsel:focus { outline: none; border-color: var(--accent); }

.section-head { display: flex; align-items: center; gap: 12px; }
.section-head .section-label { margin-bottom: 0; }
.slideshow-btn { margin-left: auto; }

/* Filename under the title: monospace so similar camera names line up and the
   differing digits are easy to spot. */
.vtile .lbl .fn {
  display: block; color: var(--ink-faint); font-size: .72rem;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  margin: 2px 0 1px;
}

/* slideshow control bar, pinned inside the viewer overlay */
.slidebar {
  position: absolute; bottom: 62px; left: 50%; transform: translateX(-50%);
  display: flex; align-items: center; gap: 10px;
  background: rgba(20,16,14,.82); backdrop-filter: blur(8px);
  border: 1px solid rgba(255,255,255,.14);
  border-radius: 999px; padding: 8px 14px; z-index: 2;
}
.sbtn {
  background: rgba(255,255,255,.1); color: #fff; border: 0;
  border-radius: 999px; padding: 6px 13px; font-size: .82rem;
  cursor: pointer; font-weight: 550;
}
.sbtn:hover { background: rgba(255,255,255,.2); }
.sspeed { padding: 6px 9px; }
.sspeed option { color: #1a1a1a; }
.spos { color: rgba(255,255,255,.72); font-size: .8rem; min-width: 62px; text-align: center; }

/* During a show the image gets a gentle fade so transitions are not abrupt. */
.viewer.slideshow img { animation: fadein .45s ease; }
@keyframes fadein { from { opacity: .25; } to { opacity: 1; } }

@media (max-width: 620px) {
  .sortwrap { margin-left: 0; width: 100%; }
  .slidebar { bottom: 74px; gap: 7px; padding: 7px 10px; }
  .sbtn { padding: 6px 10px; font-size: .78rem; }
}

/* ── v0.4.0: crossfade + Ken Burns slideshow stage ─────────────────────── */
/* Two stacked layers so the incoming photo can decode off-screen and only the
   opacities swap. A single <img> cannot cross-fade with itself. */
.stage {
  position: relative;
  width: min(96vw, 1800px);
  height: calc(100vh - 150px);
}
.stage .slide {
  position: absolute; inset: 0; margin: auto;
  max-width: 100%; max-height: 100%;
  width: auto; height: auto; object-fit: contain;
  opacity: 0; transition: opacity 1.1s ease;
  border-radius: 8px; background: transparent;
  will-change: opacity, transform;
}
.stage .slide.on { opacity: 1; }

/* Instant mode: no fade at all. */
.viewer.nofade .stage .slide { transition: none; }
.viewer.nofade .stage .slide.on { animation: none !important; transform: none !important; }

/* The slow pan-and-zoom screensavers use. Two variants, alternated, so a long
   run of photos does not all drift the same direction. */
@keyframes kenburnsA {
  from { transform: scale(1.00) translate(0, 0); }
  to   { transform: scale(1.10) translate(-1.6%, -1.1%); }
}
@keyframes kenburnsB {
  from { transform: scale(1.10) translate(1.4%, 0.9%); }
  to   { transform: scale(1.00) translate(0, 0); }
}

/* the control bar sits above the stage */
.slidebar { z-index: 3; }
.slidebar select.sbtn { padding: 6px 8px; }
@media (max-width: 620px) {
  .stage { height: calc(100vh - 175px); }
  .slidebar { flex-wrap: wrap; justify-content: center; max-width: 94vw; }
}

/* ── v0.4.1: music dock during a slideshow ─────────────────────────────── */
/* The viewer overlay is z-index 60; the dock was 40, so slideshow music
   controls rendered behind the dark backdrop — invisible, and unclickable
   exactly when autoplay had been refused and you needed to press play. */
.dock { z-index: 70; }
.viewer.withmusic .slidebar { bottom: 108px; }
@media (max-width: 620px) {
  .viewer.withmusic .slidebar { bottom: 124px; }
  .viewer.withmusic .caption { display: none; }
}

/* ── v0.5.0: song picker + failure log ─────────────────────────────────── */
.songpick {
  position: fixed; inset: 0; z-index: 90;
  background: rgba(10,8,7,.72); display: grid; place-items: center; padding: 20px;
}
.songpick-card {
  background: var(--bg-raised); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--shadow);
  width: min(560px, 96vw); max-height: min(70vh, 640px);
  display: flex; flex-direction: column; overflow: hidden;
}
.songpick-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 14px 16px; border-bottom: 1px solid var(--line);
}
.songpick-head .sbtn { background: var(--bg-sunken); color: var(--ink); }
.songpick-search { margin: 12px 16px; width: calc(100% - 32px); }
.songpick-list { overflow-y: auto; padding: 0 8px 12px; }
.songrow {
  display: block; width: 100%; text-align: left;
  background: none; border: 0; color: inherit; font: inherit;
  padding: 9px 12px; border-radius: 8px; cursor: pointer; font-size: .88rem;
}
.songrow:hover { background: var(--bg-sunken); }
.songrow.playing { color: var(--accent); font-weight: 600; }

/* failure log */
.logrow {
  display: grid; grid-template-columns: auto 1fr auto; gap: 10px;
  align-items: baseline;
  background: var(--bg-raised); border: 1px solid var(--line);
  border-left: 3px solid var(--line);
  border-radius: 8px; padding: 9px 13px; margin-bottom: 6px; font-size: .84rem;
}
.logrow.error { border-left-color: #b3452f; }
.logrow.warn  { border-left-color: #9a6b3f; }
.logrow .lk { color: var(--ink-faint); font-size: .74rem; text-transform: uppercase;
              letter-spacing: .06em; }
.logrow .lm { word-break: break-word; }
.logrow .ld { color: var(--ink-faint); font-size: .76rem; white-space: nowrap; }
.logdetail {
  grid-column: 1 / -1; color: var(--ink-faint); font-size: .76rem;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  white-space: pre-wrap; margin-top: 5px; max-height: 130px; overflow-y: auto;
}

/* ── v0.5.1: readable slideshow dropdowns ──────────────────────────────── */
/* The v0.4 rewrite dropped the .sspeed class these option colours were bound
   to, so the option list fell back to browser defaults — which on some systems
   renders near-white text on a near-white menu. Both the closed control and the
   open option list now carry explicit colours, and the control gets a solid
   background rather than a translucent one so it reads against any photo. */
.slidebar select.sbtn {
  background: #2b2724;
  color: #f6f1ea;
  border: 1px solid rgba(255,255,255,.28);
  font-weight: 600;
  -webkit-appearance: none; appearance: none;
  padding: 6px 26px 6px 11px;
  /* chevron, drawn rather than relying on the native one we just removed */
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='7'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%23f6f1ea' stroke-width='1.6' fill='none' stroke-linecap='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 9px center;
}
.slidebar select.sbtn:hover { background-color: #3a342f; }
.slidebar select.sbtn:focus { outline: 2px solid var(--accent); outline-offset: 1px; }
/* The open menu is painted by the OS; these are the only properties it honours. */
.slidebar select.sbtn option {
  background: #2b2724;
  color: #f6f1ea;
}

/* ── v0.6.0: event dates ───────────────────────────────────────────────── */
.card .when {
  color: var(--ink-faint); font-size: .78rem; margin-top: 4px;
  letter-spacing: .01em;
}

/* ── v0.8.0: collection curation ───────────────────────────────────────── */
.crow {
  display: grid; grid-template-columns: 74px 1fr auto; gap: 12px;
  align-items: center;
  background: var(--bg-raised); border: 1px solid var(--line);
  border-radius: 10px; padding: 10px 13px; margin-bottom: 8px;
}
.cthumb {
  width: 74px; aspect-ratio: 3/2; border-radius: 7px; overflow: hidden;
  background: var(--bg-sunken); display: grid; place-items: center;
}
.cthumb img { width: 100%; height: 100%; object-fit: cover; }
.cmid { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.cmid .note { margin: 0; }
.cacts { display: flex; gap: 6px; flex-wrap: wrap; }
.cacts .ghost { padding: 6px 11px; font-size: .8rem; }
.cacts .ghost:disabled { opacity: .35; cursor: default; }
.coverpick { grid-column: 1 / -1; }
.coverstrip {
  display: flex; gap: 7px; overflow-x: auto; padding: 8px 0;
}
.coverop {
  flex: 0 0 auto; width: 96px; aspect-ratio: 3/2; padding: 0;
  border: 2px solid var(--line); border-radius: 7px; overflow: hidden;
  background: var(--bg-sunken); cursor: pointer;
}
.coverop:hover { border-color: var(--accent); }
.coverop img { width: 100%; height: 100%; object-fit: cover; display: block; }

.coverop { position: relative; }
.covertime {
  position: absolute; right: 3px; bottom: 3px;
  background: rgba(0,0,0,.72); color: #fff; font-size: .62rem;
  padding: 1px 5px; border-radius: 4px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}

/* ── v0.9.0: add an image ──────────────────────────────────────────────── */
.addform {
  display: flex; flex-direction: column; gap: 10px;
  padding: 12px; margin-top: 8px;
  background: var(--bg-sunken); border-radius: 9px;
}
.addfield { display: flex; flex-direction: column; gap: 4px; }
.addfield .note { margin: 0; }
.addfield input { font: inherit; font-size: .86rem; }
.addfield input[type=date] {
  border: 1px solid var(--line); border-radius: 7px; padding: 7px 9px;
  background: var(--bg-raised); color: var(--ink); max-width: 220px;
}
.addcheck { display: flex; align-items: center; gap: 8px; font-size: .86rem; }
.addcheck input { accent-color: var(--accent); width: 16px; height: 16px; }
.addform .ghost { align-self: flex-start; }

/* ── v0.11.0: clip control ─────────────────────────────────────────────── */
.clipbar {
  position: absolute; top: 18px; left: 20px; z-index: 3;
  display: flex; align-items: center; gap: 9px;
  background: rgba(20,16,14,.82); backdrop-filter: blur(8px);
  border: 1px solid rgba(255,255,255,.14);
  border-radius: 999px; padding: 7px 13px;
}
.clipmsg { color: rgba(255,255,255,.82); font-size: .82rem; max-width: 46vw; }
.clipmsg a { color: var(--accent); }
@media (max-width: 620px) {
  .clipbar { top: auto; bottom: 150px; left: 50%; transform: translateX(-50%); }
  .clipmsg { max-width: 60vw; }
}

/* ── v0.13.0: item management ──────────────────────────────────────────── */
.armswitch {
  display: flex; align-items: center; gap: 8px; margin-left: auto;
  font-size: .84rem; color: var(--ink-soft); cursor: pointer;
  border: 1px solid var(--line); border-radius: 999px; padding: 6px 13px;
}
.armswitch input { accent-color: #b3452f; width: 16px; height: 16px; }
.armswitch:has(input:checked) { border-color: #b3452f; color: #b3452f; font-weight: 600; }
.armstate { color: #b3452f; font-weight: 600; }
.btn-trash:disabled { opacity: .3; cursor: not-allowed; }
#manage .row, #trash .row { flex-wrap: wrap; }
#mancoll { max-width: 260px; }

/* ── v0.17.0: the shared sign-up PIN ───────────────────────────────────── */
/* Shown large and spaced, not concealed: it is public by design and its whole
   purpose is being read aloud correctly the first time. */
.pinbox { display: flex; align-items: center; gap: 14px; margin: 4px 0 2px; }
.pinbox code {
  font-size: 1.8rem; font-weight: 700; letter-spacing: .3em;
  padding: 10px 6px 10px 20px; background: var(--card, #f7f4f1);
  border: 1px solid var(--line); border-radius: 10px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}

/* ── v0.19.0: the emailed sign-up code ─────────────────────────────────── */
/* Four characters, read off one screen and typed into another. Large, widely
   spaced and centred so a mis-keyed character is obvious before submitting. */
.codein {
  font-size: 1.7rem; font-weight: 700; letter-spacing: .42em;
  text-align: center; text-transform: uppercase;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  padding: 14px 10px 14px 22px;
}
.codein::placeholder { letter-spacing: .42em; font-weight: 400; opacity: .28; }

/* ── v0.20.0: per-person clip permission ───────────────────────────────── */
/* Set apart from the folder grid: folders decide what someone may look at,
   this decides whether they may write into the archive. */
.clipperm {
  margin-top: 12px; max-width: 260px;
  background: transparent; border: 1px dashed var(--line);
}
.clipperm:has(input:checked) { border-color: var(--accent); border-style: solid; }

/* ── who is here now, and the sign-in record (v0.30.0) ──────────────────── */

/* The strip sits above the person cards and stays quiet when nobody is on:
 * it answers a question you glance at, not one you read. */
.nowstrip {
  background: var(--bg-sunken); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 11px 14px; margin-bottom: 14px;
}
.nowstrip h3 {
  margin: 0 0 6px; font-size: .78rem; text-transform: uppercase;
  letter-spacing: .07em; color: var(--ink-faint); font-weight: 600;
}
.nowstrip .row { padding: 3px 0; border: 0; }

/* Fixed-width so the timestamps form a column the eye can scan down rather
 * than a ragged edge that has to be read word by word. */
.row-when {
  font-variant-numeric: tabular-nums; font-size: .8rem;
  color: var(--ink-faint); min-width: 108px;
}
.tag.ok   { color: #1f7a4d; border-color: #1f7a4d55; }
.tag.warn { color: #b3452f; border-color: #b3452f55; }

/* A refused attempt is tinted rather than shouted: a page of red would make
 * the ordinary background noise of the internet look like an emergency. */
.row.bad { background: color-mix(in srgb, #b3452f 6%, transparent); }

/* Folded older devices sit inside the person's device box, so they need to
 * read as a level deeper rather than as a sibling. */
.devbox .older { margin: 4px 0 0 10px; border-left: 2px solid var(--line);
                 padding-left: 9px; }
.devbox .older > summary { font-size: .82rem; color: var(--ink-faint); }

/* ── delete preview (v0.31.0) ───────────────────────────────────────────── */

/* A row thumbnail. The archive holds 212 clips named 00042.MOV and up, so the
 * filename is not an identifier a person can use. Requested at size=400 to hit
 * the cache the family app already filled, then scaled here. */
.mini {
  width: 46px; height: 46px; object-fit: cover; border-radius: 6px;
  background: var(--bg-sunken); border: 1px solid var(--line);
  cursor: zoom-in; flex: none;
}
.mini-none {
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--ink-faint); font-size: .8rem; cursor: default;
}

.previewback {
  position: fixed; inset: 0; z-index: 80;
  background: rgba(0, 0, 0, .62);
  display: flex; align-items: center; justify-content: center; padding: 20px;
}
.previewcard {
  background: var(--bg-raised); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 18px 20px;
  width: min(720px, 100%); max-height: 90vh; overflow: auto;
}
.previewcard h3 { margin: 0 0 12px; font-size: 1.02rem; }

/* Bounded so a 96-minute service does not open at its own size, and centred so
 * a portrait phone clip and a 16:9 film both sit in the same frame. */
.previewstage {
  background: #000; border-radius: 8px; overflow: hidden;
  display: flex; align-items: center; justify-content: center;
  min-height: 180px; max-height: 54vh;
}
.previewstage video, .previewstage img {
  max-width: 100%; max-height: 54vh; display: block;
}
.previewstage audio { width: 100%; margin: 18px; }
.previewstage .note { color: #ddd; padding: 10px 14px; }

.previewname {
  margin-top: 11px; font-weight: 560; font-size: .93rem; word-break: break-all;
}
.previewpath {
  color: var(--ink-faint); font-size: .78rem; margin-top: 3px;
  word-break: break-all; font-family: ui-monospace, monospace;
}
.previewbar {
  display: flex; gap: 9px; justify-content: flex-end; margin-top: 15px;
}
