/* ===== MISC: SHARE MODAL OVERRIDES, SCROLLBAR, MOBILE RESPONSIVE ===== */
.share-modal {
  width: 460px;
}

/* Scoped to .share-modal specifically rather than editing the shared ".modal h2" rule
   (addEditModal.css) — that rule's own "margin-bottom: 0" is relied on by every other modal. */
.share-modal h2 {
  margin-bottom: 6px;
}

.share-people-input {
  width: 100%;
  padding: 12px 14px;
  border: 1.5px solid var(--border);
  border-radius: 8px;
  font-size: 14px;
  outline: none;
  color: var(--text-primary);
  background: var(--surface);
  transition: border-color 0.15s;
  margin-bottom: 12px;
}

.share-people-input:focus {
  border-color: var(--primary);
}

.share-lists-section {
  margin-bottom: 20px;
}

.share-lists-title {
  font-size: 13px;
  font-weight: 600;
  color: var(--text-secondary);
  margin-bottom: 8px;
}

/* Scrollable rather than just "however many fit" — state.savedLists grows unbounded as the user
   adds their own custom lists via Saved Lists' "+ New folder" prompt, so this caps the modal's
   own height instead of pushing "Anyone with the link"/the footer buttons further down. */
.share-lists-scroll {
  max-height: 160px;
  overflow-y: auto;
  border: 1.5px solid var(--border);
  border-radius: 8px;
}

/* Radio-style, single-selection, tap-to-deselect — same pattern as the detail modal's "Save to:"
   menu (detailModalHeader.js's _toggleSaveToMenu). Picking a list here shares that list's own
   saved items instead of whatever's currently open in the sidebar; deselecting falls back to
   sharing the current view, same as before this list existed. */
.share-list-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 10px 12px;
  background: none;
  border: none;
  border-bottom: 1px solid var(--border);
  font-size: 14px;
  color: var(--text-primary);
  cursor: pointer;
  text-align: left;
  transition: background 0.15s;
}

.share-list-item:last-child {
  border-bottom: none;
}

.share-list-item:hover {
  background: var(--hover-bg);
}

.share-list-item--selected {
  background: var(--primary-bg);
  color: var(--primary);
  font-weight: 600;
}

.share-list-item-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Styled to look like a native radio button, but isn't one — a real <input type="radio"> can't
   be unchecked by clicking the already-selected one again, which selecting-to-deselect here
   needs. Same look as the detail modal's own .detail-save-to-menu-radio. */
.share-list-radio {
  flex-shrink: 0;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  border: 1.5px solid var(--border);
  position: relative;
  transition: border-color 0.15s;
}

.share-list-radio--selected {
  border-color: var(--primary);
}

.share-list-radio--selected::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--primary);
  transform: translate(-50%, -50%);
}

.share-lists-empty {
  padding: 12px;
  font-size: 13px;
  color: var(--text-secondary);
  text-align: center;
}

.share-general-access {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 0;
  margin-bottom: 16px;
}

.share-access-icon {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: #E8F5E9;
  color: #2E7D32;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}

[data-theme="dark"] .share-access-icon {
  background: #1B5E20;
  color: #A5D6A7;
}

/* Toggled off via #share-access-toggle-input — pink/red instead of the green "public" look,
   matching the icon swap to a lock (see updateLinkSharingUi() in share.js). */
.share-access-icon--off {
  background: #FCE4EC !important;
  color: #C62828 !important;
}

[data-theme="dark"] .share-access-icon--off {
  background: #4A1420 !important;
  color: #EF9A9A !important;
}

.share-access-info {
  flex: 1;
  min-width: 0;
}

.share-access-title {
  font-size: 14px;
  font-weight: 500;
  color: var(--text-primary);
}

.share-access-sub {
  font-size: 12px;
  color: var(--text-secondary);
  margin-top: 2px;
}

/* iOS-style on/off toggle for link sharing — replaces the old static "Viewer" role label. When
   off, updateLinkSharingUi() (share.js) also grays out Copy link/Send regardless of whether a
   Saved List/email is filled in, since there's nothing left to share the link *via*. */
.share-access-toggle {
  position: relative;
  display: inline-block;
  width: 40px;
  height: 24px;
  flex-shrink: 0;
  cursor: pointer;
}

.share-access-toggle input {
  opacity: 0;
  width: 0;
  height: 0;
}

.share-access-toggle-slider {
  position: absolute;
  inset: 0;
  background: var(--border);
  border-radius: 24px;
  transition: background 0.15s;
}

.share-access-toggle-slider::before {
  content: '';
  position: absolute;
  width: 18px;
  height: 18px;
  left: 3px;
  top: 3px;
  background: #fff;
  border-radius: 50%;
  transition: transform 0.15s;
  box-shadow: 0 1px 2px rgba(0,0,0,0.3);
}

.share-access-toggle input:checked + .share-access-toggle-slider {
  background: var(--primary);
}

.share-access-toggle input:checked + .share-access-toggle-slider::before {
  transform: translateX(16px);
}

.share-modal-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  border-top: 1px solid var(--border);
  padding-top: 16px;
}

.share-footer-btns {
  display: flex;
  gap: 8px;
  align-items: center;
}

.btn-ask-share {
  display: flex;
  align-items: center;
  gap: 6px;
  background: none;
  border: none;
  color: var(--primary);
  font-size: 14px;
  font-weight: 500;
  cursor: pointer;
  padding: 4px 0;
  transition: opacity 0.15s;
}

.btn-ask-share:hover {
  opacity: 0.75;
}

/* Grayed out until a Saved List is selected above (Copy link only — Send keeps its existing
   email-based gating, unaffected by list selection; buildShareUrl() still falls back to
   whatever's currently open in the sidebar when nothing's selected). */
.btn-ask-share:disabled {
  color: var(--text-secondary);
  cursor: not-allowed;
  opacity: 0.6;
}

.btn-ask-share:disabled:hover {
  opacity: 0.6;
}

.btn-ask-share.copied {
  color: #10B981;
}


/* ===== SCROLLBAR ===== */
/* height (thickness of a HORIZONTAL scrollbar) was missing here — width only styles vertical
   scrollbars, so any horizontal-overflow element (e.g. .kanban-board when its columns overflow
   the window width) fell back to the browser's native thick gray scrollbar instead of this same
   thin themed one, showing up as a stray gray bar along its bottom edge. */
::-webkit-scrollbar { width: 6px; height: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--scrollbar); border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: var(--scrollbar-hover); }


/* ===== MOBILE-ONLY ELEMENTS (hidden on desktop) ===== */
/* .sidebar-overlay/.sidebar-mobile-header/.sidebar-mode-tabs/.sidebar-items-scroll moved to
   sidebar.css — this file's own sidebar drawer section moved there wholesale, see that file's
   "MOBILE SIDEBAR DRAWER" section. */
.btn-hamburger,
.fab-add {
  display: none;
}

/* ===== MOBILE ===== */
@media (max-width: 768px) {
  /* Hide desktop-only elements */
  .header-sidebar { display: none; }
  .btn-add-item { display: none; }
  .share-btn-wrap { display: none; }
  /* "My Saves" duplicates the sidebar's own Home entry — redundant in the options dropdown on
     mobile, per direct request. */
  .my-options-item[data-option="home"] { display: none; }

  /* Kanban board columns — much wider than the desktop-oriented 220px floor (kanban.css), per
     direct request ("more of the text to show... sections can be wider"). Percentage rather than
     a fixed px value so it scales with the actual device instead of guessing one iPhone's width:
     .kanban-column's containing block for this is .kanban-board itself, whose own rendered width
     already equals the visible board area (it's the horizontally-scrolling container, not
     scrolled content), so 85% leaves a deliberate sliver of the next column peeking in as a
     swipe-to-see-more affordance rather than each column exactly filling the screen. Applies to
     both boards — .kanban-column is the shared class kanban.js/adminKanban.js both render. */
  .kanban-column {
    min-width: 85%;
  }

  /* Dashboard's Queue Kanban mini-widget — padding trimmed throughout (kept the 2-column
     Queue/In Progress layout itself, per direct request) to reclaim width for the title text,
     which was truncating heavily on a phone (reported live, screenshot confirmed). Nothing here
     changes sizing/structure, just the gaps and padding eating into the text column's own width.
     REAL BUG, found and fixed: this used to also override .dash-card--kanban's own outer padding
     down to 14px 8px — reclaiming width for the mini-board, but at the cost of shifting the
     "Queue Kanban" title/Open button left of where every other widget's title/button sits (they
     all still use the shared .dash-card 20px padding), per direct request. Fixed by leaving the
     card's own padding alone and instead pulling just .dash-kanban-mini-board itself wider with a
     negative margin — the header row above it is unaffected, so it stays aligned with siblings. */
  .dash-kanban-mini-board { gap: 4px; margin: 0 -8px; }
  .dash-kmini-col { padding: 0; }
  .dash-kmini-col:first-child { padding-right: 4px; }
  .dash-kmini-col:last-child { padding-left: 3px; }
  .dash-kmini-card { gap: 6px; padding-right: 4px; }
  .dash-kmini-info { padding: 5px 4px 5px 0; }
  /* Was nudged down 5px earlier; now moved up 15px net from that (5 - 15 = -10px), per direct
     request — negative margin-top on a grid item shrinks its effective row-height contribution
     on this auto-sized mobile single-column grid (.dash-widget-grid), which is what pulls every
     widget below it up the page too, not just this one. */
  .dash-card--kanban {
    margin-top: -10px;
  }
  /* 3px less than the base .dash-card 20px top/bottom padding, per direct request — applied to
     every widget card (was scoped to just Queue Kanban at first) so they all match. Left/right
     untouched, so each header's title/button stays aligned with the others. */
  .dash-card {
    padding-top: 17px;
    padding-bottom: 17px;
  }

  /* Kanban board header, two rows on mobile — per direct request, this replaces an earlier
     three-row layout (Dashboard alone / info icon alone / the two dropdowns) that wasted vertical
     space the board itself could use instead:
       Row 1: "‹ Dashboard" (left corner) ―――――――――――――――――――――――― info icon (right corner)
       Row 2: My Saves + Categories dropdowns, side by side
     REAL BUG, found and fixed: putting flex-basis: 100% directly on .saves-list-wrap (the first
     item meant for row 2) does force a new line, but it also makes THAT ITEM claim the entire row
     width for itself, leaving .board-filter-wrap nowhere to go but a third row of its own — the
     two dropdowns kept stacking instead of sharing row 2 (reported live on an iPhone 16 Pro,
     plenty of width to spare, so it wasn't a narrow-screen overflow issue). Fixed with the
     standard flexbox line-break trick instead: a zero-size ::after pseudo-element with its own
     flex-basis: 100% inserted between the two row groups (via order) — it consumes row 1's
     remaining width and forces everything after it to wrap, without itself being one of the
     visible controls that needs to share space with a sibling. */
  .grid-header {
    flex-wrap: wrap;
  }
  /* Pulled up 10px, per direct request — tightens the gap above this row. Applied equally to
     both so row 1 moves up as a whole rather than the two corners drifting out of line. */
  .kanban-dashboard-link {
    order: 0;
    margin-top: -10px;
  }
  .board-info-wrap {
    order: 1;
    margin-left: auto;
    /* margin-left: auto already pushes it flush against the row's right edge — this nudges it
       5px further right past that, per direct request. */
    margin-right: -5px;
    margin-top: -10px;
  }
  .grid-header::after {
    content: '';
    order: 2;
    flex-basis: 100%;
    width: 0;
    height: 0;
  }
  /* Pulled up 15px, per direct request — tightens the gap under row 1 (Dashboard/info) now that
     the two rows are reliably grouped correctly. Applied equally to both so the row moves up as
     a whole rather than the two dropdowns drifting out of line with each other. */
  .saves-list-wrap { order: 3; margin-top: -15px; }
  .board-filter-wrap { order: 4; margin-top: -15px; }
  /* Narrower than desktop's 148px so the pair above reliably fits on one row even on the
     smallest iPhone widths (e.g. iPhone SE's 320px), instead of the fixed desktop width being
     just wide enough to force a wrap on some devices but not others.
     REAL BUG, found and fixed: this originally targeted plain .btn-board-filter with no
     exclusion — .dash-fav-category-btn (the dashboard's "Sort" widget button) also carries that
     class, and since this file loads after dashboard.css, this same-specificity rule silently
     won the cascade tie there too, widening Sort back out to 130px and breaking dashboard.css's
     own deliberately-ordered mobile override matching it to Open's 70px (see that file's own
     comment on the exact same class of bug). Excluded here so that button is untouched again. */
  .btn-board-filter:not(.dash-fav-category-btn),
  .btn-saves-list {
    width: 130px;
  }

  /* base.css's .search-expand-wrap.open .search-expand-input is a fixed 210px, sized for a
     desktop header with room to spare — on a phone, that plus the hamburger/wordmark/settings
     icons already sharing the header row doesn't fit, pushing the whole header (and, since
     nothing was clipping it reliably, the whole page — see body's own touch-action fix) wider
     than the screen. Narrower fixed width leaves comfortable room next to the rest of the header. */
  .search-expand-wrap.open .search-expand-input {
    width: 190px;
  }
  /* REAL BUG, found and fixed: base.css's font-size: 14px on this input is below iOS Safari's
     16px auto-zoom threshold — focusing any input smaller than that makes Safari zoom the whole
     page in and scroll to center the focused field, entirely independent of this file's own
     width fix above. That's what was actually behind "the search bar makes the header bigger and
     removes the hamburger, can't be closed" (reported live, screenshot confirmed): the page
     wasn't really resizing or losing the hamburger, it was zooming in and scrolling it out of the
     now-cropped view. 16px here is the fix — at or above the threshold, Safari doesn't invoke
     the zoom at all. */
  .search-expand-input {
    font-size: 16px;
  }
  /* base.css's .global-search-results is position:absolute against .search-expand-wrap, which
     only spans the narrow 190px input above on mobile — nowhere near enough room for a
     thumbnail+title+meta result row (globalSearch.js). Anchored to the viewport instead, spanning
     close to the full header width. .header's own height is a hardcoded, non-responsive 64px
     (base.css) at every breakpoint, so this fixed top offset is safe without a JS-measured
     reposition (unlike e.g. azIndexRail.js's rail, whose anchor isn't a fixed size). */
  .global-search-results {
    position: fixed;
    top: 64px;
    left: 12px;
    right: 12px;
    min-width: 0;
    max-width: none;
  }

  /* Dashboard */
  .dash-widget-grid { grid-template-columns: 1fr; }
  /* height:auto here used to collapse the hero almost to nothing: .dash-hero-content
     (dashboard.css) is height:100% of .dash-hero, and a percentage height resolves to 'auto'
     when its containing block's own height is indeterminate — so with .dash-hero auto too, the
     greeting text ended up the ONLY thing sizing the hero (~90px), the collage thumbnails
     (absolutely positioned, 150px square, uncounted in that sizing) got clipped to almost
     nothing, and the whole banner read as "missing" (reported live on an iPhone). Real fixed
     height instead — shorter than desktop's 220px, and shrink the thumbnails to fit a narrower
     screen without needing 2+ giant 150px squares. Text anchored to the bottom over a solid-
     fading-to-transparent backdrop (going the opposite direction from the old attempt, which
     faded transparent AT the text) so the collage stays visible in the band above it. */
  .dash-hero {
    height: 172px;
    padding: 0;
    /* Bleeds past .grid-area's own 16px mobile side padding (this file, .grid-area rule below) so
       the slider touches the true screen edges instead of sitting inset with dead space on either
       side — negative margin matching that padding exactly, same edge-to-edge technique used
       elsewhere in this file's mobile overrides. .dash-hero-content's own 20px padding (below)
       keeps the greeting text sensibly inset regardless. */
    margin: 0 -16px;
  }
  .dash-hero-content {
    justify-content: flex-end;
    padding: 16px 20px;
    /* Gradient backdrop removed per direct request — greeting text now sits directly over the
       photo collage with no fade-to-solid behind it on mobile. Explicit 'none' (not just leaving
       this declaration out) — .dash-hero-content's own base rule (dashboard.css) sets a left-to-
       right gradient of its own for desktop, which otherwise keeps showing through here since a
       property this rule doesn't mention doesn't cancel the base rule's value for it. */
    background: none;
  }
  .dash-hero-greeting {
    font-size: 20px;
  }
  .dash-hero-thumb {
    width: 90px;
    height: 90px;
    border-radius: 10px;
  }
  /* Shifts just the sliding photo thumbnails up, leaving .dash-hero-content (greeting text +
     its own gradient backdrop, a separate absolutely-positioned sibling) exactly where it
     already was — per direct request. .dash-hero's own overflow: hidden still clips the
     collage's now-exposed top edge, so nothing pokes out above the hero band. */
  .dash-hero-collage {
    transform: translateY(-30px);
  }

  /* --fab-clearance: 56px-tall + 24px-off-bottom "+ Add Item" FAB clearance, applied as
     padding-bottom on the standard-practice .grid-area rule below (and dashboard.css's own
     .grid-area:has(.dashboard-wrap)) — named here so nothing re-derives this by hand. 96px -> 130px
     (a little more breathing room above the FAB itself, not just enough to clear it), per direct
     follow-up ("add a little more negative space above the 'add item' in mobile").
     --page-mobile-pad: the standard mobile side padding every page gets from .grid-area below —
     named so a page wrap that re-applies the same 16px on itself (e.g. sharedSaves.css's
     .shared-page-wrap, which needs it for desktop too) can size a bleed past *both* stacked
     layers via calc() instead of a hand-derived literal that silently drifts if either changes. */
  :root {
    --fab-clearance: 130px;
    --page-mobile-pad: 16px;
  }

  /* Profile page — FAB clearance below the page-end "Manage account" button (moved here on
     mobile, see #profile-manage-account/.profile-manage-account-mobile further down) now comes
     for free from the base .grid-area rule above (standard practice on every mobile page); this
     page needs no override of its own. */

  /* Shared Saves page — pulls the hero banner up by pinning this rule's own padding-top to 6px
     directly (16px base − the 10px reduction requested) instead of leaving the base 16px in place
     and applying a -10px margin-top on .shared-page-wrap to cancel part of it back out.
     padding-bottom's own FAB clearance (was duplicated as its own literal 96px in sharedSaves.css,
     then as its own var(--fab-clearance) restatement here) now comes for free from the base
     .grid-area rule above instead — same standard-practice consolidation as the Profile page. */
  .grid-area:has(.shared-page-wrap) {
    padding-top: 6px;
  }

  /* Admin Kanban page — mobile only, scoped via the .admin-kanban-wrap hook adminKanban.js sets
     on #cards-grid (plain .kanban-wrap is shared with the real Queue board, which keeps its
     current mobile look untouched). */
  /* "‹ Dashboard" hidden here specifically — there's already a real way back (the sidebar/
     hamburger), and it was crowding this page's now-single-row header, per direct request. */
  .grid-area:has(.admin-kanban-wrap) #btn-kanban-dashboard {
    display: none;
  }
  /* The global "+ Add Item" FAB (#fab-add) is a direct <body> child, not nested under .grid-area
     at all, so this needs body:has(...) rather than the .grid-area:has(...) idiom used everywhere
     else on this page — Admin Kanban's own "+ Add card" per column already covers card creation
     here, and the FAB opens the unrelated save-a-media-item modal, per direct request. */
  body:has(.admin-kanban-wrap) .fab-add {
    display: none;
  }
  /* REAL BUG, found and fixed: an earlier version of this file put #grid-title and .grid-header/
     #sort-select on one line by switching .grid-area itself to flex-direction: row — but
     .grid-area:has(.kanban-wrap)'s own overflow: hidden (kanban.css, shared by both boards)
     depends on staying a single flex-column, with #cards-grid's own flex:1 filling "whatever's
     left" and each column scrolling internally. Breaking that into a row+wrap layout broke the
     whole page's scrolling on mobile (reported live). Both #grid-title and #sort-select are now
     hidden entirely for this view instead (adminKanban.js renders its own title + sort dropdown
     as this page's own content, safely inside the untouched #cards-grid/flex:1 chain) — nothing
     left here to reposition or restyle. */
  .admin-kanban-title {
    font-size: 16px;
  }
  .admin-kanban-header {
    margin-bottom: 12px;
  }
  /* grid-template-rows explicitly overridden to none, not just left alone — desktop's own
     repeat(2, 1fr) (profile.css) still pairs row 1 (Connections) with row 2 (Interests) into
     equal-height tracks even here, single column and height:auto or not, so Interests kept
     getting stretched to match whatever height Connections happened to be (most visibly after
     Connections grew a second line per row for its stacked mobile layout below — reported live as
     a huge empty gap under Interests' last checkboxes). none drops the explicit 2-row definition
     entirely, so all 4 cards fall back to implicit auto-height rows sized to their own content. */
  .profile-widget-grid { grid-template-columns: 1fr; grid-template-rows: none; height: auto; }
  /* REAL BUG, found and fixed, reported live: "settings looks messed up in mobile" (screenshot
     confirmed — the Settings card collapsed to just its title, its real content rendering
     outside/below it, overlapping the FAB and the page-end Manage account button). .profile-
     bottom-row > * (profile.css) sets flex: 1 1 0 for the DESKTOP row layout — flex-basis: 0 on
     the flex MAIN axis, which is width in a row. Switching this row to flex-direction: column
     here makes the main axis vertical instead, so that same flex-basis: 0 started governing
     HEIGHT — both cards trying to split the row's own (intrinsic, content-driven) height evenly
     from a zero basis, a genuinely circular calculation that collapsed real content out of its
     box instead of just sizing it normally. flex: 1 1 auto here overrides the basis back to the
     content's own natural size for this (column) direction, only on mobile — the desktop 1 1 0 is
     untouched, still needed there for the actual side-by-side width split. */
  .profile-bottom-row { flex-direction: column; }
  .profile-bottom-row > * { flex: 1 1 auto; }
  /* Per direct request ("in mobile remove the download button") — Account Data's own size/
     description text stays, just the export action hides here. */
  #profile-download-data-btn {
    display: none;
  }
  /* Per direct request ("in mobile put the 'clear cache' button below the text") — Cache's own
     row (.profile-settings-row) stacks to a column here instead of sitting side by side with its
     label/description; Account Data's own row is unaffected in practice (its Download button is
     hidden on mobile, just above), and Last Synced has no button at all. */
  .profile-settings-row {
    flex-direction: column;
    align-items: flex-start;
  }
  .profile-settings-row .profile-settings-action-btn {
    margin-top: 8px;
  }
  /* Email's "Update" button collapses to an icon-only purple square, per direct request ("in
     mobile make the update button just be a pencil icon in a purple square. this way the email
     field can be longer.") — .profile-masked-field-row input already has flex: 1 (profile.css),
     so shrinking this button's own footprint hands the freed-up width straight to the email
     input, letting more of a long address stay visible before it needs to scroll/truncate. */
  #profile-change-email-btn {
    width: 38px;
    height: 38px;
    padding: 0;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .profile-update-btn-icon {
    display: block;
  }
  .profile-update-btn-label {
    display: none;
  }
  /* Text across every Profile card bumped up again here, layered on top of profile.css's own
     base-size bump — mobile was the specific complaint ("very small — especially in mobile"),
     and there's width to spare once the grid above drops to a single column. Saved Lists' folder
     tree also drops from 2 columns to 1 (profile.css's column-count: 2) — two columns of this
     much bigger text got visibly cramped at phone widths. */
  /* "Manage account" moved to its own button at the very end of the page on mobile (was squeezed
     beside the avatar/name in the account row, part of the same row via .profile-account-row's
     own justify-content: space-between), per request — the in-row button (profile.css's own
     .profile-manage-account-mobile base rule already hides the page-end one by default) swaps
     places here: hidden in the row, shown at the page end instead. */
  #profile-manage-account {
    display: none;
  }
  .profile-manage-account-mobile {
    display: block;
    width: 100%;
    margin-top: 8px;
  }
  /* .profile-account-actions .profile-legal-links / .profile-legal-links-mobile's own in-row/
     page-end swap removed — there's only one Privacy Policy · Terms of Service row now, on both
     desktop and mobile (profile.css's .profile-legal-links-bottom), so nothing left to swap here. */
  .profile-card-title { font-size: 18px; }
  .profile-card-copy { font-size: 15px; line-height: 1.55; }
  .profile-account-email { font-size: 16px; }
  .profile-connection-name { font-size: 16px; }
  .profile-connection-empty { font-size: 15px; }
  .profile-track-title { font-size: 15px; }
  .profile-track-artist { font-size: 14px; }
  .profile-interest-option { font-size: 15px; }
  .profile-saved-list-row { font-size: 15px; }
  .profile-saved-list-category,
  .profile-saved-list-folder { font-size: 15px; }
  .profile-saved-list-add-folder { font-size: 15px; }
  .profile-saved-list-tree { column-count: 1; }
  /* Interests' checkbox grid (profile.css) uses auto-fill with a 160px column minimum — on a
     mobile-width card (~300px after grid-area/card padding) that only ever fits one column,
     stacking all 8 CURATED_GENRES options into one tall vertical list (reported live as "the
     interest panel is too long"). Forcing 2 fixed columns here halves it to 4 rows. */
  .profile-interests-grid { grid-template-columns: repeat(2, 1fr); }
  /* Connections rows (profile.css) sit side-by-side on mobile too — text left, a fixed 150px-wide
     button right — same as desktop. On a phone that only leaves ~150px for .profile-connection-
     info, squeezing "We only access your public Steam profile." down to one or two words per line
     (reported live, screenshot confirmed) no matter how the button itself was sized; a narrower
     button (tried first) only bought the text a little more room, it didn't fix the actual cause.
     Real fix: stack each row into two lines instead — description first at the card's full width,
     button below it — so the text is never fighting a side-by-side button for space at all. */
  .profile-connection-row {
    flex-direction: column;
    align-items: flex-start;
    gap: 10px;
  }
  .profile-connection-info {
    width: 100%;
  }
  /* Width/height no longer need a fixed override once the button isn't sharing a row with the
     text — auto-sized to its own label reads fine stacked on its own line, and naturally lands
     well under half of the old 150px for the single-line "Disconnect"/"Coming soon" labels. */
  .profile-connection-row .btn-primary,
  .profile-connection-row .btn-cancel {
    width: auto;
    height: auto;
    padding: 10px 20px;
    font-size: 13px;
  }
  /* Connect Last.fm/Steam's <br class="profile-connect-break"> (profile.js) forces those two
     buttons onto 2 lines everywhere by default, sized for desktop's own fixed 150px box — with
     width now auto here, hiding just this break lets each button size to its single-line label
     instead ("longer and shorter", per direct request), scoped to mobile only so desktop's
     existing 2-line look is untouched. Disconnect/Coming soon never had a <br> here to begin with,
     already single-line on both breakpoints. */
  .profile-connect-break {
    display: none;
  }

  /* Hamburger */
  .btn-hamburger {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    border: none;
    background: none;
    color: var(--text-primary);
    cursor: pointer;
    border-radius: 50%;
    flex-shrink: 0;
  }
  .btn-hamburger:hover { background: var(--hover-bg); }

  /* Header adjustments */
  .header-main {
    padding: 0 12px;
    gap: 8px;
    border-bottom: 1px solid var(--border);
  }
  .logo {
    position: static;
    transform: none;
    flex: 1;
    /* min-width: 0 lets this actually shrink under pressure — a flex item's default min-width is
       auto (its own content's width, "SaveCraft" at 20px here), not 0, so without this the logo
       refused to shrink at all once the search input opened (140px wide, see
       .search-expand-wrap.open .search-expand-input below) and pushed the row's total content
       wider than the screen — reported live as the header appearing to grow and the hamburger
       disappearing, almost certainly iOS Safari's own "scroll the focused input into view"
       behavior shifting the whole header sideways to reveal it, since nothing here was actually
       narrow enough to avoid that overflow in the first place. overflow/ellipsis so it truncates
       gracefully instead of visibly overlapping .header-actions if it's ever squeezed this tight. */
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    /* Left, not center — per direct request, so it sits close to the hamburger on the left
       instead of centered across the whole header row. */
    text-align: left;
    /* .header-main's own row gap (8px) already puts some space between the hamburger and this —
       this extra margin brings the two closer than that alone, per direct request ("more to the
       left"; was 12px, for 20px total). */
    margin-left: 4px;
  }
  /* Hides the logo outright while searching, per direct request (the ellipsis-shrink above was
     the first attempt, but it still left a truncated sliver of "SaveCraft" — see screenshot —
     rather than the empty room the search input actually needs). :has() lets this key off the
     search wrap's own .open state without needing new JS to toggle a class elsewhere. */
  .header-main:has(.search-expand-wrap.open) .logo {
    display: none;
  }
  /* Same idea for the settings gear, per direct request — frees up further room for the search
     input alongside the logo already hiding above. */
  .header-main:has(.search-expand-wrap.open) .settings-wrap {
    display: none;
  }

  /* Sidebar mobile drawer CSS moved to sidebar.css's own "MOBILE SIDEBAR DRAWER" section. */

  /* Main layout: full width */
  /* REAL BUG, found and fixed: 100vh on iOS Safari doesn't track the browser's own address-bar/
     toolbar chrome showing or hiding — it's a fixed value that leaves a gap the size of whatever
     chrome is currently visible, which is exactly what showed up as two separate gaps on the
     Kanban board (one inside the last column, one below the whole board — both are downstream of
     .main-layout itself coming up short, since .kanban-wrap/.kanban-board/.kanban-column/
     .kanban-cards all correctly flex:1 the rest of the way once given accurate space to fill).
     100dvh (dynamic viewport height) tracks the *actual* visible area as that chrome animates in
     and out, per direct request ("I want the sections to go to the end of the screen"). Falls
     back to plain vh in the (now rare) browsers that don't support dvh, via the two-declaration
     fallback pattern below rather than @supports, since an unsupported value is simply ignored. */
  .main-layout { height: calc(100vh - 64px); height: calc(100dvh - 64px); }
  /* Standard mobile practice, per direct request ("I want this to be a standard practice for the
     mobile design"): every normally-scrolling page gets --fab-clearance as its bottom padding by
     default, not just the handful of pages that had separately asked for it below — so the last
     item on ANY page's list/grid ends up above the floating "+ Add Item" FAB once scrolled all the
     way down, not hidden behind it. A page that instead uses the .grid-area:has(<page>) fixed-fit/
     overflow:hidden idiom (Kanban, Admin Kanban, Category landing on desktop) isn't affected by
     this base rule at all — those already fully own their own padding via a higher-specificity
     :has() selector. The two mobile :has() rules just below that used to each restate this same
     --fab-clearance by hand (profile page, Shared Saves) now just inherit it from here instead. */
  .grid-area {
    padding: var(--page-mobile-pad);
    padding-bottom: var(--fab-clearance);
    /* 10px less top padding, per direct follow-up ("bring all the page content up 10px") — tied
       to --page-mobile-pad via calc() rather than a hand-derived literal, same reasoning as
       --fab-clearance above. Pages with their own already-customized top padding (Kanban/Admin
       Kanban's 15px — kanban.css, applies on every screen size, not mobile-specific; Shared
       Saves' already-pulled-up 6px — misc.css below) are untouched, same scoping as the
       FAB-clearance standard practice above: this covers the default case every other page uses. */
    padding-top: calc(var(--page-mobile-pad) - 10px);
  }

  /* Single column cards */
  .cards-grid { grid-template-columns: 1fr; }

  /* REAL BUG, found and fixed: #grid-title (cards.css) is a flex row with no flex-wrap — on a
     Top 100 drilldown that also carries a publication-credit logo (Rolling Stone/Steam/NYT,
     renderGrid.js, margin-left: auto pushed to the row's right edge), the title text + logo
     combined can be wider than a phone's viewport, forcing the row itself — and, since nothing
     clips it, the whole page — wider than the screen (reported live on an iPhone 16 Pro: the
     page could be dragged left/right). flex-wrap here lets the logo drop to its own line instead
     of forcing the row wider than the viewport; margin-left: auto still right-aligns it on
     whichever line it lands on. */
  .grid-title {
    flex-wrap: wrap;
  }

  /* Music landing page's genre grid, mobile-optimized as its own pass:
     - 2 columns, not 5 (or even 3) — the cards need real room for longer bucket names
       ("Alt/Indie", "R&B/Soul") on a narrow screen; 3+ columns left too little width per
       card and the desktop "half width, centered" treatment (cards.css) would have made them
       tiny and cramped here.
     - row-gap trimmed from desktop's 50px — 15 cards at 2-per-row is already 8 rows; 50px between
       each would add a lot of scroll for little benefit on a phone.
     - .musicgenre-card gets its own mobile width (most of its column, not the desktop-style
       half-width-plus-fixed-px formula, which was sized for 5 much-narrower desktop columns and
       would look awkwardly small carried over here) so the icon/name/count actually have room. */
  .musicgenre-landing-grid {
    grid-template-columns: repeat(2, 1fr);
    row-gap: 20px;
  }
  .musicgenre-card {
    width: 85%;
  }
  /* #musicgenre-select + #sort-select, when both are actually visible together (the Musicians/
     musicgenre: pages — .grid-header-right--dual, toggled in renderGrid.js right alongside
     #musicgenre-select's own display) — per direct request/follow-up, these now share one row on
     mobile instead of stacking (an earlier pass stacked them: two fixed 160px-wide dropdowns plus
     their gap didn't reliably fit side by side even on a mid-size phone). flex:1 1 0/min-width:0
     lets each shrink to split the row evenly instead of keeping their fixed desktop width.
     Scoped to the wrapper's real two-child state (not every .grid-header-right) so #sort-select's
     own presentation everywhere else — including every non-Music page, where it's still the only
     child — is completely unaffected. */
  .grid-header-right--dual {
    gap: 8px;
  }
  /* Both fields equal width (flex:1 1 0 on both, no min-width floor favoring either one) and their
     own content centered, per direct follow-up — #sort-select is now a wrapper div (sortSelect.js's
     custom dropdown), not the visual pill itself, so the shrink lives on the wrapper, the
     tightened box styling + centering on its trigger button. */
  .grid-header-right--dual #sort-select {
    flex: 1 1 0;
    min-width: 0;
  }
  .grid-header-right--dual #sort-select .sort-select-trigger {
    width: 100%;
    justify-content: center;
    padding: 7px 10px;
    font-size: 12px;
    font-weight: 500;
  }
  .grid-header-right--dual #musicgenre-select {
    width: auto;
    flex: 1 1 0;
    min-width: 0;
    padding: 7px 26px 7px 10px;
    background-position: right 9px center;
    text-align: center;
    /* #musicgenre-select's own 15px/bold (cards.css) is sized for its normal fixed 160px width —
       at roughly half that here, the longer bucket names ("Rock/Metal", "R&B/Soul") need the
       smaller, lighter treatment #sort-select's own trigger already gets above, to keep from
       crowding the arrow. */
    font-size: 12px;
    font-weight: 500;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  /* Empty states ("Nothing here yet", no results, curated prompts) — the landing-state box's own
     64px-per-side horizontal padding (cards.css) is sized for desktop's much wider canvas; on a
     phone that leaves very little width for the description, risking a 3rd line. Padding trimmed
     down for more room, and the description hard-capped to 2 lines as a guarantee regardless of
     copy length or exact screen width, per direct request. */
  .cards-grid.landing-state .empty-state {
    padding: 40px 24px;
  }
  .empty-state p {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    overflow: hidden;
  }

  /* Grid header */
  .grid-header { margin-bottom: 14px; }
  /* Right-edge clearance for the A-Z jump index rail (azIndexRail.js, cards.css) — it's a fixed
     overlay pinned just inside .grid-area's own right padding, so anything already flush against
     that edge (this wrapper's margin-left: auto pushes #sort-select/#musicgenre-select there) sat
     right underneath the rail's letters. Per direct request ("move the dropdown buttons to the
     left some so the letters also don't overlap the sort button"). */
  .grid-header-right {
    margin-right: 22px;
  }
  /* Cause Curated specifically — per direct request, the gap between the Dashboard link (in
     .grid-header) and the banner below it (.bare-list-hero) should be exactly 20px, not this
     page's own general 14px mobile tightening above (which applies to every other view's
     .grid-header too, so scoped here rather than changed globally). */
  .grid-area:has(.bare-list-page) .grid-header {
    margin-bottom: 20px;
  }

  /* FAB */
  .fab-add {
    display: flex;
    align-items: center;
    justify-content: center;
    position: fixed;
    bottom: 24px;
    /* Horizontally centered, per request (was pinned to the left edge) — left: 50% +
       translateX(-50%) centers against the real viewport width regardless of the button's own
       56px, rather than a fixed left px value that'd only be centered at one specific screen
       width. Vertical position (bottom: 24px) is untouched — only asked to center horizontally. */
    left: 50%;
    transform: translateX(-50%);
    width: 56px;
    height: 56px;
    border-radius: 50%;
    background: var(--primary);
    color: white;
    font-size: 28px;
    font-weight: 300;
    border: none;
    cursor: pointer;
    box-shadow: 0 4px 16px rgba(0,0,0,0.3);
    z-index: 150;
    transition: background 0.15s, transform 0.1s;
  }
  .fab-add:hover,
  .fab-add:active {
    background: #fff;
    color: var(--primary);
    /* translateX(-50%) carried over from the base rule's own centering — a bare scale(1.05) here
       would replace (not add to) that transform, snapping the button back to the left on hover. */
    transform: translateX(-50%) scale(1.05);
  }
}


/* .cat-icon's inset box-shadow "edge" bevel (four 1px inset lines forming a border-like ring)
   AND its top-half ::after gradient (a hard cutoff at the 50%-height mark, see sidebar.css) each
   independently render as visible defects on real iOS devices — a white outline from the
   box-shadow, a hard seam line from the gradient's cutoff — even though both read as subtle, fine
   bevel/gloss everywhere else. Confirmed live on an iPhone 16 Pro (both Safari and Chrome — same
   WebKit engine) that the gradient alone still reproduces the seam, independent of the box-shadow.
   The seam traces back to ::after's own `height: 50%` — the pseudo-element's box itself simply
   stops existing there, a hard edge WebKit visibly renders, distinct from the gradient's color
   fade inside it. Stretched to the icon's full height here (still fading to fully transparent, so
   it still reads as a top-half highlight) — no edge left anywhere for WebKit to render.
   Scoped via @supports (-webkit-touch-callout: none) rather than a max-width media query — that
   property is iOS-WebKit-only (true on an iPhone/iPad regardless of window size, false on every
   desktop browser regardless of how narrow the window gets, Chrome included) — a narrow desktop
   window was incorrectly losing this effect too under the old max-width scoping, even though
   desktop browsers never had the underlying rendering bug in the first place. */
@supports (-webkit-touch-callout: none) {
  .cat-icon {
    box-shadow: none;
  }
  .cat-icon::after {
    height: 100%;
    background: linear-gradient(to bottom, rgba(255,255,255,0.15), rgba(255,255,255,0) 65%);
  }
}
