/* ===================================================================
   music.css — music end-to-end
   ===================================================================
   This file owns every music-touching surface across compose, settings,
   profile, dashboard column body, post body, and freespace.

   Phases covered:
     - au0-d           Music settings provider cards
     - au1a            Theme song foundation + visual refresh
     - au1c            Library search tab (picker modal)
     - au2             Home audio strip + CTA
     - au3             Music as first-class content, inline picker
                       panel, freespace mode F (music)
     - au-ux-cleanup   Mobile fixes for the above surfaces

   Reference: markdown/design/DESIGN_DECISIONS_CSS_REFACTOR.md
   =================================================================== */

/* ===================================================================
   au0-d — Music settings provider cards
   ===================================================================
   /settings/music renders one .inline-editor.provider-card per
   provider (Spotify, Apple Music). When the card is in its
   connected state (the .summary-pill.is-public is present), hover
   gets a green halo using the same three-layer recipe as .has-halo
   (see top of file for the master pattern).

   Disconnected cards have no hover treatment — neutral by design,
   so the eye is drawn to whatever's already connected rather than
   what isn't.

   :has() is the connected-state selector: it's stable in all
   evergreen browsers as of 2023, no fallback needed for the
   friend-group launch. */

.provider-card {
    /* Both connected and disconnected cards get the transition so
       the hover-in and hover-out animate symmetrically. The 200ms
       ease-out matches the timing curve used by other interactive
       cards in the design system. */
    transition: box-shadow 200ms ease-out;
}

.provider-card:has(.summary-pill.is-public):hover {
    /* Three-layer halo recipe — kept inline rather than composing
       .has-halo because the hover-only + connected-only conjunction
       needs the rule selector to live here. The numeric layers
       (1px / 24px / 60px) and alpha values (0.5 / 0.25 / 0.12)
       match the .has-halo source of truth. */
    box-shadow:
        0 0 0 1px rgba(var(--bs-success-rgb), 0.5),
        0 0 24px 4px rgba(var(--bs-success-rgb), 0.25),
        0 0 60px 8px rgba(var(--bs-success-rgb), 0.12);
}

/* ===================================================================
   au1a — Theme song foundation
   ===================================================================
   Styles for:
     - .profile-theme-song-row     row container below profile header
     - .profile-theme-song-row--empty   owner-only "set a theme song"
                                        empty-state CTA
     - .profile-theme-song-clear   small remove form, owner-only
     - .music-embed-chrome*        lazy-load placeholder + iframe shell
                                   (reused au2 dashboard + au3 posts)
     - .music-picker-*             picker modal segmented control +
                                   paste-tab preview card

   Visual rhythm:
     - Chrome card is 80px tall by default — feels equivalent to a
       compact post card. Iframe load expands the chrome vertically
       to ~152px (Spotify-recommended embed height).
     - Empty-state CTA mirrors .freespace-empty-cta (subtle border,
       muted text, accent on hover) so the two empty states read as
       siblings on profile pages without theme song + freespace.
     - Picker preview card uses the same layout shape as the chrome
       but with 48px cover and tighter padding — clear visual lineage,
       compact enough for modal context.

   All values via existing tokens. Will inherit dark-mode treatment
   when pr6c ships without further changes. */

/* ===== Profile theme song row (outer container) ===== */

.profile-theme-song-row {
    margin: var(--space-3) 0;
    position: relative;
}

/* Empty-state branch — owner-only, opens the picker modal. Mirrors
   .freespace-empty-cta's treatment for consistency with the other
   profile-header empty state. */
.profile-theme-song-row--empty {
    display: flex;
    justify-content: center;
}

.profile-theme-song-row--empty > .btn {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    min-height: 56px;
    padding: var(--space-2) var(--space-4);
    color: var(--color-text-muted);
    border: 1px dashed var(--color-border);
    border-radius: var(--radius-md);
    background-color: transparent;
    text-decoration: none;
    transition: color var(--transition-fast),
                background-color var(--transition-fast),
                border-color var(--transition-fast);
}

.profile-theme-song-row--empty > .btn:hover,
.profile-theme-song-row--empty > .btn:focus-visible {
    color: var(--color-accent);
    border-color: var(--color-accent);
    background-color: color-mix(in srgb, var(--color-accent) 6%, transparent);
    text-decoration: none;
}

/* Remove form sits to the upper-right of the chrome when a theme
   song is set. Absolute-positioned so it overlays the chrome corner
   without consuming layout width. */
.profile-theme-song-clear {
    position: absolute;
    top: var(--space-1);
    right: var(--space-2);
    margin: 0;
}

.profile-theme-song-clear > .btn {
    padding: var(--space-1) var(--space-2);
    line-height: 1;
}

/* ===== Music embed chrome (shared partial) ===== */

.music-embed-chrome {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-3);
    min-height: 80px;
    background-color: var(--color-bg-surface);
    border: 1px solid var(--color-border-subtle);
    border-radius: var(--radius-md);
    transition: border-color var(--transition-fast);
}

.music-embed-chrome:hover {
    border-color: var(--color-border);
}

/* Modifier classes carry no visual style of their own in au1a — the
   base .music-embed-chrome rule covers all three branches. The
   modifiers exist as hooks for later (au1b's upload chrome will
   restyle --upload-stub once it's functional). */

.music-embed-cover {
    flex: 0 0 auto;
    width: 64px;
    height: 64px;
    border-radius: var(--radius-sm);
    object-fit: cover;
}

.music-embed-cover--placeholder {
    display: flex;
    align-items: center;
    justify-content: center;
    background-color: var(--color-bg-subtle);
    color: var(--color-text-muted);
}

.music-embed-meta {
    flex: 1 1 auto;
    min-width: 0;  /* Allow text truncation inside flex item. */
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: var(--space-1);
}

.music-embed-title {
    font-weight: var(--font-weight-medium);
    font-size: var(--font-size-body);
    color: var(--color-text);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.music-embed-artist {
    font-size: var(--font-size-sm);
    color: var(--color-text-muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.music-embed-album {
    font-size: var(--font-size-xs);
    color: var(--color-text-muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.music-embed-play {
    flex: 0 0 auto;
    align-self: center;
}

/* Iframe state — after the user clicks play, the placeholder
   children get display:none and the iframe + remove button get
   appended. The chrome grows vertically; the flex layout doesn't
   apply meaningfully when the only visible child is the iframe. */

.music-embed-iframe {
    width: 100%;
    border: 0;
    border-radius: var(--radius-sm);
    display: block;
}

.music-embed-remove {
    align-self: flex-end;
    margin-top: var(--space-2);
    padding: var(--space-1) var(--space-2);
    line-height: 1;
}

/* When the iframe + remove-button are present alongside the
   (display:none) children, the chrome needs to switch from a row
   layout to a column layout so the iframe gets its full width.
   :has() lets us key off the iframe's presence directly. */
.music-embed-chrome:has(.music-embed-iframe) {
    flex-direction: column;
    align-items: stretch;
}

/* ===== Music picker modal ===== */

.music-picker-segments {
    /* The .btn-group + .btn-check pattern handles most of the
       segmented control rendering out of the box. We just need to
       ensure the disabled segments are visually distinct from the
       active one — Bootstrap's .btn-outline-secondary already
       provides the muted treatment on disabled state. Soft tweak
       on the badge spacing. */
    margin-bottom: var(--space-3);
}

.music-picker-segment {
    /* Equal width segments via flex-grow from the .btn-group
       parent. Inherits sizing from Bootstrap's .btn. */
}

.music-picker-panel {
    /* Each tab's panel is a flex column. Hidden panels use
       Bootstrap's d-none — no extra rule needed here. */
}

/* Paste tab — URL input section already styled via Bootstrap's
   .form-control. Spinner block needs flex alignment for the
   icon + text. */

.music-picker-spinner {
    display: flex;
    align-items: center;
    color: var(--color-text-muted);
    font-size: var(--font-size-sm);
}

/* Preview card — same layout as embed chrome but compact. Lives
   inside the modal body, gets its own visual shape so it doesn't
   accidentally look identical to the chrome it's about to become. */

.music-picker-preview-card {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-2);
    background-color: var(--color-bg-subtle);
    border-radius: var(--radius-sm);
}

.music-picker-preview-cover {
    flex: 0 0 auto;
    width: 48px;
    height: 48px;
    border-radius: var(--radius-sm);
    object-fit: cover;
}

.music-picker-preview-meta {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
}

.music-picker-preview-title {
    font-weight: var(--font-weight-medium);
    font-size: var(--font-size-body);
    color: var(--color-text);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.music-picker-preview-artist {
    font-size: var(--font-size-sm);
    color: var(--color-text-muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.music-picker-preview-album {
    font-size: var(--font-size-xs);
    color: var(--color-text-muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Picker error inherits .alert .alert-warning styling from
   Bootstrap. No bespoke rule needed beyond the spacing the d-none
   toggle already provides. */


/* ===================================================================
   au1a — Visual refresh: theme song row stacking + hover halo
   ===================================================================
   Append-only override of the v1 layout shipped earlier in au1a.

   Changes:
     - .profile-theme-song-row stacks form (above) + chrome (below)
       as a flex column instead of relying on absolute positioning.
       The remove form moves out of the chrome card's top-right
       corner — it overlapped the iframe on Apple Music's player
       load.
     - .profile-theme-song-clear is right-aligned via align-self,
       overriding the earlier absolute-positioning rule. Selector
       specificity (.row > .clear) wins against the bare .clear rule.
     - .music-embed-chrome gets a hover halo when in placeholder
       state (no iframe loaded) — three-layer recipe from au0-d's
       .has-halo source of truth. When the iframe IS loaded, no
       halo — the user's attention belongs to the player.

   The remove button switching from .btn-link.text-muted to
   .btn-danger lives in profile/view.html (one commit, two files).
   */

.profile-theme-song-row {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

.profile-theme-song-row > .profile-theme-song-clear {
    /* Beats the earlier `.profile-theme-song-clear` absolute-position
       rule on specificity (compound selector vs single class). */
    position: static;
    align-self: flex-end;
    top: auto;
    right: auto;
    margin: 0;
}

.music-embed-chrome {
    /* CSS's `transition` is a shorthand — re-declaration replaces,
       it doesn't append. So we restate the earlier border-color
       transition here alongside the new box-shadow transition. */
    transition:
        border-color var(--transition-fast),
        box-shadow 200ms ease-out;
}

.music-embed-chrome:not(:has(.music-embed-iframe)):hover {
    /* Three-layer halo recipe. Layer sizes (1px / 24px / 60px) and
       alpha values (0.5 / 0.25 / 0.12) match au0-d's .provider-card
       hover — the .has-halo source of truth across FriendZone. The
       :not(:has(.music-embed-iframe)) gate ensures no halo once
       the user clicks play and the iframe takes over. */
    box-shadow:
        0 0 0 1px rgba(var(--color-halo-rgb), 0.5),
        0 0 24px 4px rgba(var(--color-halo-rgb), 0.25),
        0 0 60px 8px rgba(var(--color-halo-rgb), 0.12);
}

/* ===================================================================
   au1c — Library search tab (picker modal)
   ===================================================================
   Styles for:
     - .music-picker-library-placeholder   initial / pre-search state
     - .music-picker-library-notices       per-provider inline notices
       .music-library-notice               (each notice row)
     - .music-picker-library-results       result list container
     - .music-library-empty-cta            "connect a service" CTA when
                                           neither provider is connected
     - .music-library-no-matches           "no matches" message
     - .music-library-row                  result row (button)
     - .music-library-cover                cover thumbnail (or placeholder)
     - .music-library-meta                 title/artist/album column
     - .music-library-title / -artist / -album
     - .music-library-flags                badge + library-hit dot column
     - .music-library-badge                provider pill, tinted via
                                           .music-library-badge--spotify
                                           or .music-library-badge--apple_music
     - .music-library-libdot               dot indicating from_library=true

   Visual rhythm:
     - Result row uses Bootstrap .list-group-item-action for the
       hover/focus treatment; we layer cover + meta + flags as a flex
       row inside it. Mirrors the .music-embed-chrome layout shape
       (cover-meta-controls) at smaller sizing so the picker reads
       as visually related to the chrome it produces.
     - Provider badges use color-mix of --bs-success-rgb (spotify) and
       --bs-danger-rgb (apple music) at low alpha for soft tinted
       backgrounds. Token-based so dark mode (pr6c) inherits the shift
       automatically.
     - Library-hit dot uses --bs-primary-rgb (the accent) so it reads
       as "this is in your library" rather than as another provider
       cue. Apple-only in v1 since Spotify's API doesn't expose
       saved-tracks substring search; class is generic so the dot
       generalizes if that ever changes.
     - Results scroller capped via max-height + overflow-y so 20 rows
       don't blow the modal height out. Refine if friends complain.

   All values via existing tokens. Will inherit dark-mode treatment
   when pr6c ships without further changes. */

/* ===== Placeholder / spinner alignment (reuses au1a primitives) ===== */

.music-picker-library-placeholder {
    /* Centered, muted; small enough to not dominate the panel
       before the user starts typing. */
    text-align: center;
    padding: var(--space-3) 0;
}

/* ===== Notices ===== */

.music-library-notice {
    /* Bootstrap .alert is heavy by default; tighten to fit inline
       above the results without dominating. */
    padding: var(--space-2) var(--space-3);
    font-size: var(--font-size-sm);
    margin-bottom: var(--space-2);
}

.music-library-notice:last-child {
    margin-bottom: 0;
}

/* ===== Results container ===== */

.music-picker-library-results.list-group {
    /* Cap modal-relative so 20 rows don't push the picker off-screen.
       60vh works on most viewports; 480px ceiling keeps it readable
       on very tall windows. */
    max-height: min(60vh, 480px);
    overflow-y: auto;
}

/* ===== Empty-state CTA (neither provider connected) ===== */

.music-library-empty-cta {
    /* Lives inside [data-library-results] when neither provider is
       connected. Centered prompt + outline button linking to
       /settings/music. */
    padding: var(--space-4) var(--space-3);
}

.music-library-no-matches {
    /* Inline message inside [data-library-results] when a query
       returned zero matches across all connected providers. */
    padding: var(--space-3);
    font-size: var(--font-size-sm);
}

/* ===== Result row ===== */

.music-library-row {
    /* .list-group-item-action provides the base hover/focus/active
       treatment via Bootstrap; we add the flex row layout for the
       cover-meta-flags content. */
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-2) var(--space-3);
    text-align: left;
}

.music-library-cover {
    flex: 0 0 auto;
    width: 48px;
    height: 48px;
    border-radius: var(--radius-sm);
    object-fit: cover;
}

.music-library-cover--placeholder {
    /* No cover_url — use a muted block, same dimensions. The picker
       UI doesn't need an icon here; the row's title carries the
       meaning. */
    background-color: var(--color-bg-subtle);
}

.music-library-meta {
    flex: 1 1 auto;
    min-width: 0;  /* Allow text truncation inside flex item. */
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.music-library-title {
    font-weight: var(--font-weight-medium);
    font-size: var(--font-size-body);
    color: var(--color-text);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.music-library-artist {
    font-size: var(--font-size-sm);
    color: var(--color-text-muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.music-library-album {
    font-size: var(--font-size-xs);
    color: var(--color-text-muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* ===== Flags (badge + library-hit dot) ===== */

.music-library-flags {
    flex: 0 0 auto;
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: var(--space-1);
}

.music-library-badge {
    /* Compact pill, lowercase voice. Provider modifier classes layer
       a tinted background via color-mix. */
    display: inline-block;
    padding: 2px var(--space-2);
    border-radius: var(--radius-sm);
    font-size: var(--font-size-xs);
    font-weight: var(--font-weight-medium);
    line-height: 1.4;
    color: var(--color-text-muted);
    background-color: var(--color-bg-subtle);
    white-space: nowrap;
}

.music-library-badge--spotify {
    /* Spotify green at low alpha for a recognizable-but-quiet tint. */
    background-color: color-mix(in srgb, rgba(var(--bs-success-rgb), 1) 14%, transparent);
    color: var(--color-text);
}

.music-library-badge--apple_music {
    /* Apple Music red at low alpha. Same alpha as spotify so the two
       read as siblings rather than one dominating. */
    background-color: color-mix(in srgb, rgba(var(--bs-danger-rgb), 1) 14%, transparent);
    color: var(--color-text);
}

.music-library-libdot {
    /* 8px solid dot indicating from_library=true. Accent token so it
       reads as "in your library" rather than as a provider cue. */
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background-color: var(--color-accent);
    display: inline-block;
}

/* Disabled state on result rows during in-flight submit — prevents
   double-clicks. Bootstrap doesn't style :disabled on .list-group-item
   directly; layer a muted treatment here. */
.music-library-row:disabled {
    opacity: 0.6;
    cursor: progress;
}

/* === === === au2 home audio strip + CTA (phase 14d) === === ===

   Renders above .dashboard-grid as a sibling, between the top nav
   and the column grid. Inherits the grid's centering pattern so the
   strip lines up with the columns below.

   Two pieces, both gated by route-layer context:
   - .home-audio-cta — first-time CTA banner for users with a theme
     song but no home_audio_source pick yet.
   - .home-audio-strip — the player itself, when resolve() returned
     a MusicReference.

   Visual model per the au2 spec's "Player rendering" section:
   minimal chrome, iframe carries its own provider branding, FZ adds
   only an attribution line above (sources 2/3) or nothing (source 1).

   State matrix via data attributes set by home_audio_mobile.js:
   - [data-mobile="false"]: desktop. Show attribution + iframe slot.
                           Hide the chip.
   - [data-mobile="true"][data-expanded="false"]: mobile collapsed.
                           Show chip only. Iframe absent from DOM.
   - [data-mobile="true"][data-expanded="true"]: mobile expanded.
                           Show chip + iframe slot. Iframe is in DOM.

   Tokens throughout — light + pr6c dark inherit automatically. */

/* ---- strip outer container ---- */

.home-audio-strip {
    width: 100%;
    max-width: calc(540px * 3 + 1rem * 2);
    margin: 0 auto var(--space-4);
    padding: 0 var(--space-3);
}

/* ---- mobile chip (toggle button) ---- */

.home-audio-strip .home-audio-toggle {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    min-height: 48px;
    padding: var(--space-2) var(--space-3);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-pill);
    background-color: var(--color-bg-surface);
    color: var(--color-text);
    font-size: var(--font-size-base);
    cursor: pointer;
}

.home-audio-strip .home-audio-toggle:hover {
    background-color: var(--color-bg-subtle);
}

.home-audio-strip .home-audio-toggle:focus-visible {
    outline: none;
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--color-accent) 40%, transparent);
}

.home-audio-strip .home-audio-toggle-indicator {
    font-size: var(--font-size-md);
    color: var(--color-text-muted);
}

/* Hide the chip on desktop. data-mobile defaults to "false" on
   server render, so the chip is hidden on first paint until JS
   flips data-mobile to "true". */
.home-audio-strip[data-mobile="false"] .home-audio-toggle {
    display: none;
}

/* ---- desktop attribution line ---- */

.home-audio-strip .home-audio-attribution {
    font-size: var(--font-size-base);
    color: var(--color-text-muted);
    margin-bottom: var(--space-2);
}

.home-audio-strip .home-audio-attribution a {
    color: var(--color-text);
    text-decoration: none;
    font-weight: var(--font-weight-medium);
}

.home-audio-strip .home-audio-attribution a:hover {
    text-decoration: underline;
}

/* Hide attribution on mobile — the chip carries the same info. */
.home-audio-strip[data-mobile="true"] .home-audio-attribution {
    display: none;
}

/* ---- iframe slot ---- */

/* Container takes its height from the iframe inside (per-provider
   heights set on the iframe element by _embed_chrome.html). When
   the slot is empty (mobile collapsed), the container collapses to
   zero height naturally — no display:none needed. Adding margin-top
   so the iframe doesn't butt up against the attribution line. */
.home-audio-strip .home-audio-iframe-slot:not(:empty) {
    margin-top: var(--space-2);
}

/* ---- first-time CTA banner ---- */

.home-audio-cta {
    width: 100%;
    max-width: calc(540px * 3 + 1rem * 2);
    margin: 0 auto var(--space-4);
    padding: var(--space-3) var(--space-4);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    background-color: color-mix(in srgb, var(--color-accent) 6%, var(--color-bg-surface));
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
}

.home-audio-cta-text {
    margin: 0;
    font-size: var(--font-size-base);
    color: var(--color-text);
}

.home-audio-cta-text .text-muted {
    margin-left: var(--space-1);
}

.home-audio-cta-actions {
    display: flex;
    gap: var(--space-2);
    flex-shrink: 0;
}

/* === au3 — music as first-class content (interim styles, pre-Block-5) === */

/*
 * Minimal styles for the post-compose / edit-post music attachment
 * tile. The full au3 styling pass (covering the chip, convert
 * affordance, post-card music wrapper, freespace Mode F, dark-mode
 * adjustments) lands in Block 5; this slice exists so the tile is
 * usable for smoke-testing compose flows before then.
 */
.post-music-attachment-tile {
    display: flex;
    align-items: center;
    gap: var(--space-3, 0.75rem);
    padding: var(--space-2, 0.5rem);
    border: 1px solid var(--bs-border-color-translucent);
    border-radius: var(--bs-border-radius, 0.5rem);
    background: var(--bs-tertiary-bg, transparent);
}

.post-music-attachment-cover {
    width: 48px;
    height: 48px;
    flex: 0 0 auto;
    border-radius: var(--bs-border-radius-sm, 0.25rem);
    object-fit: cover;
}

.post-music-attachment-meta {
    flex: 1 1 auto;
    min-width: 0;  /* allows ellipsis on long titles */
}

.post-music-attachment-title {
    font-weight: 500;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.post-music-attachment-artist {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.post-music-attachment-tile .btn-close {
    flex: 0 0 auto;
    align-self: flex-start;
}

/* === au3 — inline music picker panel === */

/*
 * Inline music picker styling. The same _picker_body.html partial
 * renders in two contexts:
 *   1. Inside a Bootstrap modal       (theme-song flow on profile)
 *   2. Inside .post-music-picker-inline  (post compose / edit /
 *                                          freespace in Block 4)
 *
 * .music-picker-body owns the internal padding + header/content
 * rhythm so both contexts render visually identical content. The
 * .post-music-picker-inline wrapper provides the framing chrome so
 * the panel reads as a discrete sub-section inside the compose form
 * or edit page rather than a free-floating chunk of UI.
 */

/* Inline wrapper — visible framing around the picker body. Subtle
   inset background + soft border so the panel reads as "interactive
   sub-section" without competing with the surrounding card or modal
   chrome. overflow:hidden so the picker-body header's bottom border
   doesn't poke past the wrapper's rounded corners. */
.post-music-picker-inline {
    border: 1px solid var(--bs-border-color-translucent);
    border-radius: var(--bs-border-radius, 0.5rem);
    background: var(--bs-tertiary-bg, transparent);
    overflow: hidden;
}

/* Picker body — layout container only. No padding here; the header
   and content children own their own padding so the header can span
   the full width with its bottom border. */
.music-picker-body {
    /* Reserved for future surface-wide tweaks. Padding lives on
       .music-picker-header and .music-picker-content below. */
}

/* Header — title + close-button row. Mirrors Bootstrap's modal-header
   visual rhythm so the modal surface keeps a familiar header even
   though we dropped Bootstrap's modal-header markup in au3. */
.music-picker-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-2);
    padding: var(--space-3);
    border-bottom: 1px solid var(--bs-border-color-translucent);
}

.music-picker-title {
    /* Override h5 default top/bottom margins; keep h5's font-size
       and let Bootstrap's body font-weight cascade through. The 500
       weight matches Bootstrap's .modal-title default. */
    margin: 0;
    font-weight: 500;
    line-height: 1.5;
}

/* Content — segmented control + paste/library/upload panels + submit.
   Padding matches Bootstrap's modal-body default (1rem) so the modal
   surface renders identical to its pre-au3 spacing. */
.music-picker-content {
    padding: var(--space-3);
}

/* ============================================================
 * au3 — freespace mode F (music) (au3-followup updates surfaces)
 * ============================================================
 *
 * Two rendering surfaces:
 *   1. editor surface — both account/free_space.html (settings)
 *      AND profile/_free_space_modal.html (profile modal). Au3-
 *      followup unified them: the same picker + picked-tile
 *      markup ships on both. CSS rules below apply to both.
 *      - .freespace-music-picker-inline  inline picker wrapper
 *      - .freespace-music-tile           picked-tile (cover+meta+remove)
 *   2. profile view render (profile/view.html)
 *      - .freespace-content-music        embed_chrome wrapper
 *      - .freespace-content-music-empty  owner-only CTA when the
 *                                        music slot is empty (i.e.
 *                                        music_remove'd without
 *                                        picking a replacement)
 *
 * The inline picker wrapper + picked-tile mirror the post-compose
 * equivalents (.post-music-picker-inline, .post-music-attachment-*)
 * so freespace and compose surfaces feel visually consistent. The
 * empty-state CTA mirrors .freespace-content-post-empty.
 *
 * All colors / borders / spacing are token-driven — dark-mode work
 * in pr6c will pick these up automatically.
 */

/* ---- Editor surface (settings page + profile modal) --------- */

/* Inline picker wrapper — same framing as .post-music-picker-inline
   (subtle inset background + soft border so the panel reads as a
   discrete sub-section inside the form). overflow:hidden keeps the
   picker-body header's bottom border from poking past the wrapper's
   rounded corners. */
.freespace-music-picker-inline {
    border: 1px solid var(--bs-border-color-translucent);
    border-radius: var(--bs-border-radius, 0.5rem);
    background: var(--bs-tertiary-bg, transparent);
    overflow: hidden;
}

/* Picked-tile — horizontal row with cover, title/artist/album stack,
   and a remove button. Mirrors .post-music-attachment-tile's layout;
   the difference is the meta column shows three lines (title +
   artist + album) vs compose's two (title + artist). */
.freespace-music-tile {
    display: flex;
    align-items: center;
    gap: var(--space-3, 0.75rem);
    padding: var(--space-2, 0.5rem);
    border: 1px solid var(--bs-border-color-translucent);
    border-radius: var(--bs-border-radius, 0.5rem);
    background: var(--bs-tertiary-bg, transparent);
}

.freespace-music-tile-cover {
    width: 48px;
    height: 48px;
    flex: 0 0 auto;
    border-radius: var(--bs-border-radius-sm, 0.25rem);
    object-fit: cover;
}

.freespace-music-tile-meta {
    flex: 1 1 auto;
    min-width: 0;  /* allows ellipsis on long titles */
}

.freespace-music-tile-title {
    font-weight: 500;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.freespace-music-tile-artist,
.freespace-music-tile-album {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.freespace-music-tile .btn-close {
    flex: 0 0 auto;
    align-self: flex-start;
}

/* ---- Profile view surface ----------------------------------- */

/* Wrapper around embed_chrome on profile-view Mode F render. The
   embed_chrome partial owns its own internal layout (cover + meta +
   iframe / play button); this rule just provides the freespace-cell
   context. */
.freespace-content-music {
    width: 100%;
}

/* Owner-only state when the music slot is empty (music_remove'd
   without picking a new one). Visitors don't see this branch — the
   template skips rendering. Layout mirrors .freespace-content-post-
   empty. */
.freespace-content-music-empty {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
}

/* === au-ux-cleanup — mobile fixes ================================
   Five small mobile regressions caught during dev testing. Each rule
   below is a targeted fix; no broader refactor.
   ================================================================ */

/* ---- Library tab tap-fix (issue 1) ----
   iOS Safari sometimes swallows label-tap-to-toggle-radio events when
   touch-action isn't explicitly set on the label. The picker's library
   tab works fine on desktop but was unresponsive on mobile until this
   was added. -webkit-tap-highlight-color: transparent removes the
   default blue flash so the tap reads as intentional, not glitchy. */
.music-picker-segment {
    touch-action: manipulation;
    -webkit-tap-highlight-color: transparent;
}

/* ---- Profile page horizontal overflow (issue 4) ----
   Page was scrollable horizontally on mobile, leaving an empty band
   on the right side. Two root causes addressed:
     1. .profile-identity is a flex column whose default min-width:
        auto refuses to shrink below children's intrinsic width. A
        long URL in bio (rendered as <a>) or a long display name
        pushed the column wider than the viewport.
     2. .profile-identity-bio and -name had no overflow-wrap, so long
        tokens stayed as one unbreakable run.
   Combined: column can now shrink, and long content breaks where it
   needs to. No safety-net overflow-x: hidden on body — that would
   break position: sticky on the nav. */
.profile-identity {
    min-width: 0;
}

.profile-identity-name {
    overflow-wrap: anywhere;
    max-width: 100%;
}

.profile-identity-bio {
    overflow-wrap: anywhere;
}

/* ---- Post-menu dropdown off-screen overflow (au-ux-cleanup followup) ----
   .post-menu-list is rendered with display:block + visibility:hidden so
   its open/close transition can animate (display:none kills transitions).
   But that means it contributes to layout even when invisible. With
   Bootstrap's default .dropdown-menu position-absolute and no explicit
   left/right pinning, the 200px-wide menu extends rightward from the
   .post-menu wrapper, which itself sits at the card's right edge. On
   narrow viewports that pokes ~150px past the right edge of the page,
   causing horizontal scroll across the whole profile route.

   Fix: pin the menu to the right edge of its absolute-positioned parent
   so it extends LEFTWARD into the card, not rightward past viewport.
   When .show activates, Popper sets inline `inset`/`transform` styles
   that override these rules — the open state still positions correctly. */
.post-menu-list {
    right: 0;
    left: auto;
}
