/* ==========================================================================
 * File:      showcase.css
 * Location:  showcase/assets/css/showcase.css
 * Project:   Memory Vault
 * Version:   1.25.0
 * Updated:   2026-10-10
 * Purpose:   Layout and styling for the menu, search bar, Add Memories button,
 *            toast, music player, tile detail modal, hint and fallback.
 *
 * Change log
 *   1.25.0 2026-10-10  A collapsed sphere carries no controls: .intro-open is
 *                      gone, and .intro-core.is-reopen gives the name the
 *                      pointer cursor and focus ring it needs now that it is
 *                      the keyboard way back into a memory.
 *   1.24.0 2026-10-10  Fixed Save covering the landing-memory input: it reused
 *                      .vault-btn, which is a full-width form button defined
 *                      later in this file, so at equal specificity its
 *                      width:100% won and the button stretched across the row.
 *                      .set-save is now styled in full on its own, as a
 *                      compact inline control the height of the input.
 *   1.23.0 2026-10-10  The landing-memory control is a text field now, not a
 *                      dropdown: .set-select gives way to .set-entry,
 *                      .set-input, .set-save and the .set-suggest type-ahead.
 *   1.22.0 2026-10-10  Added .set-row--stack, .set-select and .set-status for
 *                      the landing-memory setting: a setting whose control
 *                      takes a line of its own under its label, with a status
 *                      line that collapses while it is empty.
 *   1.21.0 2026-10-06  Added .intro-add, the control offered on the bar when
 *                      the open collection has nothing in it yet.
 *   1.20.0 2026-10-06  Phones no longer show the scrolling index on the left:
 *                      the heading, item list and counter are hidden under
 *                      720px, leaving the search bar and the gear/sign-out row.
 *   1.19.0 2026-10-06  Added .vault-btn/.vault-error/.vault-note: the content
 *                      forms were using auth.css's equivalents, which index.php
 *                      never loads, so the buttons came out unstyled.
 *   1.18.0 2026-10-06  Added the content form styles (.vf fields, .up-strip
 *                      uploader and its progress bar) used by the add, edit
 *                      and media-ingest views.
 *   1.17.0 2026-10-06  Dropped the blanket .intro-labels.interactive rule: it
 *                      made every label hit-testable at once, including ones
 *                      faded to nothing. Each control now manages its own
 *                      pointer-events from showcase.js.
 *   1.16.0 2026-10-06  Close and Open are circular icon buttons now; the
 *                      search bar stays visible while the bar is closed.
 *   1.15.0 2026-10-05  Added .menu.vault-closed: hides the memories carousel
 *                      (heading, items, counter, search) while the bar is
 *                      collapsed, leaving the gear and sign-out reachable.
 *   1.14.0 2026-10-05  .intro-core.is-cta replaced by .intro-open: the sphere
 *                      now keeps its Family label with a smaller Open control
 *                      beneath it.
 *   1.12.0 2026-10-05  Family and Close moved onto the bar itself (drawn in
 *                      WebGL); the matching .intro-top/.intro-bottom elements
 *                      are now transparent hit targets parked over them.
 *   1.11.0 2026-10-05  Added .intro-labels: the "Family" label shown inside
 *                      the sphere, and the Family / Close labels carried by
 *                      the bar once the opening sequence has stretched it.
 *   1.10.0 2026-10-05  .topleft replaced by .menu-actions: the gear and
 *                      sign-out now sit inside the menu under the search bar
 *                      rather than fixed to the top-left corner.
 *   1.9.0  2026-10-05  Added .topleft (sign-out + gear cluster), the circular
 *                      .gear button, and the settings modal's rows, toggles
 *                      and QR list.
 *   1.8.0  2026-10-05  Added the .signout control shown when signed in.
 *   1.7.1  2026-10-04  Volume slider now sits on the pill's bottom edge
 *                      (absolute, inset to clear the rounded ends) rather
 *                      than as its own row; dot trimmed to 6px to suit.
 *   1.7.0  2026-10-04  Added the player's volume slider (.vol): a hairline
 *                      track filled to --vol with a small dot for the thumb.
 *   1.6.0  2026-10-04  The card no longer scrolls: it is a flex column
 *                      (header + tabs fixed, #modalBody taking the rest),
 *                      with overflow:hidden on the card and the only
 *                      scrollbar inside #modalBody, under the tabs. Tightened
 *                      padding, name, avatar and grid sizes to suit the
 *                      smaller tile-matched card showcase.js now sizes it to.
 *   1.5.0  2026-10-04  Modal now echoes its carousel tile's look: a slowly
 *                      drifting ::before glow tinted with --tile-hue (set by
 *                      showcase.js from the person's hue — the same value
 *                      the panel's own background uses), sitting behind
 *                      .modal-inner via explicit z-index so it never covers
 *                      the content. Added the avatar: a circular profile
 *                      picture, or an initials badge tinted the same way
 *                      when none is set, in a new .modal-header row.
 *   1.4.0  2026-10-04  Fixed the close button: it was positioned outside the
 *                      card's edge while the card clipped overflow, so only
 *                      its bottom-left quarter ever showed. Moved it inside
 *                      the card bounds and gave the header text clearance.
 *                      Added .modal-inner (fades in once the card's shape
 *                      animation finishes) and the left/top/width/height/
 *                      border-radius transitions the morph animates in
 *                      showcase.js — the modal now expands out of the tile
 *                      the person selected, rather than fading in centered.
 *   1.3.0  2026-10-04  Modal reworked: Photos/Videos/Audio are now tabs sharing
 *                      one placeholder-tile grid style; selecting a tile swaps
 *                      the grid for an enlarged viewer (big image, playing
 *                      video, or playing audio) with a caption and timestamp.
 *   1.2.0  2026-10-04  Added the tile detail modal: translucent glass card,
 *                      circular close button, photo/video/audio grids.
 *   1.1.0  2026-10-03  Added search bar with voice button, circular Add Memories
 *                      button, toast and search status styles; mobile layout updated.
 *                      Header project name changed to Memory Vault.
 *   1.0.1  2026-10-03  Added standard file header (version, location, change log).
 *                      No functional change.
 *   1.0.0  2026-10-03  Initial release.
 * ========================================================================== */
  :root{
    --void:#02050a;
    --ice:#d8f3ee;
    --ice-dim:rgba(216,243,238,.62);
    --teal:#62e6c9;
    --teal-line:rgba(110,240,200,.45);
    --violet:#9a82ff;
    --violet-hi:#efeaff;
    --magenta:#ff4f9a;
    --glass:rgba(4,14,15,.55);
    --row:41px;
    box-sizing:border-box;
    padding-top:env(safe-area-inset-top,0px);
    padding-bottom:env(safe-area-inset-bottom,0px);
  }
  /* The piece is a night scene by design, so both themes resolve to the same palette. */
  @media (prefers-color-scheme: dark){ :root:not([data-theme="light"]){ --void:#02050a; --ice:#d8f3ee; } }
  :root[data-theme="dark"]{ --void:#02050a; --ice:#d8f3ee; }
  html{ scroll-padding-top:env(safe-area-inset-top,0px); }
  *,*::before,*::after{ box-sizing:inherit; }
  html,body{ height:100%; margin:0; }
  body{
    background:var(--void); color:var(--ice); overflow:hidden; overscroll-behavior:none;
    font-family:"Chakra Petch", ui-monospace, "SF Mono", Menlo, Consolas, monospace;
    -webkit-font-smoothing:antialiased; touch-action:none;
  }
  #gl{ position:fixed; inset:0; width:100%; height:100%; display:block; cursor:grab; }
  #gl.dragging{ cursor:grabbing; }
  button{ font:inherit; color:inherit; }
  .sr{ position:absolute; width:1px; height:1px; overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; }

  /* ---------- left index ---------- */
  .menu{
    position:fixed; z-index:2;
    left:clamp(18px,3.4vw,64px);
    bottom:calc(env(safe-area-inset-bottom,0px) + clamp(18px,4vh,48px));
    width:min(320px,44vw);
  }
  .menu h2{
    margin:0 0 12px 8px; font-size:16px; font-weight:500; letter-spacing:.05em;
    text-transform:uppercase; color:var(--ice);
  }
  .menu-window{
    position:relative; height:calc(var(--row) * 5); perspective:520px;
    -webkit-mask-image:linear-gradient(transparent, #000 16%, #000 84%, transparent);
            mask-image:linear-gradient(transparent, #000 16%, #000 84%, transparent);
  }
  .menu-item{
    position:absolute; left:0; top:50%; height:var(--row); margin-top:calc(var(--row) / -2);
    display:flex; align-items:center; gap:10px; padding:0 10px;
    background:none; border:0; cursor:pointer; white-space:nowrap;
    color:var(--violet); font-size:17px; letter-spacing:.05em; text-transform:uppercase;
    transform-origin:left center; will-change:transform,opacity;
    transition:color .25s, text-shadow .25s;
  }
  .menu-item .arw{ display:inline-block; width:2.4em; opacity:.75; transition:transform .35s cubic-bezier(.2,.8,.2,1); }
  .menu-item.active{ color:var(--violet-hi); text-shadow:0 0 14px rgba(154,130,255,.75); }
  .menu-item.active .arw{ transform:translateX(5px); opacity:1; }
  .menu-item:hover{ color:var(--violet-hi); }
  .menu-item:focus-visible{ outline:1px solid var(--teal); outline-offset:-2px; border-radius:8px; }
  .menu-count{
    margin:14px 0 0 10px; font-size:13px; letter-spacing:.08em; color:var(--ice-dim);
    font-variant-numeric:tabular-nums;
  }

  /* ---------- music player ---------- */
  .player{
    position:fixed; z-index:3;
    top:calc(env(safe-area-inset-top,0px) + clamp(14px,2.4vw,38px));
    right:clamp(14px,2.6vw,48px);
    display:flex; flex-direction:column; align-items:center;
  }
  .pill{
    position:relative; display:flex; align-items:center; gap:14px;
    height:58px; padding:0 22px 0 10px; border-radius:999px;
    border:1.5px solid var(--teal-line);
    background:var(--glass);
    -webkit-backdrop-filter:blur(12px); backdrop-filter:blur(12px);
    box-shadow:inset 0 0 0 1px rgba(0,0,0,.45), 0 14px 40px -16px rgba(80,255,190,.55), 0 0 26px -8px rgba(80,255,190,.4);
  }
  .play{
    width:40px; height:40px; border-radius:50%; flex:none;
    border:1px solid rgba(200,255,240,.32); background:rgba(255,255,255,.04);
    display:grid; place-items:center; cursor:pointer; transition:background .2s, border-color .2s;
  }
  .play:hover{ background:rgba(98,230,201,.12); border-color:var(--teal); }
  .play svg{ width:14px; height:14px; fill:var(--ice); }
  .play:focus-visible,.sub button:focus-visible{ outline:1px solid var(--teal); outline-offset:2px; }
  #wave{ width:128px; height:28px; display:block; }
  .time{ min-width:44px; text-align:right; font-size:15px; letter-spacing:.04em; font-variant-numeric:tabular-nums; }
  .ticks{
    display:flex; gap:5px; align-items:flex-start; height:14px; margin-top:-1px;
  }
  .ticks i{
    width:1px; height:4px; background:var(--teal); opacity:.35; border-radius:1px;
    box-shadow:0 0 6px rgba(98,230,201,.9); transition:height .08s linear;
  }
  .sub{
    display:flex; align-items:stretch; height:40px; margin-top:4px;
    border:1px solid rgba(255,255,255,.13); border-radius:7px;
    background:rgba(6,11,13,.62); -webkit-backdrop-filter:blur(8px); backdrop-filter:blur(8px);
    font-size:12px; letter-spacing:.06em; text-transform:uppercase;
  }
  .sub button{
    width:38px; border:0; background:none; cursor:pointer; color:var(--ice-dim); font-size:13px;
    border-radius:6px; transition:color .2s, background .2s;
  }
  .sub button:hover{ color:var(--ice); background:rgba(255,255,255,.05); }
  .track{
    width:178px; padding:0 8px; display:flex; flex-direction:column; justify-content:center;
    border-left:1px solid rgba(255,255,255,.08); border-right:1px solid rgba(255,255,255,.08);
    overflow:hidden; white-space:nowrap;
  }
  .track-name{ overflow:hidden; text-overflow:clip; }
  .prog{ height:2px; margin-top:6px; background:rgba(255,255,255,.1); border-radius:2px; overflow:hidden; }
  .prog i{ display:block; height:100%; width:0; background:linear-gradient(90deg,var(--violet),var(--teal)); }

  /* ---------- volume: mute icon + hairline track + small dot ---------- */
  .vol-mute{
    position:absolute; left:56px; bottom:1px; width:20px; height:20px; padding:0;
    display:grid; place-items:center; cursor:pointer;
    border:0; background:none; color:var(--ice-dim);
    transition:color .2s;
  }
  .vol-mute:hover{ color:var(--ice); }
  .vol-mute[aria-pressed="true"]{ color:var(--magenta); }
  .vol-mute:focus-visible{ outline:1px solid var(--teal); outline-offset:2px; border-radius:4px; }

  /* ---------- volume: hairline track + small dot ---------- */
  /* rides the pill's bottom edge. Starts clear of the round play button on the
     left (which reaches ~50px in and sits low enough to steal the clicks) and
     stops short of the rounded right end. */
  .vol{
    -webkit-appearance:none; appearance:none;
    position:absolute; left:80px; right:22px; bottom:6px;
    width:auto; margin:0; height:8px; background:transparent; cursor:pointer;
    --vol:80; /* filled percentage, kept in sync by showcase.js */
  }
  .vol:focus{ outline:none; }
  .vol:focus-visible{ outline:1px solid var(--teal); outline-offset:2px; border-radius:6px; }
  /* the line: filled to --vol in teal, the remainder a faint rule */
  .vol::-webkit-slider-runnable-track{
    height:1px; border-radius:1px;
    background:linear-gradient(90deg,
      var(--teal) 0 calc(var(--vol) * 1%),
      rgba(216,243,238,.18) calc(var(--vol) * 1%) 100%);
  }
  .vol::-moz-range-track{ height:1px; border-radius:1px; background:rgba(216,243,238,.18); }
  .vol::-moz-range-progress{ height:1px; border-radius:1px; background:var(--teal); }
  .vol::-webkit-slider-thumb{
    -webkit-appearance:none; appearance:none;
    width:6px; height:6px; margin-top:-2.5px; border-radius:50%; border:0;
    background:var(--ice); box-shadow:0 0 7px rgba(98,230,201,.9);
    transition:transform .15s;
  }
  .vol::-moz-range-thumb{
    width:6px; height:6px; border-radius:50%; border:0;
    background:var(--ice); box-shadow:0 0 7px rgba(98,230,201,.9);
  }
  .vol:hover::-webkit-slider-thumb, .vol:active::-webkit-slider-thumb{ transform:scale(1.45); }
  .vol.is-muted::-webkit-slider-thumb{ background:var(--ice-dim); box-shadow:none; }
  .vol.is-muted::-moz-range-thumb{ background:var(--ice-dim); box-shadow:none; }

  @media (prefers-reduced-motion: reduce){
    .vol::-webkit-slider-thumb{ transition:none; }
  }

  /* The memories carousel belongs to an open collection: heading, item list,
     counter and search all fade out when the bar collapses to a sphere. The
     gear and sign-out stay put, so settings remain reachable while closed, and
     so does the search bar. */
  .menu h2, .menu-window, .menu-count{
    transition:opacity .45s ease;
  }
  .menu.vault-closed h2,
  .menu.vault-closed .menu-window,
  .menu.vault-closed .menu-count{
    opacity:0; pointer-events:none;
  }

  /* ---------- settings + sign out, under the search bar ---------- */
  .menu-actions{
    display:flex; align-items:center; gap:10px;
    margin:14px 0 0 2px;
  }
  .gear{
    width:38px; height:38px; flex:none; border-radius:50%; cursor:pointer;
    display:grid; place-items:center;
    border:1px solid rgba(255,255,255,.14); background:rgba(6,11,13,.5);
    -webkit-backdrop-filter:blur(10px); backdrop-filter:blur(10px);
    color:var(--ice-dim); transition:color .2s, border-color .2s, transform .35s cubic-bezier(.2,.8,.2,1);
  }
  .gear:hover{ color:var(--ice); border-color:var(--teal-line); transform:rotate(60deg); }
  .gear:active{ transform:rotate(60deg) scale(.94); }
  .gear:focus-visible{ outline:1px solid var(--teal); outline-offset:2px; }

  .signout{
    padding:8px 15px; border-radius:999px; cursor:pointer;
    border:1px solid rgba(255,255,255,.14); background:rgba(6,11,13,.5);
    -webkit-backdrop-filter:blur(10px); backdrop-filter:blur(10px);
    color:var(--ice-dim); font:inherit; font-size:12px; letter-spacing:.08em;
    text-transform:uppercase; transition:color .2s, border-color .2s;
  }
  .signout:hover{ color:var(--ice); border-color:var(--teal-line); }
  .signout:disabled{ opacity:.5; cursor:default; }
  .signout:focus-visible{ outline:1px solid var(--teal); outline-offset:2px; }
  @media (max-width:720px){
    .signout{ font-size:11px; padding:6px 11px; }
    .gear{ width:34px; height:34px; }
    .menu-actions{ margin-top:10px; }
  }

  /* ---------- opening sequence labels ---------- */
  .intro-labels{ position:fixed; inset:0; z-index:2; pointer-events:none; }
  .intro-labels > *{
    position:absolute; left:0; top:0; opacity:0;
    transform:translate(-50%,-50%); will-change:transform,opacity;
    white-space:nowrap; text-transform:uppercase; letter-spacing:.18em;
    color:var(--ice); text-shadow:0 0 18px rgba(2,8,10,.9);
  }
  .intro-core{ font-size:clamp(15px,1.7vw,20px); font-weight:500; }
  /* While collapsed, the name IS the control — no icon, but it must still show
     that it can be pressed and must take a visible focus ring from the keyboard. */
  .intro-core.is-reopen{ cursor:pointer; }
  .intro-core.is-reopen:focus-visible{
    outline:1px solid var(--teal); outline-offset:6px; border-radius:6px;
  }
  /* once Close has collapsed the bar, Family stays and Open appears beneath it */

  /* Family and Close are painted on the bar in WebGL; these two stay only as
     hit targets sitting over them, so Close is still a real focusable button
     and both are still announced to screen readers. */
  /* Family stays painted on the bar; this is just its hit target. */
  .intro-top{
    font:inherit; font-size:13px; letter-spacing:.18em; text-transform:uppercase;
    color:transparent; text-shadow:none; background:none; border:0;
    padding:10px 26px; border-radius:999px;
  }
  /* Close and Open: matching circular glass buttons */
  /* Offered when a collection is open but still empty. */
  .intro-add{
    display:flex; align-items:center; gap:10px; padding:11px 20px; border-radius:999px;
    font:inherit; font-size:13px; letter-spacing:.1em; text-transform:uppercase;
    border:1px solid var(--teal-line); background:rgba(4,14,15,.55);
    -webkit-backdrop-filter:blur(10px); backdrop-filter:blur(10px);
    color:var(--ice); cursor:pointer; white-space:nowrap;
    box-shadow:0 0 30px -12px rgba(80,255,190,.7);
    transition:border-color .2s, transform .15s;
  }
  .intro-add:hover{ border-color:var(--teal); }
  .intro-add:active{ transform:scale(.97); }
  .intro-add:focus-visible{ outline:1px solid var(--teal); outline-offset:3px; }
  .intro-add svg{ flex:none; }

  /* Close, on the bar. The sphere has no icons at all — .intro-open is gone. */
  .intro-bottom{
    width:38px; height:38px; padding:0; border-radius:50%;
    display:grid; place-items:center; cursor:pointer;
    border:1px solid var(--teal-line); background:rgba(4,14,15,.5);
    -webkit-backdrop-filter:blur(10px); backdrop-filter:blur(10px);
    color:var(--ice-dim); text-shadow:none;
    transition:color .2s, border-color .2s, transform .2s;
  }
  .intro-bottom:hover{ color:var(--ice); border-color:var(--teal); }
  .intro-bottom:active{ transform:scale(.93); }
  .intro-bottom:focus-visible{ outline:1px solid var(--teal); outline-offset:3px; }

  .hint{
    position:fixed; left:50%; bottom:calc(env(safe-area-inset-bottom,0px) + 22px); transform:translateX(-50%);
    font-size:12px; letter-spacing:.12em; text-transform:uppercase; color:var(--ice-dim);
    transition:opacity .8s; pointer-events:none; z-index:2;
  }
  .hint.gone{ opacity:0; }
  .fallback{
    position:fixed; inset:0; display:grid; place-items:center; text-align:center; padding:24px;
    font-size:16px; color:var(--ice-dim);
  }


  /* ---------- search (under the menu) ---------- */
  .search{
    position:relative; display:flex; align-items:center; gap:8px;
    margin:16px 0 0 0; height:46px; padding:0 6px 0 14px; border-radius:999px;
    border:1.5px solid rgba(154,130,255,.55);
    background:rgba(12,8,26,.45); -webkit-backdrop-filter:blur(10px); backdrop-filter:blur(10px);
    transition:border-color .25s, box-shadow .25s;
  }
  .search:focus-within{ border-color:var(--violet-hi); box-shadow:0 0 22px -6px rgba(154,130,255,.7); }
  .search-ico{ width:16px; height:16px; flex:none; fill:none; stroke:var(--violet); stroke-width:1.8; stroke-linecap:round; }
  .search input{
    flex:1; min-width:0; height:100%; border:0; outline:0; background:none;
    color:var(--violet-hi); font:inherit; font-size:15px; letter-spacing:.05em;
  }
  .search input::placeholder{ color:var(--violet); opacity:.75; text-transform:uppercase; }
  .search input::-webkit-search-cancel-button{ filter:hue-rotate(200deg); }
  .mic{
    position:relative; width:34px; height:34px; flex:none; border-radius:50%;
    border:1px solid rgba(154,130,255,.4); background:rgba(154,130,255,.08);
    display:grid; place-items:center; cursor:pointer; transition:background .2s, border-color .2s;
  }
  .mic svg{ width:16px; height:16px; fill:none; stroke:var(--violet-hi); stroke-width:1.6; stroke-linecap:round; }
  .mic:hover{ background:rgba(154,130,255,.2); }
  .mic:focus-visible{ outline:1px solid var(--teal); outline-offset:2px; }
  .mic.listening{ border-color:var(--magenta); background:rgba(255,79,154,.18); }
  .mic.listening::after{
    content:""; position:absolute; inset:-5px; border-radius:50%;
    border:1.5px solid var(--magenta); animation:micPulse 1.2s ease-out infinite;
  }
  @keyframes micPulse{ from{ transform:scale(.85); opacity:.9; } to{ transform:scale(1.35); opacity:0; } }
  .search-status{ min-height:18px; margin:8px 0 0 14px; font-size:12px; letter-spacing:.06em; color:var(--ice-dim); }

  /* ---------- add memories (circular, bottom right) ---------- */
  .add-wrap{
    position:fixed; z-index:3; display:flex; align-items:center; gap:14px; flex-direction:row-reverse;
    right:clamp(18px,3vw,56px); bottom:calc(env(safe-area-inset-bottom,0px) + clamp(18px,3vw,48px));
  }
  .add-btn{
    position:relative; width:74px; height:74px; border-radius:50%; cursor:pointer;
    border:1.5px solid var(--teal-line); background:var(--glass);
    -webkit-backdrop-filter:blur(12px); backdrop-filter:blur(12px);
    display:grid; place-items:center;
    box-shadow:inset 0 0 0 1px rgba(0,0,0,.45), 0 0 30px -8px rgba(80,255,190,.5);
    transition:transform .3s cubic-bezier(.2,.8,.2,1), border-color .25s, box-shadow .25s;
  }
  .add-btn::before{
    content:""; position:absolute; inset:6px; border-radius:50%;
    border:1px dashed rgba(110,240,200,.35); animation:addSpin 18s linear infinite;
  }
  @keyframes addSpin{ to{ transform:rotate(360deg); } }
  .add-btn svg{ width:26px; height:26px; fill:none; stroke:var(--ice); stroke-width:1.8; stroke-linecap:round; transition:transform .3s; }
  .add-btn:hover{ border-color:var(--teal); box-shadow:inset 0 0 0 1px rgba(0,0,0,.45), 0 0 40px -6px rgba(80,255,190,.75); }
  .add-btn:hover svg{ transform:rotate(90deg); }
  .add-btn:active{ transform:scale(.94); }
  .add-btn:focus-visible{ outline:1px solid var(--teal); outline-offset:4px; }
  .add-label{ font-size:13px; letter-spacing:.1em; text-transform:uppercase; color:var(--ice-dim); }

  .toast{
    position:fixed; z-index:4; right:clamp(18px,3vw,56px);
    bottom:calc(env(safe-area-inset-bottom,0px) + clamp(18px,3vw,48px) + 92px);
    max-width:min(340px, calc(100vw - 36px)); padding:12px 16px; border-radius:12px;
    border:1px solid var(--teal-line); background:rgba(4,14,15,.82);
    -webkit-backdrop-filter:blur(12px); backdrop-filter:blur(12px);
    font-size:14px; line-height:1.45; color:var(--ice);
    opacity:0; transform:translateY(8px); pointer-events:none; transition:opacity .3s, transform .3s;
  }
  .toast.show{ opacity:1; transform:none; }

  @media (prefers-reduced-motion: reduce){
    .add-btn::before, .mic.listening::after{ animation:none; }
  }

  /* ---------- tile detail modal ---------- */
  .modal-overlay{
    position:fixed; inset:0; z-index:10; display:flex; align-items:center; justify-content:center;
    padding:clamp(16px,4vh,56px) clamp(16px,4vw,40px);
    background:radial-gradient(120% 140% at 50% 20%, rgba(6,14,18,.35), rgba(1,3,6,.72));
    -webkit-backdrop-filter:blur(3px); backdrop-filter:blur(3px);
    opacity:0; pointer-events:none; transition:opacity .32s ease;
  }
  .modal-overlay.open{ opacity:1; pointer-events:auto; }
  .modal-card{
    /* The card itself never scrolls — it is sized to the grown tile by
       showcase.js. Overflow is handled inside #modalBody below. */
    position:relative; width:min(760px, 100%); max-height:100%;
    display:flex; flex-direction:column; overflow:hidden;
    border-radius:22px; padding:clamp(20px,2.6vw,30px) clamp(18px,2.6vw,30px) clamp(16px,2vw,22px);
    border:1.5px solid var(--teal-line);
    background:
      linear-gradient(165deg, rgba(14,32,32,.5), rgba(5,10,14,.58)),
      var(--glass);
    -webkit-backdrop-filter:blur(22px) saturate(1.3); backdrop-filter:blur(22px) saturate(1.3);
    box-shadow:inset 0 0 0 1px rgba(0,0,0,.4), 0 30px 90px -24px rgba(0,0,0,.65), 0 0 60px -18px rgba(80,255,190,.35);
    transform:scale(.92) translateY(10px);
    transition:
      transform .36s cubic-bezier(.2,.8,.2,1),
      left .5s cubic-bezier(.22,.8,.2,1), top .5s cubic-bezier(.22,.8,.2,1),
      width .5s cubic-bezier(.22,.8,.2,1), height .5s cubic-bezier(.22,.8,.2,1),
      border-radius .5s cubic-bezier(.22,.8,.2,1);
  }
  .modal-overlay.open .modal-card{ transform:none; }
  /* While morphing out of / back into a tile, the card is pinned with inline
     position:fixed + left/top/width/height (see showcase.js openModal/closeModal);
     the transition list above animates those instead of the scale/translate entrance. */

  /* A soft, slowly-drifting glow tinted with --tile-hue (set per-person by
     showcase.js, the same hue the carousel panel's own background uses) —
     carries that tile's colour identity into the modal, present from the
     very first frame of the morph, not just after it lands. */
  .modal-card::before{
    content:""; position:absolute; inset:0; z-index:0; pointer-events:none;
    border-radius:inherit; overflow:hidden; filter:blur(6px);
    background:
      radial-gradient(55% 65% at 22% 12%, hsla(calc(var(--tile-hue, 0.7) * 360), 85%, 62%, .38), transparent 70%),
      radial-gradient(50% 60% at 82% 88%, hsla(calc((var(--tile-hue, 0.7) + 0.5) * 360), 80%, 58%, .26), transparent 70%);
    animation:modalNebula 16s ease-in-out infinite alternate;
  }
  @keyframes modalNebula{
    0%{ transform:translate(0, 0) rotate(0deg); }
    100%{ transform:translate(3%, -4%) rotate(5deg); }
  }
  .modal-inner{
    position:relative; z-index:1; /* paint above the ::before glow */
    display:flex; flex-direction:column; flex:1; min-height:0; /* min-height:0 lets the body shrink and scroll */
    opacity:0; transform:translateY(6px); pointer-events:none;
    transition:opacity .3s ease, transform .3s ease;
  }
  /* The only scrolling region: tab content (tile grid or enlarged viewer). */
  #modalBody{
    flex:1; min-height:0; overflow-y:auto; overscroll-behavior:contain;
    -webkit-overflow-scrolling:touch; padding-right:4px;
    scrollbar-width:thin; scrollbar-color:var(--teal-line) transparent;
  }
  #modalBody::-webkit-scrollbar{ width:8px; }
  #modalBody::-webkit-scrollbar-track{ background:transparent; }
  #modalBody::-webkit-scrollbar-thumb{ background:rgba(110,240,200,.3); border-radius:4px; }
  #modalBody::-webkit-scrollbar-thumb:hover{ background:rgba(110,240,200,.5); }
  .modal-card.revealed .modal-inner{ opacity:1; transform:none; pointer-events:auto; }
  .modal-close{
    position:absolute; top:16px; right:16px; width:48px; height:48px; border-radius:50%;
    border:1.5px solid var(--teal-line); background:var(--glass);
    -webkit-backdrop-filter:blur(14px); backdrop-filter:blur(14px);
    display:grid; place-items:center; cursor:pointer; z-index:2;
    box-shadow:inset 0 0 0 1px rgba(0,0,0,.45), 0 10px 30px -10px rgba(0,0,0,.6);
    transition:transform .2s, border-color .2s;
  }
  .modal-close svg{ width:17px; height:17px; fill:none; stroke:var(--ice); stroke-width:1.8; stroke-linecap:round; }
  .modal-close:hover{ border-color:var(--teal); transform:scale(1.07); }
  .modal-close:active{ transform:scale(.95); }
  .modal-close:focus-visible{ outline:1px solid var(--teal); outline-offset:3px; }

  .modal-header{ flex:none; display:flex; align-items:flex-start; gap:16px; padding-right:54px; } /* keep clear of the close button */
  .modal-avatar{
    flex:none; width:52px; height:52px; border-radius:50%; object-fit:cover;
    border:1.5px solid hsla(calc(var(--tile-hue, 0.7) * 360), 70%, 70%, .55);
    box-shadow:0 0 0 1px rgba(0,0,0,.35), 0 8px 22px -10px rgba(0,0,0,.6);
  }
  .modal-avatar--fallback{
    display:flex; align-items:center; justify-content:center;
    font-size:19px; font-weight:500; letter-spacing:.03em; color:#fff;
    background:
      linear-gradient(155deg, hsla(calc(var(--tile-hue, 0.7) * 360), 80%, 66%, .9), hsla(calc((var(--tile-hue, 0.7) + 0.5) * 360), 75%, 50%, .9));
  }
  .modal-header-text{ min-width:0; }
  .modal-eyebrow{ font-size:13px; letter-spacing:.1em; text-transform:uppercase; color:var(--violet); margin:0 0 6px; }
  .modal-name{ margin:0; font-size:clamp(20px,2.6vw,28px); font-weight:500; letter-spacing:.02em; color:var(--ice); line-height:1.15; }
  .modal-dob{ margin:8px 0 0; font-size:14px; letter-spacing:.04em; color:var(--ice-dim); }
  .modal-caption{ margin:4px 0 0; font-size:14px; color:var(--ice-dim); font-style:italic; }

  /* ---- tabs (Photos / Videos / Audio) ---- */
  .modal-tabs{
    flex:none; display:flex; gap:6px; margin-top:18px; border-bottom:1px solid rgba(255,255,255,.1);
  }
  .modal-tab{
    position:relative; display:flex; align-items:center; gap:8px; cursor:pointer;
    padding:10px 4px 12px; border:0; background:none; color:var(--ice-dim);
    font:inherit; font-size:13px; letter-spacing:.1em; text-transform:uppercase;
    transition:color .2s;
  }
  .modal-tab + .modal-tab{ margin-left:22px; }
  .modal-tab svg{ width:15px; height:15px; fill:none; stroke:currentColor; stroke-width:1.5; opacity:.85; }
  .modal-tab .cnt{ font-variant-numeric:tabular-nums; opacity:.65; font-size:11px; }
  .modal-tab:hover{ color:var(--ice); }
  .modal-tab.active{ color:var(--ice); }
  .modal-tab.active::after{
    content:""; position:absolute; left:0; right:0; bottom:-1px; height:2px;
    background:linear-gradient(90deg, var(--teal), var(--violet)); border-radius:2px;
  }
  .modal-tab:focus-visible{ outline:1px solid var(--teal); outline-offset:2px; border-radius:4px; }

  /* ---- tile grid: shared placeholder style across photos/videos/audio ---- */
  .modal-grid{ display:grid; grid-template-columns:repeat(auto-fill, minmax(104px,1fr)); gap:10px; margin-top:16px; }
  .media-tile{
    position:relative; aspect-ratio:1; border-radius:12px; overflow:hidden; cursor:pointer;
    border:1px solid rgba(255,255,255,.1); background:rgba(255,255,255,.02);
    display:flex; flex-direction:column; align-items:center; justify-content:center; gap:6px;
    color:rgba(220,250,245,.55); text-align:center; padding:8px;
    transition:border-color .2s, transform .2s, color .2s;
  }
  .media-tile:hover, .media-tile:focus-visible{ border-color:var(--teal-line); transform:translateY(-2px); color:var(--ice); }
  .media-tile:focus-visible{ outline:1px solid var(--teal); outline-offset:2px; }
  .media-tile svg{ width:24px; height:24px; fill:none; stroke:currentColor; stroke-width:1.4; opacity:.85; }
  .media-tile span{ font-size:11px; letter-spacing:.03em; line-height:1.3; }
  .media-tile img{ position:absolute; inset:0; width:100%; height:100%; object-fit:cover; }
  .media-tile.has-file{ color:rgba(220,250,245,.85); border-color:rgba(110,240,200,.3); }
  .media-tile.has-file.is-photo span{ display:none; }
  .media-tile.has-file.is-photo svg{ display:none; }
  .media-tile .play-badge{
    position:absolute; bottom:6px; right:6px; width:20px; height:20px; border-radius:50%;
    background:rgba(2,8,10,.75); display:none; align-items:center; justify-content:center;
  }
  .media-tile.has-file .play-badge{ display:flex; }
  .media-tile .play-badge svg{ width:9px; height:9px; fill:var(--teal); stroke:none; opacity:1; }

  /* ---- enlarged viewer (replaces the grid while a tile is open) ---- */
  .modal-viewer{ margin-top:16px; }
  .viewer-back{
    display:inline-flex; align-items:center; gap:6px; margin-bottom:16px; cursor:pointer;
    border:0; background:none; color:var(--ice-dim); font:inherit; font-size:12px;
    letter-spacing:.08em; text-transform:uppercase; padding:4px 2px;
  }
  .viewer-back:hover{ color:var(--ice); }
  .viewer-back:focus-visible{ outline:1px solid var(--teal); outline-offset:2px; }
  .viewer-back svg{ width:13px; height:13px; fill:none; stroke:currentColor; stroke-width:2; stroke-linecap:round; }
  .viewer-stage{
    display:flex; align-items:center; justify-content:center;
    border-radius:14px; background:rgba(0,0,0,.3); border:1px solid rgba(255,255,255,.08);
    overflow:hidden; min-height:200px;
  }
  .viewer-stage img{ display:block; width:100%; max-height:46vh; object-fit:contain; }
  .viewer-stage video{ display:block; width:100%; max-height:46vh; background:#000; }
  .viewer-stage.is-empty{ flex-direction:column; gap:14px; padding:50px 20px; color:rgba(220,250,245,.45); }
  .viewer-stage.is-empty svg{ width:48px; height:48px; fill:none; stroke:currentColor; stroke-width:1.2; }
  .viewer-stage.is-empty span{ font-size:13px; letter-spacing:.04em; }
  .viewer-stage.is-audio{ flex-direction:column; gap:22px; padding:40px 24px; background:rgba(0,0,0,.22); }
  .viewer-audio-icon{
    width:76px; height:76px; border-radius:50%; display:flex; align-items:center; justify-content:center;
    background:radial-gradient(circle at 35% 30%, rgba(154,130,255,.35), rgba(80,255,190,.12));
    border:1px solid var(--teal-line);
  }
  .viewer-audio-icon svg{ width:30px; height:30px; fill:none; stroke:var(--ice); stroke-width:1.4; }
  .viewer-stage audio{ width:100%; max-width:420px; }

  /* ---------- settings modal ---------- */
  .set-row{
    display:flex; align-items:center; justify-content:space-between; gap:16px;
    padding:13px 0; border-bottom:1px solid rgba(255,255,255,.07);
  }
  .set-row:last-child{ border-bottom:0; }
  .set-label{ font-size:14px; color:var(--ice); }
  .set-hint{ display:block; margin-top:3px; font-size:12px; color:var(--ice-dim); }
  /* a small sliding switch, same hairline language as the volume control */
  .set-toggle{
    position:relative; flex:none; width:42px; height:22px; border-radius:999px; cursor:pointer;
    border:1px solid rgba(255,255,255,.18); background:rgba(255,255,255,.05); padding:0;
    transition:background .2s, border-color .2s;
  }
  .set-toggle::after{
    content:""; position:absolute; top:2px; left:2px; width:16px; height:16px; border-radius:50%;
    background:var(--ice-dim); transition:transform .2s, background .2s;
  }
  .set-toggle[aria-checked="true"]{ background:rgba(98,230,201,.22); border-color:var(--teal-line); }
  .set-toggle[aria-checked="true"]::after{ transform:translateX(20px); background:var(--teal); }
  .set-toggle:focus-visible{ outline:1px solid var(--teal); outline-offset:3px; }
  /* a setting whose control needs a full line of its own, under its label */
  .set-row--stack{ flex-direction:column; align-items:stretch; gap:9px; }
  /* a free-text name plus its Save button, on one line. align-items:stretch
     makes the button take the input's height, so the two line up without
     either one carrying a hand-tuned height that the other has to match. */
  .set-entry{ display:flex; align-items:stretch; gap:9px; max-width:100%; }
  .set-suggest-wrap{ position:relative; flex:1 1 auto; min-width:0; }
  .set-input{
    width:100%; padding:10px 12px; border-radius:9px; font:inherit; font-size:15px;
    color:var(--ice); background:rgba(12,8,26,.45);
    border:1.5px solid rgba(154,130,255,.35);
    outline:none; transition:border-color .2s, box-shadow .2s, opacity .2s;
  }
  .set-input::placeholder{ color:var(--ice-dim); opacity:.7; }
  .set-input:focus{ border-color:var(--violet-hi); box-shadow:0 0 18px -6px rgba(154,130,255,.6); }
  .set-input:disabled{ opacity:.55; }
  /* Deliberately NOT .vault-btn: that one is a full-width form button, is
     defined further down this file, and at equal specificity its width:100%
     won — which left Save lying across the whole row on top of the input.
     This is the same hairline language at the size a inline control wants. */
  .set-save{
    flex:0 0 auto; align-self:stretch;
    display:inline-flex; align-items:center; justify-content:center;
    padding:0 16px; border-radius:9px; cursor:pointer;
    font:inherit; font-size:13px; letter-spacing:.06em; text-transform:uppercase;
    color:var(--ice); background:rgba(98,230,201,.1);
    border:1.5px solid var(--teal-line);
    transition:border-color .2s, background .2s, opacity .2s, transform .15s;
  }
  .set-save:hover:not(:disabled){ border-color:var(--teal); background:rgba(98,230,201,.18); }
  .set-save:active:not(:disabled){ transform:scale(.97); }
  .set-save:disabled{ opacity:.5; cursor:default; }
  .set-save:focus-visible{ outline:1px solid var(--teal); outline-offset:3px; }

  /* the type-ahead: a shortcut for names already loaded, never the whole set */
  .set-suggest{
    position:absolute; z-index:4; left:0; right:0; top:calc(100% + 5px);
    margin:0; padding:5px; list-style:none; border-radius:10px;
    max-height:232px; overflow-y:auto;
    background:rgba(8,12,20,.96); border:1px solid var(--teal-line);
    -webkit-backdrop-filter:blur(14px); backdrop-filter:blur(14px);
    box-shadow:0 18px 40px -16px rgba(0,0,0,.9);
  }
  .set-suggest-item{
    padding:9px 11px; border-radius:7px; font-size:14px; color:var(--ice); cursor:pointer;
    white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
  }
  .set-suggest-item:hover, .set-suggest-item.hi{ background:rgba(98,230,201,.16); }
  /* empty until something is saved, so it must not reserve a line */
  .set-status{ margin:0; font-size:12px; line-height:1.5; color:var(--ice-dim); }
  .set-status:empty{ display:none; }
  .set-status--ok{ color:var(--teal); }
  .set-status--warn{ color:#ffd9e4; }

  /* ---------- content forms: add / edit / upload ---------- */
  .vf{ display:flex; flex-direction:column; gap:6px; margin-bottom:13px; }
  .vf > span{ font-size:12px; letter-spacing:.09em; text-transform:uppercase; color:var(--ice-dim); }
  .vf input, .vf select, .vf textarea{
    padding:10px 12px; border-radius:9px; font:inherit; font-size:15px; color:var(--ice);
    border:1.5px solid rgba(154,130,255,.35); background:rgba(12,8,26,.45);
    outline:none; transition:border-color .2s, box-shadow .2s; resize:vertical;
  }
  .vf input:focus, .vf select:focus, .vf textarea:focus{
    border-color:var(--violet-hi); box-shadow:0 0 18px -6px rgba(154,130,255,.6);
  }
  .vf select option{ background:#0d1418; color:var(--ice); }
  .vf input[type="date"]{ color-scheme:dark; }
  .vf-actions{ margin-top:6px; }
  .vf-save{ min-height:44px; }

  /* Buttons and messages for the content forms. These used to lean on
     auth.css, which index.php does not load — hence unstyled buttons. */
  .vault-btn{
    display:flex; align-items:center; justify-content:center; gap:10px;
    width:100%; min-height:46px; padding:0 18px; border-radius:999px; cursor:pointer;
    border:1.5px solid var(--teal-line); background:var(--glass); color:var(--ice);
    -webkit-backdrop-filter:blur(12px); backdrop-filter:blur(12px);
    font:inherit; font-size:15px; letter-spacing:.04em;
    box-shadow:inset 0 0 0 1px rgba(0,0,0,.4), 0 0 26px -12px rgba(80,255,190,.55);
    transition:border-color .2s, transform .15s, box-shadow .2s, opacity .2s;
  }
  .vault-btn:hover:not(:disabled){ border-color:var(--teal); box-shadow:inset 0 0 0 1px rgba(0,0,0,.4), 0 0 36px -10px rgba(80,255,190,.8); }
  .vault-btn:active:not(:disabled){ transform:scale(.98); }
  .vault-btn:disabled{ opacity:.55; cursor:default; }
  .vault-btn:focus-visible{ outline:1px solid var(--teal); outline-offset:3px; }
  .vault-error{
    margin:12px 0 0; padding:11px 13px; border-radius:9px; font-size:13px; line-height:1.5;
    color:#ffd9e4; background:rgba(255,79,154,.1); border:1px solid rgba(255,79,154,.35);
  }
  .vault-note{ margin:0; font-size:13px; line-height:1.55; color:var(--ice-dim); }

  .up-strip{ display:flex; align-items:center; gap:12px; flex-wrap:wrap; margin:14px 0 10px; }
  .up-pick{
    display:inline-flex; align-items:center; gap:8px; cursor:pointer;
    padding:9px 16px; border-radius:999px; font-size:13px; letter-spacing:.06em;
    border:1px solid var(--teal-line); background:rgba(4,14,15,.5); color:var(--ice);
    transition:border-color .2s, color .2s;
  }
  .up-pick:hover{ border-color:var(--teal); }
  .up-pick:focus-within{ outline:1px solid var(--teal); outline-offset:2px; }
  .up-note{ font-size:12px; color:var(--ice-dim); flex:1; min-width:140px; }
  .up-progress{ height:3px; border-radius:2px; background:rgba(255,255,255,.1); overflow:hidden; margin-bottom:12px; }
  .up-progress i{ display:block; height:100%; width:0; background:linear-gradient(90deg,var(--violet),var(--teal)); transition:width .2s; }

  .qr-list{ display:flex; flex-wrap:wrap; gap:18px; justify-content:center; margin-top:4px; }
  .qr-item{ flex:1 1 170px; max-width:220px; text-align:center; }
  .qr-item .qr{ border-radius:10px; padding:9px; background:#fff; }
  .qr-title{ margin:10px 0 2px; font-size:13px; letter-spacing:.06em; text-transform:uppercase; color:var(--ice); }
  .qr-url{ margin:0; font-size:11px; color:var(--ice-dim); word-break:break-all; }

  .viewer-meta{ margin-top:14px; }
  .viewer-caption{ margin:0; font-size:15px; color:var(--ice); }
  .viewer-caption:empty::before{ content:"No description yet"; color:var(--ice-dim); font-style:italic; }
  .viewer-timestamp{ margin:4px 0 0; font-size:12px; letter-spacing:.04em; color:var(--ice-dim); font-variant-numeric:tabular-nums; }

  @media (prefers-reduced-motion: reduce){
    .modal-overlay, .modal-card, .modal-close, .media-tile{ transition:none; }
    .modal-card::before{ animation:none; }
  }

  @media (max-width:720px){
    :root{ --row:34px; }
    /* No room for the scrolling index on a phone: the tiles themselves are
       the navigation there (swipe or drag). The search bar, gear and sign-out
       stay, so the collection is still reachable without it. */
    .menu h2, .menu-window, .menu-count{ display:none; }
    .menu{ top:auto; bottom:calc(env(safe-area-inset-bottom,0px) + 18px); width:calc(100vw - 130px); max-width:320px; }
    .search{ height:42px; margin-top:0; }
    .add-btn{ width:60px; height:60px; }
    .add-label{ display:none; }
    .toast{ bottom:calc(env(safe-area-inset-bottom,0px) + 100px); }
    .hint{ display:none; }
    .pill{ height:50px; gap:10px; padding:0 16px 0 8px; }
    .play{ width:34px; height:34px; }
    .vol{ left:66px; right:16px; bottom:5px; }
    .vol-mute{ left:40px; }
    #wave{ width:84px; }
    .track{ width:140px; }
    .modal-overlay{ padding:0; align-items:stretch; }
    .modal-card{ width:100%; max-height:100%; height:100%; border-radius:0; border:none; padding-top:60px; }
    .modal-close{ top:14px; right:14px; width:44px; height:44px; }
    .modal-header{ gap:12px; padding-right:48px; }
    .modal-avatar{ width:48px; height:48px; }
    .modal-avatar--fallback{ font-size:17px; }
    .modal-grid{ grid-template-columns:repeat(auto-fill, minmax(92px,1fr)); }
    .modal-tab + .modal-tab{ margin-left:14px; }
    .modal-tab .cnt{ display:none; }
    .viewer-stage img, .viewer-stage video{ max-height:48vh; }
  }
