/* ===== RESET & BASE ===== */
*, *::before, *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
  /* Every iOS browser (Safari, Chrome, etc. — Apple requires them all to run on WebKit) draws its
     own default gray tap-highlight flash over any tappable element, and it ignores that element's
     actual border-radius (e.g. the sidebar's flush-left/rounded-right items, or the full-screen
     drawer overlay) — rendering as a mismatched rounded "halo" that bleeds past the element's real
     bounds. The app already provides its own tuned :hover/:active/.active styling everywhere a tap
     needs feedback, so the browser's own effect is redundant at best and visibly broken at worst. */
  -webkit-tap-highlight-color: transparent;
}

/* Every button in this app sets its own background/border via its own class (.btn-primary,
   .btn-search-icon, .btn-theme, etc.), but without this reset, WebKit's native default button
   chrome (a light/white pill-ish appearance) can still show through or around that custom
   background on real iOS devices — invisible against a light page, but glaringly visible now
   that :root defaults to dark (reported live: solid white circles behind the header's search and
   settings-gear icons on an iPhone 16 Pro, both Safari and Chrome). */
button {
  appearance: none;
  -webkit-appearance: none;
  font: inherit;
  color: inherit;
}

/* ===== THEME VARIABLES ===== */
/* :root now holds the DARK palette directly, so dark is the true zero-JS default — the very
   first paint, before main.js has even loaded let alone read the stored preference back and
   called applyTheme(). It used to be the reverse ([data-theme="dark"] as the override, :root as
   a light default), which meant anything rendered before applyTheme() ran (in practice, most
   visibly the web build's sign-in gate, which main.js can hold open indefinitely waiting on a
   visitor) stayed stuck in light styling — reported live as the demo's sign-in screen looking
   wrong even though the rest of the app around it was already dark. applyTheme() itself is
   unchanged: it still always sets data-theme to 'dark' or 'light' explicitly either way, so
   toggling still works exactly as before — only the no-attribute-yet default flipped. */
:root {
  /* Lets native controls (checkboxes, scrollbars, etc.) render with an appropriate default
     appearance for the active theme instead of always defaulting to a light-mode look that
     clashes against a dark surface. */
  color-scheme: dark;
  /* Material 3 motion easing tokens (https://m3.material.io/styles/motion/easing-and-duration) —
     theme-independent, so defined once here rather than repeated in [data-theme="light"]. M3's
     asymmetry between entering and exiting motion is the point: entrances use a "decelerate"
     curve (fast start, gentle settle — reads as arriving/growing into place) and exits use an
     "accelerate" curve (slow start, fast finish — reads as snappier, getting out of the way).
     "emphasized" is the more pronounced version M3 recommends for larger/more prominent
     transitions (e.g. a modal appearing) vs. "standard" for smaller, quieter ones (e.g. an
     accordion row). Both are single-cubic-bezier approximations of M3's true (multi-segment)
     curves — close enough for anything CSS `transition-timing-function` can express. */
  --m3-standard:              cubic-bezier(0.2, 0, 0, 1);
  --m3-standard-accelerate:   cubic-bezier(0.3, 0, 1, 1);
  --m3-standard-decelerate:   cubic-bezier(0, 0, 0, 1);
  --m3-emphasized-accelerate: cubic-bezier(0.3, 0, 0.8, 0.15);
  --m3-emphasized-decelerate: cubic-bezier(0.05, 0.7, 0.1, 1);
  --bg:              #1A1B1E;
  --surface:         #25262B;
  --border:          #373A40;
  --text-primary:    #E8EAED;
  --text-secondary:  #9AA0A6;
  --text-muted:      #6B7280;
  --primary:         #5B5BEF;
  --primary-bg:      #2D2945;
  --primary-hover:   #6A5EE0;
  --hover-bg:        #2C2D32;
  /* Was a lighter gray (#45454A), then black (#000000), per two earlier requests — now matches
     var(--bg), the same dark gray already used for the page's own content-area background (see
     body, base.css below), per a later request that black read too harsh. Kept as its own variable
     rather than reusing --bg directly, since the two are conceptually different things that just
     happen to share a value right now. */
  --active-bg-light: var(--bg);
  --search-bg:       #3A3B42;
  --search-focus-bg: #44454D;
  --modal-bg:        #25262B;
  --scrollbar:       #373A40;
  --scrollbar-hover: #4A4B50;
  --shadow-card:     rgba(0,0,0,0.35);
  --shadow-modal:    rgba(0,0,0,0.50);
}

/* Tunes the timing of every View Transition on the page (currently just the sidebar's
   accordion-open transition — renderSidebar.js's withViewTransition) — the platform default is a
   snappy 0.25s ease cross-fade, which read as more of a flicker than a section actually "opening."
   ::view-transition-group() only takes a literal name or this universal selector, so this rule is
   necessarily page-wide rather than sidebar-scoped — filed here rather than in sidebar.css so that
   scope is explicit, not implied by which file it happens to live in. --m3-standard (above) is M3's
   own recommended curve for "smaller, quieter" transitions like an accordion row, reused here
   instead of a one-off bezier value. No-op on browsers without View Transitions support — these
   pseudo-elements simply don't exist there, same graceful fallback as the JS side. */
::view-transition-group(*) {
  animation-duration: 0.32s;
  animation-timing-function: var(--m3-standard);
}

[data-theme="light"] {
  color-scheme: light;
  --bg:              #F8F9FA;
  --surface:         #ffffff;
  --border:          #E0E0E0;
  --text-primary:    #202124;
  --text-secondary:  #80868B;
  --text-muted:      #9AA0A6;
  --primary:         #5B5BEF;
  --primary-bg:      #EBE8FD;
  --primary-hover:   #4A3DD4;
  --hover-bg:        #F1F3F4;
  --active-bg-light: var(--bg);
  --search-bg:       #F1F3F4;
  --search-focus-bg: #ffffff;
  --modal-bg:        #ffffff;
  --scrollbar:       #DADCE0;
  --scrollbar-hover: #BDC1C6;
  --shadow-card:     rgba(0,0,0,0.12);
  --shadow-modal:    rgba(0,0,0,0.20);
}

/* html, not just body: the actual root scrolling/panning box for the document is the html
   element (documentElement), not body. body's own overflow: hidden happens to propagate up to
   the viewport per the CSS overflow-propagation rule, but touch-action does NOT propagate the
   same way — it only constrains touch gestures on the element it's set on and that element's own
   descendants. With pan-y declared on body alone, a touch that begins panning is still recognized
   at the html/document root level, one level above where the restriction lives, so the whole page
   — background and any open modal alike, since they're both just descendants of the same
   unrestricted root — could still be dragged left/right as a single unit (reported live: the
   entire dashboard, and a detail modal open on top of it, sliding sideways together). Repeating
   both rules here closes that gap at the actual root. */
html {
  height: 100%;
  overflow: hidden;
  touch-action: pan-y;
}

body {
  font-family: -apple-system, BlinkMacSystemFont, 'Google Sans', 'Segoe UI', sans-serif;
  background: var(--bg);
  color: var(--text-primary);
  /* REAL BUG, found and fixed: this static 100vh — not misc.css's .main-layout height calc,
     which was fixed earlier to 100dvh but could never have mattered — is what actually caused
     the mobile Kanban board's bottom gaps (reported live, screenshot confirmed). body has
     overflow: hidden, so whatever height it computes to is a hard ceiling every descendant is
     clipped to, regardless of what any of them calculate for themselves further down — 100vh not
     tracking iOS Safari's toolbar meant that ceiling sat short of the true visible area. 100dvh
     (with the two-declaration vh fallback, since an unsupported value is simply ignored rather
     than needing @supports) fixes it at its actual source instead. */
  height: 100vh;
  height: 100dvh;
  overflow: hidden;
  /* overflow: hidden above is meant to make the whole page unscrollable — any real scrolling
     happens in an inner container (.grid-area, .detail-body, etc.) — but iOS Safari has a long-
     documented quirk where overflow: hidden alone doesn't reliably stop a touch drag from
     panning the page horizontally if any descendant is even briefly wider than the viewport
     (reported live: the whole screen shifting sideways with the search bar open, whose fixed
     210px expanded width doesn't fit next to the rest of the header on a phone — see
     .search-expand-input's own mobile-width fix). touch-action is the modern, reliable fix for
     that same gap — and belongs here once, not per-descendant: touch-action isn't something a
     child can loosen back up, the browser intersects it down through the whole subtree, so this
     single rule already covers the header, every modal, the sidebar, etc. below it. (The same
     pair is also set on html above it — see that rule's own comment for why body alone isn't
     the actual root scrolling box.) */
  touch-action: pan-y;
  display: flex;
  flex-direction: column;
  font-size: 14px;
  transition: background 0.2s, color 0.2s;
}

/* ===== HEADER ===== */
.header {
  display: flex;
  align-items: stretch;
  height: 64px;
  background: var(--surface);
  position: sticky;
  top: 0;
  z-index: 400;
  flex-shrink: 0;
}

.header-sidebar {
  width: 300px;
  flex-shrink: 0;
  border-right: 1px solid var(--border);
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0 16px;
}

.header-main {
  flex: 1;
  display: flex;
  align-items: center;
  padding: 0 20px;
  position: relative;
  gap: 16px;
  border-bottom: 1px solid var(--border);
}

.logo {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  font-size: 20px;
  font-weight: 700;
  /* A lighter periwinkle purple than --primary, scoped to just the logo (per direct request,
     matching a color swatch sent live) — everything else (buttons, etc.) keeps --primary as-is. */
  color: #5e60fb;
  white-space: nowrap;
  letter-spacing: -0.02em;
  pointer-events: none;
}

/* Sidebar header controls */
.sidebar-header-controls {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
}

.sidebar-header-title {
  font-size: 17px;
  font-weight: 600;
  color: var(--text-primary);
  display: flex;
  align-items: center;
  gap: 4px;
}

.sidebar-back-btn {
  background: none;
  border: none;
  color: inherit;
  font: inherit;
  cursor: pointer;
  padding: 2px;
  display: flex;
  align-items: center;
  gap: 4px;
  border-radius: 4px;
  transition: color 0.15s;
}

.sidebar-back-btn:hover {
  color: var(--primary);
}

/* Options dropdown (Home/My Saves/Shared Saves/Curated/⚡ VC) — shown on hover of
   .sidebar-collapse-wrap; see sidebar.css for the trigger and .btn-sidebar-collapse for the
   merged collapse-toggle + menu-trigger button it hangs off of. */
.my-options-dropdown {
  position: absolute;
  top: 100%; /* flush against the button — no gap for the cursor to lose hover in en route */
  left: 0;
  min-width: 180px;
  background: var(--primary);
  border-radius: 14px;
  box-shadow: 0 4px 24px rgba(0,0,0,0.4);
  overflow: hidden;
  z-index: 500;
  opacity: 0;
  visibility: hidden;
  /* Shows instantly on hover; hiding is delayed 0.25s so briefly overshooting the target while
     moving the mouse toward it doesn't close the menu before you get there. */
  transition: opacity 0.12s ease, visibility 0s linear 0.25s;
}

.my-options-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 11px 18px 11px 10px;
  background: none;
  border: none;
  color: #fff;
  font-size: 14px;
  text-align: left;
  white-space: nowrap;
  cursor: pointer;
}

.my-options-item svg {
  flex-shrink: 0;
  width: 18px;
  height: 18px;
}

.my-options-item-icon {
  flex-shrink: 0;
  width: 18px;
  text-align: center;
  font-size: 15px;
}

.my-options-item-arrow {
  flex-shrink: 0;
  margin-left: auto;
  margin-right: -8px;
}

.my-options-item:hover {
  background: rgba(255,255,255,0.15);
}

.my-options-item--sponsored {
  border-top: 1px solid rgba(255,255,255,0.15);
  color: #FFD700;
  background: rgba(255,215,0,0.15);
  font-weight: 700;
}

.my-options-item--sponsored:hover {
  background: rgba(255,215,0,0.25);
}

/* Search expand */
.search-expand-wrap {
  position: relative;
  display: flex;
  flex-direction: row-reverse;
  align-items: center;
  gap: 0;
}

/* Global search results panel (globalSearch.js) — right-anchored to the wrap's own right edge
   (not centered) since .search-expand-wrap grows toward the logo via row-reverse, so a
   left-anchored panel would risk overflowing past the header's right edge on desktop. Rows reuse
   .step1-result-row/-art/-info/-title/-meta (addEditModal.css) directly — same thumbnail+title+
   meta visual language as the Add-modal's own title search, no new row-level CSS needed. z-index
   above .header's own 400, below modal overlays (500+). */
.global-search-results {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  min-width: 320px;
  max-width: 380px;
  max-height: 340px;
  overflow-y: auto;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  box-shadow: 0 8px 24px rgba(0,0,0,0.18);
  z-index: 450;
  padding: 4px;
}
.global-search-results[hidden] { display: none; }
/* .step1-result-row (addEditModal.css) has no border-radius of its own — it normally sits in a
   square-cornered results list; this panel is a rounded card, so its rows get a light radius to
   match rather than looking squared-off inside a rounded container. */
.global-search-results .step1-result-row {
  border-radius: 8px;
}

.search-expand-input {
  width: 0;
  padding: 0;
  border: 1.5px solid transparent;
  border-radius: 20px;
  outline: none;
  background: transparent;
  font-size: 14px;
  color: var(--text-primary);
  overflow: hidden;
  transition: width 0.25s ease, padding 0.25s ease, border-color 0.25s ease, margin 0.25s ease, background 0.25s ease;
  white-space: nowrap;
}

.search-expand-wrap.open .search-expand-input {
  width: 210px;
  padding: 8px 14px;
  margin-left: 8px;
  border-color: var(--primary);
  background: var(--search-bg);
}

.search-expand-input::placeholder {
  color: var(--text-secondary);
}

/* Search icon button */
.btn-search-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  border: none;
  background: var(--search-bg);
  color: var(--text-secondary);
  cursor: pointer;
  flex-shrink: 0;
  transition: background 0.15s;
}

.btn-search-icon:hover {
  background: var(--search-focus-bg);
  color: var(--text-primary);
}

.btn-search-icon .icon-close { display: none; }
.search-expand-wrap.open .btn-search-icon .icon-search { display: none; }
.search-expand-wrap.open .btn-search-icon .icon-close { display: block; }
/* Purple circle while searching (was the same neutral --search-bg the resting state uses), per
   direct request — reads as a real "active" state, not just a swapped icon. Hardcoded to the
   same #5B5BEF hex the "Open"/"Sort"/"Add +" widget buttons use (dashboard.css) rather than the
   --primary token both are already set to — same resolved color either way, but this rules out
   any theming/inheritance difference between the two as the cause of a reported mismatch, and
   explicit opacity: 1 rules out any transparency softening it. */
.search-expand-wrap.open .btn-search-icon {
  background: #5B5BEF;
  opacity: 1;
  color: #fff;
}
.search-expand-wrap.open .btn-search-icon:hover,
.search-expand-wrap.open .btn-search-icon:active {
  background: #fff;
  opacity: 1;
  color: var(--primary);
}

.header-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-left: auto;
}

.sort-select {
  padding: 7px 48px 7px 18px;
  border: 1px solid var(--border);
  border-radius: 24px;
  font-size: 13px;
  background-color: var(--surface);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 24 24' fill='none' stroke='%2380868B' stroke-width='2.5'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 18px center;
  appearance: none;
  cursor: pointer;
  outline: none;
  color: var(--text-primary);
  width: 160px;
}

/* Custom replacement for what used to be a native #sort-select <select> (sortSelect.js) — this
   class is only on #musicgenre-select now (still a real <select>, out of scope for the redesign);
   #sort-select itself is the wrapper below. Positioning context for .sort-select-dropdown, which
   reuses .board-filter-dropdown (kanban.css) for its own panel chrome. */
.sort-select-custom {
  position: relative;
  display: inline-block;
}

/* Copies .sort-select's own pill box styling above, minus appearance:none/the baked-in
   background-image chevron — this has a real child <svg> chevron instead (index.html), so
   duplicating both would show two arrows. */
.sort-select-trigger {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  width: 160px;
  padding: 7px 18px;
  border: 1px solid var(--border);
  border-radius: 24px;
  font-size: 13px;
  background-color: var(--surface);
  color: var(--text-primary);
  cursor: pointer;
  outline: none;
}
.sort-select-trigger svg {
  flex-shrink: 0;
  opacity: 0.6;
}
.sort-select-trigger-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Wider than the trigger — "Release Date (Newest)" needs more room than the 160px pill to stay on
   one line. top:calc(100% + 6px) comes from the reused .board-filter-dropdown class, but its own
   left:0 is overridden here — REAL BUG, found and fixed: #sort-select always sits at (or very
   near) the right edge of its row (.grid-header-right's own margin-left:auto pushes it there on
   every page; the mobile dual-dropdown row puts it flush against the row's own right edge too),
   so a 220px panel opening from the trigger's LEFT edge ran off the right side of the
   viewport/content area (reported live, screenshot confirmed — cut off badly on mobile, slightly
   on desktop). right:0 opens it from the trigger's right edge instead, growing leftward, which
   stays inside the viewport regardless of how close to the edge the trigger itself sits.
   REAL BUG, found and fixed: that override never actually applied — .board-filter-dropdown
   (kanban.css, loaded AFTER this file in index.html) also sets left:0 on this same element (it
   carries both classes, index.html), and same-specificity + later-source-order meant its left:0
   silently won regardless of what this rule said, still cutting the panel off on mobile (reported
   live again on the Musicians/musicgenre: dual-dropdown row specifically: "the sort dropdown is
   getting cut off on the right side"). With left, width, and right all specified on an
   absolutely-positioned box, the browser drops `right` and anchors from `left` in LTR — so this
   really was opening from the trigger's left edge the whole time, off the right side of the
   screen, exactly matching the report. Selector bumped to two classes (matching the markup's own
   `sort-select-dropdown board-filter-dropdown`) so its specificity wins outright, regardless of
   which file loads last — the same fix shape already used elsewhere this session for this exact
   class of cascade bug. */
.sort-select-dropdown.board-filter-dropdown {
  width: 220px;
  left: auto;
  right: 0;
}

.sort-select-search-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 12px;
  color: var(--text-muted);
}
.sort-select-search-row svg {
  flex-shrink: 0;
}
.sort-select-search-input {
  flex: 1;
  min-width: 0;
  border: none;
  outline: none;
  background: none;
  font-size: 13px;
  color: var(--text-primary);
  padding: 0;
}
.sort-select-search-input::placeholder {
  color: var(--text-muted);
}

.btn-add-item {
  padding: 7px 22px;
  background: var(--primary);
  color: white;
  border: none;
  border-radius: 24px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  cursor: pointer;
  transition: background 0.15s;
  white-space: nowrap;
}

.btn-add-item:hover,
.btn-add-item:active {
  background: #fff;
  color: var(--primary);
}

/* Share button */
.share-btn-wrap {
  position: relative;
  display: flex;
  align-items: stretch;
}

.btn-share {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 8px 14px;
  background: var(--search-bg);
  color: var(--text-primary);
  border: none;
  border-radius: 24px 0 0 24px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  cursor: pointer;
  transition: background 0.15s;
  white-space: nowrap;
}

.btn-share:hover {
  background: var(--search-focus-bg);
}

.btn-share-arrow {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 8px 10px;
  background: var(--search-bg);
  color: var(--text-secondary);
  border: none;
  border-left: 1px solid var(--border);
  border-radius: 0 24px 24px 0;
  cursor: pointer;
  transition: background 0.15s;
}

.btn-share-arrow:hover {
  background: color-mix(in srgb, var(--primary-bg) 70%, var(--primary) 30%);
}

.share-dropdown {
  display: none;
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  background: var(--search-bg);
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: 0 8px 24px var(--shadow-card);
  min-width: 190px;
  z-index: 150;
  overflow: hidden;
}

.share-dropdown.open {
  display: block;
}

.share-option {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 11px 16px;
  border: none;
  background: none;
  font-size: 14px;
  color: var(--text-primary);
  cursor: pointer;
  text-align: left;
  transition: background 0.12s;
}

.share-option:hover {
  background: var(--hover-bg);
}

.share-dropdown-divider {
  height: 1px;
  background: var(--border);
  margin: 4px 0;
}

.share-people-count {
  color: var(--text-secondary);
  font-size: 13px;
  cursor: default;
  pointer-events: none;
}

/* Settings wrap + dropdown */
.settings-wrap {
  position: relative;
  margin-right: 3px;
}

.settings-dropdown {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  min-width: 160px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  box-shadow: 0 4px 20px var(--shadow-modal);
  overflow: hidden;
  z-index: 500;
}

.settings-option {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 10px 14px;
  background: none;
  border: none;
  color: var(--text-primary);
  font-size: 13px;
  font-weight: 500;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
  transition: background 0.15s;
  box-sizing: border-box;
}

.settings-option:hover {
  background: var(--hover-bg);
}

/* Gray circle behind the profile row's person icon, per direct request ("put a gray circle
   behind the only profile icon in the drop down") — the one icon in this menu that reads as a
   real identity mark rather than a plain action glyph (Light/Dark mode, Sponsors, About), so it's
   the only one singled out with a badge. Reuses .btn-theme's own gray-circle treatment
   (background: var(--search-bg), same trigger button that opens this very dropdown) rather than
   inventing a second gray. */
.profile-menu-icon-badge {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: var(--search-bg);
  color: var(--text-secondary);
  flex-shrink: 0;
}

.settings-divider {
  height: 1px;
  background: var(--border);
  margin: 2px 0;
}

/* Theme toggle button */
.btn-theme {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  border: none;
  background: var(--search-bg);
  color: var(--text-secondary);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background 0.15s, color 0.15s;
  flex-shrink: 0;
}

.btn-theme:hover {
  background: var(--search-focus-bg);
  color: var(--text-primary);
}

