/* Shared layout tokens — single source of truth for navbar height, iOS PWA
   safe-area insets, and z-index layering. Reference these from every view
   instead of hardcoding 56px / 1090 / etc. — that's how things drifted before
   and is what made tweaks accidentally break sticky/fixed anchors. */
:root {
  --nav-height:   56px;
  --safe-top:     env(safe-area-inset-top, 0px);
  --safe-right:   env(safe-area-inset-right, 0px);
  --safe-bottom:  env(safe-area-inset-bottom, 0px);
  --safe-left:    env(safe-area-inset-left, 0px);

  /* z-index ladder — pick the closest tier rather than inventing a new
     number. New floating UI in-page → --z-floating; nav-anchored popups
     (avatar/site menus) → --z-nav-menu. Modal stays above --z-floating so
     dialogs claim the screen while open (that's why bd/modal are bumped
     above their Bootstrap defaults of 1050/1055 — see the override below). */
  --z-nav:        1030;  /* Bootstrap fixed-top */
  --z-overlay:    1040;  /* scrims, click-traps above the nav, below content */
  --z-floating:   1090;  /* in-page floating UI: action clusters, section pill, FABs */
  --z-modal-bd:   1094;
  --z-modal:      1095;
  --z-nav-menu:   2000;  /* nav-anchored popups — top of stack */
}

body {
  /* !important is load-bearing here: dozens of views ship inline
     `style="padding-top:56px"` (legacy of pre-token days), which would
     otherwise win the cascade and zero out the iOS PWA safe-area inset
     — leaving the navbar and the page header visible *behind* the
     translucent status bar on Dynamic Island devices. */
  padding-top: calc(var(--nav-height) + var(--safe-top)) !important;
}

.breadcrumb-item+.breadcrumb-item::before {
  color: #fff !important;
}

.navbar-brand {
  margin-right: 0 !important;
}

/* iOS standalone-mode (PWA) intermittently detaches position:fixed
   elements during scroll / overscroll bounce — they briefly track the
   scroll position instead of staying pinned. Promoting them to their
   own compositor layer (translate3d + will-change) makes iOS treat
   them as a separate render target that the scroll handler can't
   move. Applies to .fixed-top (the global nav), every .fab (back-to-
   top buttons etc.), and the album/MorgenGram action clusters.

   Trade-off: a transformed ancestor breaks backdrop-filter on iOS
   Safari descendants — the cosmetic saturate(1.2) inside #ev-actions /
   #mg-actions buttons effectively becomes a no-op. We keep the GPU
   promotion since fixed-element detachment is worse than losing 20%
   saturation. New floating elements should add a class to this list
   (or use .fab) rather than inventing a new id-based selector. */
.fixed-top, .fab,
#ev-actions, #mg-actions, #mg-cal-popup, #mg-view-menu {
  -webkit-transform: translate3d(0, 0, 0);
  transform: translate3d(0, 0, 0);
  will-change: transform;
  -webkit-backface-visibility: hidden;
  backface-visibility: hidden;
}

/* Photo upload triggers carry data-upload-trigger so public/js/upload-
   fullres.js can attach long-press / Shift+click handlers for the
   "Upload at full resolution" toggle. The CSS below suppresses iOS
   Safari's text selection + long-press callout on those elements so
   the gesture is purely ours — the JS-level contextmenu preventDefault
   alone isn't enough, the selection fires first. */
[data-upload-trigger] {
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
}

/* Per-mode upload-trigger paint. Cycled by long-press / Shift+click on
   any [data-upload-trigger] via public/js/upload-fullres.js:
     normal → default blue (no override here)
     hires  → purple
     orig   → gold
   !important is intentional — it has to win against Bootstrap .btn-primary,
   album/morgenGram .*-top-btn rules, and visit-live .btn-plain rules in
   a single declaration. */
body.upload-mode-hires [data-upload-trigger] {
  background-color: #6f42c1 !important;
  border-color: #6f42c1 !important;
  color: #fff !important;
}
body.upload-mode-hires [data-upload-trigger]:hover,
body.upload-mode-hires [data-upload-trigger]:focus {
  background-color: #5a32a3 !important;
  border-color: #5a32a3 !important;
  color: #fff !important;
}
body.upload-mode-orig [data-upload-trigger] {
  background-color: #c9a227 !important;
  border-color: #c9a227 !important;
  color: #fff !important;
}
body.upload-mode-orig [data-upload-trigger]:hover,
body.upload-mode-orig [data-upload-trigger]:focus {
  background-color: #a8841d !important;
  border-color: #a8841d !important;
  color: #fff !important;
}

/* Standard floating action button. Sits at the bottom-right of the
   viewport, respects iOS PWA safe-area on both sides so the home
   indicator doesn't clip the button. Layers at --z-floating, below
   modals but above other in-page floating UI. Pair with .fab-left or
   override `right` for variants. */
.fab {
  position: fixed;
  right:  calc(1rem + var(--safe-right));
  bottom: calc(1rem + var(--safe-bottom));
  z-index: var(--z-floating);
}

/* Full-viewport-minus-nav container for messaging-style pages where the
   composer/input must stay anchored to the bottom. We use lvh (LARGE
   viewport height) instead of dvh because iOS PWAs report dvh
   inconsistently — sometimes excluding safe areas, sometimes
   including them, sometimes shrinking with the soft keyboard — which
   left the composer either off-screen or floating mid-screen
   depending on the device. lvh always equals the full physical
   viewport in standalone PWA mode. We subtract --nav-height (for
   the fixed navbar) and --safe-top (matching body's padding-top)
   so shell-top sits exactly below the navbar and shell-bottom lands
   at the physical screen bottom; composer's own padding-bottom (in
   chat.css) keeps the textarea above the home indicator pill.
   Combine with display:flex; flex-direction:column on the same
   element — see chat.css/.chat-root, group.ejs/.group-shell. */
.page-full-height {
  height: calc(100vh - var(--nav-height) - var(--safe-top));
  height: calc(100lvh - var(--nav-height) - var(--safe-top));
}
/* Date popup (album) still centers itself (position:fixed; left:50%), so fold
   the GPU promotion into its centering transform to avoid killing it. */
#ev-date-bar-popup {
  -webkit-transform: translate3d(-50%, 0, 0);
  transform: translate3d(-50%, 0, 0);
  will-change: transform;
  -webkit-backface-visibility: hidden;
  backface-visibility: hidden;
}
/* The date pill itself no longer centers via transform — it's a flex child of
   #ev-float-pills (album.ejs), centered by the container. It must NOT carry a
   translateX(-50%) (that double-shifted it half its width to the left on every
   viewport). Keep only the GPU promotion, with NO horizontal offset. */
#ev-date-bar {
  -webkit-transform: translateZ(0);
  transform: translateZ(0);
  will-change: transform;
  -webkit-backface-visibility: hidden;
  backface-visibility: hidden;
}

/* Bootstrap's default modal z-index is 1055 (backdrop 1050), below the
   album's floating section pill (z 1091). That made the pill paint over
   open modals. Raise modals above --z-floating so dialogs own the screen
   while they're up. The nav popups (--z-nav-menu, 2000) sit higher still
   — they only open by themselves, so this doesn't conflict. */
.modal { --bs-modal-zindex: var(--z-modal); z-index: var(--z-modal) !important; }
.modal-backdrop { --bs-backdrop-zindex: var(--z-modal-bd); z-index: var(--z-modal-bd) !important; }

/* Lightbox overlay: when EITHER fullscreen photo lightbox is open, force
   the body background to black so the iOS PWA safe-bottom strip (where
   position:fixed; inset:0 clamps short of the screen bottom on iOS) shows
   continuous black instead of the page background bleeding through. The
   lightbox itself is inset:0; background:#000 — this just extends that
   black underneath into the home-indicator area.

   Uses :has() (Safari 15.4+, Chrome 105+) so we don't have to wire body
   class toggles into every JS open/close path. Falls back gracefully on
   older browsers — the gap stays visible but nothing breaks. */
body:has(#ev-lightbox[style*="display: block"]),
body:has(#mg-lightbox[style*="display: block"]),
body:has(#ev-slideshow[style*="display: block"]),
body:has(#mg-slideshow[style*="display: block"]) {
  background: #000 !important;
}

/* iOS PWA safe-area inset for Bootstrap modals. In standalone mode with
   viewport-fit=cover + apple-mobile-web-app-status-bar-style=black-
   translucent (set in theme-init.ejs), the page renders BEHIND the
   status bar / Dynamic Island. body padding-top already accounts for
   that, but Bootstrap's .modal-dialog uses its own margin (0.5rem on
   small screens, 1.75rem on >=576px) measured from the top of the
   viewport, so a top-aligned modal's title row sits behind the time/
   signal/battery indicators. In a regular browser --safe-top is 0 so
   this rule is a no-op; in PWA it pushes the dialog down by the inset.
   Applies to vertically-centered modals too — they're centered in
   100% minus the margins, so adding to the top margin just shifts the
   centered area down, which is the intended behavior. */
.modal-dialog {
  margin-top: calc(0.5rem + var(--safe-top));
}
@media (min-width: 576px) {
  .modal-dialog {
    margin-top: calc(1.75rem + var(--safe-top));
  }
}

/* Card shadow tokens. The slate-tinted shadow pair below is duplicated
   verbatim across .store-hero / .song-hero / .panel / .music-toolbar /
   .alb-tile and several home-page tiles. Define once here so a tweak to
   the elevation system applies everywhere. */
:root {
  --card-shadow:       0 1px 2px rgba(15, 23, 42, 0.04), 0 4px 12px rgba(15, 23, 42, 0.04);
  --card-shadow-hover: 0 2px 6px rgba(15, 23, 42, 0.08), 0 18px 40px rgba(15, 23, 42, 0.12);
}

/* Empty-state card — dashed border on a muted background, used as the
   "no items yet" placeholder in memories + travel. Consolidated from 4
   byte-identical per-view copies; theme-init.ejs handles the dark-mode
   background swap via the .empty-card entry in its card-class list. */
.empty-card {
  border: 2px dashed var(--app-card-border, rgba(0, 0, 0, 0.15));
  border-radius: 0.6rem;
  padding: 3rem 1rem;
  text-align: center;
  color: var(--app-muted, #6c757d);
}

/* Bootstrap tab override used by memories + travel top-bar nav. The
   default .active state inherits transparent background which looks
   broken against the page bg. Swap to the card surface. */
.top-tabs .nav-link.active {
  background: var(--app-card-bg, #fff);
  color: var(--app-page-fg, #212529);
  border-color: var(--app-card-border, rgba(0, 0, 0, 0.125))
                var(--app-card-border, rgba(0, 0, 0, 0.125))
                transparent;
}

/* Uppercase tracked section label used to introduce groups of tiles or
   list rows. Consolidated from store / music-list / albums-list. */
.section-heading {
  font-size: 0.78rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: #64748b;
  margin: 0 0 14px;
  display: flex;
  align-items: center;
  gap: 8px;
}
.section-heading i {
  font-size: 1rem;
}

/* Share-list card — used by both /memories (user-default share) and the
   memory edit page (per-memory extraShare). Markup in
   views/partials/_memory-share-card.ejs, behavior in
   public/js/memory-share.js. Kept here so a future use site picks up
   the same look automatically. */
.share-card {
  background: var(--app-card-bg, #fff);
  border: 1px solid var(--app-card-border, #e3e6ea);
  border-radius: 0.6rem;
  /* No overflow:hidden — the search-results dropdown is position:
     absolute and needs to escape the card's bounds when matches
     extend past the bottom edge. The summary's rounded corners hold
     up fine without clipping. */
}
.share-card > summary {
  list-style: none;
  cursor: pointer;
  padding: 0.7rem 1rem;
  font-weight: 600;
  display: flex; align-items: center; gap: 0.5rem;
}
.share-card > summary::-webkit-details-marker { display: none; }
.share-card > summary::after {
  content: ''; margin-left: auto;
  width: 0.6rem; height: 0.6rem;
  border-right: 2px solid var(--app-muted, #6c757d);
  border-bottom: 2px solid var(--app-muted, #6c757d);
  transform: rotate(45deg);
  transition: transform 0.15s ease;
}
.share-card[open] > summary::after { transform: rotate(-135deg); }
.share-count { color: var(--app-muted, #6c757d); font-weight: 400; font-size: 0.95rem; }
.share-card .share-body { padding: 0.5rem 1rem 1rem; border-top: 1px solid var(--app-divider, #e9ecef); }
.share-chips { display: flex; flex-wrap: wrap; gap: 0.4rem; margin: 0.6rem 0; min-height: 0.5rem; }
.share-chip {
  display: inline-flex; align-items: center; gap: 0.35rem;
  background: var(--bs-primary, #0d6efd); color: white;
  border-radius: 999px;
  padding: 0.18rem 0.4rem 0.18rem 0.5rem;
  font-size: 0.9rem;
}
/* Visually distinguish group pills (slate) from user pills (blue) so a
   glance tells you which is which without reading the chip's text. */
.share-chip-group { background: #475569; }
.share-chip .share-chip-name { line-height: 1.1; }
.share-chip button {
  background: rgba(255,255,255,0.2); border: 0; color: white;
  border-radius: 999px;
  width: 1.3rem; height: 1.3rem;
  display: inline-flex; align-items: center; justify-content: center;
  cursor: pointer;
  padding: 0;
}
.share-chip button:hover { background: rgba(255,255,255,0.35); }
/* Search dropdown — anchored under the input. position:relative on the
   parent .share-search keeps the absolutely-positioned list aligned. */
.share-search { position: relative; }
.share-search input { max-width: 24rem; }
.share-results {
  position: absolute;
  z-index: 5;
  margin: 0; padding: 0;
  list-style: none;
  background: var(--app-card-bg, #fff);
  border: 1px solid var(--app-card-border, #e3e6ea);
  border-radius: 0.4rem;
  box-shadow: 0 4px 14px rgba(0,0,0,0.12);
  max-width: 24rem; min-width: 16rem;
  max-height: 18rem; overflow-y: auto;
  display: none;
  margin-top: 0.2rem;
}
.share-result {
  display: flex; align-items: center; gap: 0.5rem;
  padding: 0.4rem 0.6rem;
  cursor: pointer;
  border-bottom: 1px solid var(--app-divider, #f1f3f5);
}
.share-result:last-child { border-bottom: 0; }
.share-result:hover { background: var(--app-divider, #f1f3f5); }
.share-result i.bi-collection { color: #475569; font-size: 1.05rem; }
.share-result-tag {
  margin-left: auto;
  font-size: 0.75rem;
  color: var(--app-muted, #6c757d);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
/* Collections feature: tag chip used on item cards and the tags
   management page. Color is set inline on the element from the tag's
   stored HSL — the type accepts both an anchor (clickable filter
   link) and a span (plain label) without restyling. */
.coll-tag-chip {
  display: inline-flex; align-items: center; gap: .25rem;
  padding: .12rem .55rem; border-radius: 999px;
  color: #fff !important; font-size: .78rem; line-height: 1.4;
  text-shadow: 0 0 1px rgba(0,0,0,.45);
  white-space: nowrap;
}
.coll-tag-chip:hover { filter: brightness(1.15); }
.coll-tag-chip-input {
  background: transparent; border: 0; outline: 0; color: inherit;
  text-shadow: inherit; font: inherit; padding: 0; min-width: 4ch;
}
.coll-tag-filter-banner {
  display: inline-flex; align-items: center; gap: .5rem;
  padding: .35rem .75rem; border-radius: 999px;
  background: var(--bs-body-tertiary-bg);
  border: 1px solid var(--bs-border-color);
  font-size: .9rem;
}

/* Collections tag picker search dropdown. The rows use Bootstrap
   list-group classes; we just need to size + scroll them and give
   the keyboard-highlighted row a visible accent. */
.coll-tag-picker-search .coll-tag-picker-results {
  max-height: 16rem;
  overflow-y: auto;
}
.coll-tag-picker-search .list-group-item.active {
  background: var(--bs-body-tertiary-bg);
  color: var(--bs-body-color);
  border-color: var(--bs-border-color);
}

/* Inactive filter chip — faded background so the active filter stands
   out without recoloring it. */
.coll-tag-chip-dim { opacity: .55; }
.coll-tag-chip-dim:hover { opacity: 1; }
