/* ---------------------------------------------------------------------------
 * shanmp3 design system — components layer.
 *
 * Consumes the --shanmp3-* tokens defined in style.css. This file holds the
 * reusable visual primitives shared across the theme: buttons, cards, grids,
 * chips/badges, avatars, forms, skeletons, empty states, sections, dividers,
 * icons, and the accessible modal/dropdown shells.
 *
 * Class API:  .shanmp3-<component>[--<modifier>][__<element>]
 * No ids, no !important (except reduced-motion), no element selectors beyond
 * those scoped under a .shanmp3- class. Tree-shaken by hand: ship only what
 * the theme uses.
 * ------------------------------------------------------------------------- */

/* =========================================================================
   Buttons
   ========================================================================= */
.shanmp3-btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: 0.5em;
	box-sizing: border-box;
	min-height: var(--shanmp3-touch);
	padding: 0.6em 1.25em;
	border: 1px solid transparent;
	border-radius: var(--shanmp3-radius-pill);
	background: var(--shanmp3-accent);
	color: #ffffff;
	font-family: inherit;
	font-size: 0.95rem;
	font-weight: 600;
	line-height: 1;
	text-decoration: none;
	cursor: pointer;
	user-select: none;
	transition: transform var(--shanmp3-transition-fast),
		background-color var(--shanmp3-transition-fast),
		color var(--shanmp3-transition-fast),
		border-color var(--shanmp3-transition-fast),
		box-shadow var(--shanmp3-transition-fast);
	-webkit-tap-highlight-color: transparent;
}
.shanmp3-btn:hover {
	background: linear-gradient(160deg, #1e2a8a 0%, #3b3fd6 50%, #5356ff 100%);
	color: #ffffff;
	transform: translateY(-1px);
}
.shanmp3-btn:active {
	transform: translateY(0);
}
.shanmp3-btn:disabled,
.shanmp3-btn[aria-disabled="true"] {
	opacity: 0.55;
	cursor: not-allowed;
	transform: none;
}

.shanmp3-btn--secondary {
	background: var(--shanmp3-surface-2);
	color: var(--shanmp3-fg);
	border-color: var(--shanmp3-border);
}
.shanmp3-btn--secondary:hover {
	background: var(--shanmp3-surface);
	color: var(--shanmp3-fg);
	border-color: var(--shanmp3-accent);
}

.shanmp3-btn--ghost {
	background: transparent;
	color: var(--shanmp3-fg);
	border-color: transparent;
}
.shanmp3-btn--ghost:hover {
	background: var(--shanmp3-surface-2);
	color: var(--shanmp3-fg);
}

.shanmp3-btn--danger {
	background: var(--shanmp3-danger);
	color: #fff;
}
.shanmp3-btn--danger:hover {
	background: #ff7075;
	color: #fff;
}

/* Icon button: square, content-centered, used for controls. */
.shanmp3-btn--icon {
	width: var(--shanmp3-touch);
	height: var(--shanmp3-touch);
	min-height: 0;
	padding: 0;
	border-radius: var(--shanmp3-radius-pill);
	background: transparent;
	color: var(--shanmp3-muted);
}
.shanmp3-btn--icon:hover {
	background: var(--shanmp3-surface-2);
	color: var(--shanmp3-fg);
	transform: none;
}
.shanmp3-btn--icon.shanmp3-btn--active,
.shanmp3-btn--icon[aria-pressed="true"] {
	color: var(--shanmp3-accent);
}

.shanmp3-btn--sm {
	min-height: 32px;
	padding: 0.4em 0.85em;
	font-size: 0.85rem;
}
.shanmp3-btn--lg {
	min-height: 52px;
	padding: 0.8em 1.75em;
	font-size: 1.05rem;
}
.shanmp3-btn--block {
	display: flex;
	width: 100%;
}

/* =========================================================================
   Cards
   ========================================================================= */
/* Cover-dominant card: the cover fills the top edge-to-edge; the title/meta sit
   in a tight foot below. On hover the whole card lifts and a centered play
   button fades in over the cover. No surface background/border — the cover is
   the visual. Data attributes on the markup are untouched (see class-blocks.php). */
.shanmp3-card {
	position: relative;
	display: flex;
	flex-direction: column;
	gap: 0.1rem;
	padding: 0;
	background: transparent;
	border: 0;
	border-radius: var(--shanmp3-radius-lg);
	transition: transform var(--shanmp3-transition);
}
.shanmp3-card:hover {
	transform: translateY(-4px);
}
a.shanmp3-card {
	text-decoration: none;
	color: inherit;
}
/* Card title is the navigation link (card itself is not an anchor so the play
   button can be a real <button> without nesting interactive elements). */
a.shanmp3-card__title {
	text-decoration: none;
	color: var( --shanmp3-fg );
	display: block;
}
a.shanmp3-card__title:hover {
	color: var( --shanmp3-accent );
}

.shanmp3-card__cover {
	position: relative;
	display: block;
	width: 100%;
	aspect-ratio: 1 / 1;
	border-radius: var(--shanmp3-radius-lg);
	overflow: hidden;
	background: var(--shanmp3-surface-2);
	transition: box-shadow var(--shanmp3-transition);
}
.shanmp3-card__cover img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	display: block;
}
.shanmp3-card:hover .shanmp3-card__cover {
	box-shadow: var(--shanmp3-shadow-lg);
}
/* Circular cover variant (artist avatars). */
.shanmp3-card__cover--circle {
	border-radius: 50%;
}

.shanmp3-card__title {
	margin: 0;
	padding-top: 0.1rem;
	font-size: 1rem;
	font-weight: 600;
	line-height: 1.8;

	color: var(--shanmp3-fg);
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
.shanmp3-card__meta {
	margin: 0;
	font-size: 0.85rem;
	color: var(--shanmp3-muted);
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
/* Artist-name links inside the meta line stay underlined by default; drop the
   underline so the card reads cleanly while a hover color shift keeps the link
   discoverable. */
.shanmp3-card__meta a {
	text-decoration: none;
	color: inherit;
}
.shanmp3-card__meta a:hover {
	color: var(--shanmp3-accent);
}

/* Play button: centered over the cover, fades + scales in on hover. */
.shanmp3-card__play {
	position: absolute;
	top: 50%;
	left: 50%;
	z-index: 2;
	display: inline-flex;
	width: 48px;
	height: 48px;
	min-height: 0;
	padding: 0;
	border: none;
	border-radius: 50%;
	background: var(--shanmp3-accent);
	color: #0b0c10;
	align-items: center;
	justify-content: center;
	box-shadow: var(--shanmp3-shadow-md);
	opacity: 0;
	transform: translate(-50%, -50%) scale(0.9);
	transition: opacity var(--shanmp3-transition),
		transform var(--shanmp3-transition);
	cursor: pointer;
}
/* Touch devices have no hover: keep the play button visible. */
@media ( hover: none ) {
	.shanmp3-card__play {
		opacity: 1;
		transform: translate(-50%, -50%) scale(1);
	}
}
.shanmp3-card:hover .shanmp3-card__play,
.shanmp3-card:focus-within .shanmp3-card__play {
	opacity: 1;
	transform: translate(-50%, -50%) scale(1);
}
/* On mobile, dim the always-visible play button so it sits quieter over the
   cover (it still brightens to full on tap/focus). */
@media (max-width: 767px) {
	.shanmp3-card__play {
		opacity: 0.6;
	}
}

/* Playlist card play button: anchored to the cover's bottom-right (instead of
   centered) and a little smaller (40px vs the default 48px). Scoped to playlist
   cards so song/album/artist cards keep their centered play overlay. The
   bottom/right insets replace the centered translate, so the transform drops
   the translate(-50%,-50%) and keeps only the scale. */
.shanmp3-card--playlist .shanmp3-card__play {
	top: auto;
	left: auto;
	bottom: 0.6rem;
	right: 0.6rem;
	width: 40px;
	height: 40px;
	transform: scale(0.9);
}
.shanmp3-card--playlist .shanmp3-card__play svg {
	width: 20px;
	height: 20px;
}
.shanmp3-card--playlist:hover .shanmp3-card__play,
.shanmp3-card--playlist:focus-within .shanmp3-card__play {
	transform: scale(1);
}
@media (hover: none) {
	.shanmp3-card--playlist .shanmp3-card__play {
		transform: scale(1);
	}
}

/* Album card play button: anchored to the cover's bottom-right (instead of
   centered) and slightly smaller (40px), matching the playlist card. Scoped to
   album cards so song/artist cards keep their centered play overlay. The
   bottom/right insets replace the centered translate, so the transform drops
   the translate(-50%,-50%) and keeps only the scale. */
.shanmp3-card--album .shanmp3-card__play {
	top: auto;
	left: auto;
	bottom: 0.6rem;
	right: 0.6rem;
	width: 40px;
	height: 40px;
	transform: scale(0.9);
}
.shanmp3-card--album .shanmp3-card__play svg {
	width: 20px;
	height: 20px;
}
.shanmp3-card--album:hover .shanmp3-card__play,
.shanmp3-card--album:focus-within .shanmp3-card__play {
	transform: scale(1);
}
@media (hover: none) {
	.shanmp3-card--album .shanmp3-card__play {
		transform: scale(1);
	}
}

/* Song card "more" control: a 3-dot button pinned to the card's top-right on
   every viewport. It opens a native <dialog> (top layer — see
   .shanmp3-card__more-dialog below), which floats above every stacking context
   so no card-lift / z-index hacks are needed. The toggle itself is the
   positioned element (the old .shanmp3-card__more wrapper is gone); scoped to
   .shanmp3-card--song so the inline list/chart-row toggle is unaffected. */
.shanmp3-card--song .shanmp3-card__more-toggle {
	position: absolute;
	top: 0.5rem;
	right: 0.5rem;
	z-index: 3;
}
/* Lift the hovered/focused card above its grid siblings so the hover lift
   (translateY(-4px) from .shanmp3-card:hover) isn't painted under the next
   row. The dialog itself needs no such lift — it lives in the top layer. */
.shanmp3-card--song:hover,
.shanmp3-card--song:focus-within {
	z-index: 1;
}
.shanmp3-card__more-toggle {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 32px;
	height: 32px;
	padding: 0;
	border: 0;
	border-radius: 50%;
	background: rgba(0, 0, 0, 0.5);
	color: #fff;
	cursor: pointer;
	-webkit-backdrop-filter: blur(2px);
	backdrop-filter: blur(2px);
	transition: background 0.15s ease;
}
.shanmp3-card__more-toggle:hover {
	background: rgba(0, 0, 0, 0.65);
}
.shanmp3-card__more-toggle svg {
	width: 18px;
	height: 18px;
}
/* Play-count badge on the card cover, tucked under the 3-dot: a headphones
   glyph with the play number stacked beneath it, no background. pointer-
   events:none so it never blocks the card's hover/click. */
.shanmp3-card__plays {
	position: absolute;
	top: calc(0.5rem + 32px + 0.4rem);
	right: 0.5rem;
	z-index: 3;
	/* Match the 3-dot's 32px box so the stacked icon+number centers directly
	   beneath the dot, not flush to the card's right edge. */
	width: 32px;
	display: inline-flex;
	flex-direction: column;
	align-items: center;
	gap: 0.1rem;
	padding: 0;
	background: transparent;
	color: #fff;
	font-size: 0.66rem;
	font-weight: 600;
	line-height: 1;
	pointer-events: none;
	white-space: nowrap;
	text-shadow: 0 1px 3px rgba(0, 0, 0, 0.7);
}
.shanmp3-card__plays-icon {
	width: 14px;
	height: 14px;
	flex: 0 0 auto;
	filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.7));
}
.shanmp3-card__plays-num {
	font-variant-numeric: tabular-nums;
	text-shadow: 0 1px 3px rgba(0, 0, 0, 0.7);
}
/* Detail rows: icon | label | value, one per line. Rendered inside the
   dialog body; padding is neutralized by the .shanmp3-card__more-dialog
   overrides below so the dialog body's own padding isn't doubled. */
.shanmp3-card__more-meta {
	display: flex;
	flex-direction: column;
	gap: 0.35rem;
	padding: 0 0.75rem 0.5rem;
}
.shanmp3-card__more-row {
	display: flex;
	align-items: center;
	gap: 0.55rem;
	min-width: 0;
}
.shanmp3-card__more-row-icon {
	flex: 0 0 auto;
	width: 16px;
	height: 16px;
	color: var(--shanmp3-muted);
}
.shanmp3-card__more-row-label {
	flex: 0 0 auto;
	font-size: 0.78rem;
	font-weight: 600;
	letter-spacing: 0.02em;
	color: var(--shanmp3-muted);
}
.shanmp3-card__more-row-value {
	flex: 1 1 auto;
	min-width: 0;
	font-size: 0.85rem;
	color: var(--shanmp3-fg);
	text-align: right;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
.shanmp3-card__more-row-value a {
	color: inherit;
	text-decoration: none;
}
.shanmp3-card__more-row-value a:hover {
	color: var(--shanmp3-accent);
}

/* --- Song info dialog (native <dialog>, top layer) ----------------------
   The 3-dot opens a <dialog> via showModal(). The top layer places it above
   every stacking context, so no card-lift / z-index / containing-block hacks
   are needed — the old position:fixed bottom sheet was trapped behind sibling
   cards because the card's hover transform made the card its containing block.
   Reuses the shared .shanmp3-credit-dialog base (centered panel, border,
   shadow, ::backdrop) from shanmp3-music.css; .shanmp3-card__more-dialog
   scopes the bottom-sheet mobile overrides and neutralizes sheet-era paddings.

   Specificity note: shanmp3-music.css loads AFTER this file and defines
   .shanmp3-credit-dialog at (0,1,0), so the dialog-scoped overrides below use
   `dialog.shanmp3-card__more-dialog` (0,1,1) to win the width/margin/position/
   border-radius fights against that later base rule. */
dialog.shanmp3-card__more-dialog {
	width: min( 30rem, 92vw );
}
/* The cover/title/artist head and the meta rows carry sheet-era horizontal
   padding (0.75rem) that would double up inside the dialog body's own padding.
   Zero it out so spacing is driven by .shanmp3-credit-dialog__body. */
.shanmp3-card__more-dialog .shanmp3-card__more-head,
.shanmp3-card__more-dialog .shanmp3-card__more-meta {
	padding: 0;
}
.shanmp3-card__more-dialog .shanmp3-dropdown__sep {
	margin: 0.25rem 0;
}
.shanmp3-card__more-dialog .shanmp3-dropdown__item {
	padding: 0.5em 0.6em;
	border-radius: var( --shanmp3-radius-md, 0.5rem );
	font-size: 0.92rem;
}

/* Mobile bottom-sheet idiom for these popups now lives on the shared
   .shanmp3-credit-dialog base in shanmp3-music.css, so the single-song "Track
   Info" and album "Album info" popups get the same bottom-anchored treatment as
   the song-card "Song info" popup. Only the desktop width override above is
   card-scoped (narrower 30rem panel). */
/* Dialog body header: a small cover thumb + song title (linked) + artist,
   shown at the top of the dialog so the user sees which song it is before the
   detail rows. The title bar + close button above it come from
   .shanmp3-credit-dialog__head. */
.shanmp3-card__more-head {
	display: flex;
	align-items: center;
	gap: 0.75rem;
	padding: 0.25rem 0.75rem 0.75rem;
	min-width: 0;
}
.shanmp3-card__more-cover {
	flex: 0 0 auto;
	width: 48px;
	height: 48px;
	border-radius: var(--shanmp3-radius-md, 0.5rem);
	overflow: hidden;
	background: var(--shanmp3-surface-2);
}
.shanmp3-card__more-cover img,
.shanmp3-card__more-cover svg {
	width: 100%;
	height: 100%;
	object-fit: cover;
	display: block;
}
.shanmp3-card__more-text {
	flex: 1 1 auto;
	min-width: 0;
	display: flex;
	flex-direction: column;
	gap: 0.15rem;
}
.shanmp3-card__more-title {
	font-size: 0.95rem;
	font-weight: 700;
	color: var(--shanmp3-fg);
	text-decoration: none;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
.shanmp3-card__more-title:hover {
	color: var(--shanmp3-accent);
}
.shanmp3-card__more-artist {
	font-size: 0.8rem;
	color: var(--shanmp3-muted);
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
.shanmp3-card__more-artist a {
	color: inherit;
	text-decoration: none;
}
.shanmp3-card__more-artist a:hover {
	color: var(--shanmp3-accent);
}

@media (max-width: 767px) {
	/* Play button at the cover's bottom-right, shrunk to match the 3-dot. */
	.shanmp3-card--song .shanmp3-card__play {
		top: auto;
		left: auto;
		bottom: 0.5rem;
		right: 0.5rem;
		transform: none !important; /* beat reduced-motion's centering */
		opacity: 0.6;
		width: 32px;
		height: 32px;
		/* Match the 3-dot: dark translucent disc + white icon. */
		background: rgba(0, 0, 0, 0.5);
		color: #fff;
	}
	.shanmp3-card--song .shanmp3-card__play svg {
		width: 18px;
		height: 18px;
	}
	.shanmp3-card--song:hover .shanmp3-card__play,
	.shanmp3-card--song:focus-within .shanmp3-card__play {
		transform: none;
		opacity: 1;
	}
}

/* Artist card: circular avatar + centered text (replaces the former inline style
   on the anchor in class-blocks.php). */
.shanmp3-card--artist {
	text-align: center;
	align-items: center;
}

/* Label card: square cover (default radius), left-aligned text like album/song
   cards — distinct from the artist circle. The "Record Label" meta line reads as
   the card subtitle. */
.shanmp3-card--label {
	text-align: left;
}

/* Credit cards (Composer/Studio/Producer/Band). Shape is driven by the entity
   config: people (composer/producer/band) use the artist-style circular avatar
   + centered text; studio (an organization) uses the label-style square cover +
   left-aligned text. The subtitle meta line reads as the role label. */
.shanmp3-card--credit-circle {
	text-align: center;
	align-items: center;
}
.shanmp3-card--credit-square {
	text-align: left;
}

/* News card: landscape 16:9 cover (overrides the base 1/1), with a category
   eyebrow chip pinned to the top-left of the cover, a 2-line clamped title,
   and a date · reading-time meta row. */
.shanmp3-card--news {
	text-align: left;
}
.shanmp3-card--news .shanmp3-card__cover {
	aspect-ratio: 16 / 9;
	/* Landscape thumbs read better with a tighter corner than the 20px
	   (--shanmp3-radius-lg) used on the square 1:1 entity covers. */
	border-radius: var(--shanmp3-radius-sm);
}
.shanmp3-news__cat {
	position: absolute;
	top: 8px;
	left: 8px;
	z-index: 1;
	padding: 0.25em 0.6em;
	font-size: 0.68rem;
	font-weight: 700;
	line-height: 1;
	text-transform: uppercase;
	letter-spacing: 0.02em;
	color: var(--shanmp3-surface, #fff);
	background: var(--shanmp3-accent, #6366f1);
	border-radius: var(--shanmp3-radius-pill, 999px);
}
.shanmp3-card--news .shanmp3-card__title {
	display: -webkit-box;
	-webkit-box-orient: vertical;
	-webkit-line-clamp: 2;
	overflow: hidden;
	padding-top: 0;
	line-height: 1.6;
}
/* Category accent — a short accent-colored bar + an uppercase category label
   linking to the term archive. Sits between the cover and the title. */
.shanmp3-news__accent {
	display: flex;
	align-items: center;
	gap: 0.5rem;
	margin-top: 0.15rem;
}
.shanmp3-news__accent-bar {
	flex: 0 0 auto;
	width: 22px;
	height: 2px;
	background: var(--shanmp3-accent);
	border-radius: 1px;
}
.shanmp3-news__accent-label {
	font-size: 0.66rem;
	font-weight: 800;
	letter-spacing: 0.12em;
	text-transform: uppercase;
	color: var(--shanmp3-accent);
	text-decoration: none;
}
.shanmp3-news__accent-label:hover {
	text-decoration: underline;
}
.shanmp3-card--news .shanmp3-card__meta {
	font-size: 0.78rem;
}

/* Secondary meta line (label names) under the primary meta on song/album/artist
   cards. Smaller and dimmer so the artist/album line stays the dominant subtitle. */
.shanmp3-card__meta--label {
	font-size: 0.76rem;
	opacity: 0.85;
}

/* =========================================================================
   Responsive grid (used by card grids in Phase 08 patterns)
   ========================================================================= */
.shanmp3-grid {
	display: grid;
	gap: 1rem;
	grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
}
.shanmp3-grid--cols-2 { grid-template-columns: repeat(2, 1fr); }
.shanmp3-grid--cols-3 { grid-template-columns: repeat(3, 1fr); }
.shanmp3-grid--cols-4 { grid-template-columns: repeat(4, 1fr); }
.shanmp3-grid--cols-5 { grid-template-columns: repeat(5, 1fr); }
@media (max-width: 600px) {
	.shanmp3-grid {
		grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
		gap: 0.75rem;
	}
	.shanmp3-grid--cols-3,
	.shanmp3-grid--cols-4,
	.shanmp3-grid--cols-5 {
		grid-template-columns: repeat(2, 1fr);
	}
}
/* Opt-in mobile carousel (shanmp3-grid--carousel). On mobile the grid becomes a
   horizontally swipeable scroll-snap row of fixed-width cards — native touch
   scrolling, no JS. Desktop keeps the normal grid (the modifier is inert above
   600px). (0,2,0) beats the base .shanmp3-grid (0,1,0) mobile rule. */
@media (max-width: 600px) {
	.shanmp3-grid--carousel {
		display: flex;
		flex-wrap: nowrap;
		gap: 0.75rem;
		overflow-x: auto;
		scroll-snap-type: x mandatory;
		-webkit-overflow-scrolling: touch;
		/* Hide the scrollbar — the swipe affordance is the peeking next card. */
		scrollbar-width: none;
		padding-bottom: 0.25rem;
	}
	.shanmp3-grid--carousel::-webkit-scrollbar {
		display: none;
	}
	/* Fixed-width items so ~2.5 cards show and the next one peeks to signal
	   there's more to swipe. scroll-snap-align:start lands a card cleanly. */
	.shanmp3-grid--carousel > * {
		flex: 0 0 auto;
		width: 150px;
		scroll-snap-align: start;
	}
}

/* Opt-in always-on carousel (shanmp3-grid--carousel-always). Same swipeable
   scroll-snap row as the mobile carousel, but applied at every breakpoint so a
   section (e.g. home "Trending this week") reads as a horizontal carousel on
   desktop too. Native scroll, no JS. Source order wins over the base grid. */
.shanmp3-grid--carousel-always {
	display: flex;
	flex-wrap: nowrap;
	gap: 0.75rem;
	overflow-x: auto;
	scroll-snap-type: x mandatory;
	-webkit-overflow-scrolling: touch;
	scrollbar-width: none;
	padding-bottom: 0.25rem;
}
.shanmp3-grid--carousel-always::-webkit-scrollbar {
	display: none;
}
.shanmp3-grid--carousel-always > * {
	flex: 0 0 auto;
	width: 200px;
	scroll-snap-align: start;
}
@media (max-width: 600px) {
	.shanmp3-grid--carousel-always > * {
		width: 150px;
	}
}

/* =========================================================================
   Chips & badges
   ========================================================================= */
.shanmp3-chip {
	display: inline-flex;
	align-items: center;
	gap: 0.35em;
	padding: 0.3em 0.75em;
	border-radius: var(--shanmp3-radius-pill);
	background: var(--shanmp3-surface-2);
	color: var(--shanmp3-fg);
	font-size: 0.8rem;
	font-weight: 500;
	text-decoration: none;
	white-space: nowrap;
	transition: background-color var(--shanmp3-transition-fast),
		color var(--shanmp3-transition-fast);
}
a.shanmp3-chip:hover {
	background: var(--shanmp3-accent);
	color: #0b0c10;
}
.shanmp3-chip--active {
	background: var(--shanmp3-accent);
	color: #0b0c10;
}

.shanmp3-badge {
	display: inline-flex;
	align-items: center;
	padding: 0.15em 0.45em;
	border-radius: var(--shanmp3-radius-sm);
	background: var(--shanmp3-surface-2);
	color: var(--shanmp3-muted);
	font-size: 0.7rem;
	font-weight: 700;
	letter-spacing: 0.04em;
	text-transform: uppercase;
}
.shanmp3-badge--explicit {
	background: var(--shanmp3-danger);
	color: #fff;
}
.shanmp3-badge--new {
	background: var(--shanmp3-accent);
	color: #0b0c10;
}
/* Premium crown badge (membership). */
.shanmp3-badge--premium {
	gap: 0.25em;
	background: linear-gradient(135deg, #f5c451, #e0a328);
	color: #2a1c00;
}
/* Featured (editorial) — accent star. */
.shanmp3-badge--featured {
	gap: 0.25em;
	background: var(--shanmp3-accent);
	color: #0b0c10;
}
/* Promoted / sponsored — distinct amber so it never reads as organic. */
.shanmp3-badge--promoted {
	gap: 0.25em;
	background: #b98a1a;
	color: #1a1200;
}
/* Verified-artist mark — Twitter-style: a scalloped/curly-edged seal (12
   outward scallops) filled with the accent color, with a white check, icon-only.
   The scalloped edge distinguishes it from the plain circular premium/explicit
   badges. The "Verified Artist" label is exposed to assistive tech via the badge
   aria-label, not as visible text, so it reads as a compact trust signal next to
   the name instead of a large pill. */
.shanmp3-verified-badge {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	flex: 0 0 auto;
	width: clamp(16px, 1.1em, 24px);
	height: clamp(16px, 1.1em, 24px);
	color: var(--shanmp3-accent);
	vertical-align: middle;
}
.shanmp3-verified-badge__seal {
	width: 100%;
	height: 100%;
	display: block;
}
/* The scalloped seal ring is filled with the accent color; the check sits on
   top in white so the mark reads as a trust badge, distinct from the plain
   circular premium/explicit badges. */
.shanmp3-verified-badge__ring {
	fill: var(--shanmp3-accent);
}
.shanmp3-verified-badge__check {
	stroke: #fff;
	stroke-width: 2.2;
}
/* Archive card: a little breathing room between the artist name and the mark. */
.shanmp3-card__title .shanmp3-verified-badge {
	margin-left: 0.3em;
}
/* Artist single: the mark scales with the large h1 title (up to 32px), so it
   reads as a clear trust signal beside the name rather than a tiny glyph. */
.shanmp3-artist-profile__title-row .shanmp3-verified-badge {
	width: clamp(20px, 1.3em, 32px);
	height: clamp(20px, 1.3em, 32px);
}

/* Title + verified-mark inline row (artist single). The h1 stays a heading for
   SEO; the verified mark sits right after the title text and wraps to its own
   line only when the title fills the row. The gap is qualified to (0,2,0) so it
   beats the core layout flex gap (var(--wp--style--block-gap) = 24px) injected
   on .is-layout-flex at (0,1,0) — without this override the mark sits a full
   24px from the name. */
.shanmp3-artist-profile__title-row.shanmp3-artist-profile__title-row {
	align-items: center;
	gap: 0.3rem;
}
.shanmp3-artist-profile__title-row .shanmp3-hero__title {
	margin: 0;
	min-width: 0;
	flex: 0 1 auto;
}
/* Members-only lock (premium content indicator). */
.shanmp3-badge--members-only {
	gap: 0.25em;
	background: rgba(245, 196, 81, 0.16);
	color: #f5c451;
}

/* Badge positioned over a card cover (PREMIUM / FEATURED / PROMOTED). */
.shanmp3-card__badge {
	position: absolute;
	top: 8px;
	left: 8px;
	z-index: 3;
	font-size: 0.625rem;
	padding: 0.2em 0.5em;
	border-radius: var(--shanmp3-radius-sm, 0.25rem);
	box-shadow: var(--shanmp3-shadow-sm);
	pointer-events: none;
}
/* Icon-only card badge (the premium lock on song cards): just a yellow lock,
   no text and no background chip — the lock sits directly on the cover art.
   Qualified to (0,2,0) so it overrides the gold-pill .shanmp3-badge--premium
   background/color at (0,1,0). A drop-shadow keeps the yellow readable over
   light covers. */
.shanmp3-card__badge.shanmp3-card__badge--icon {
	padding: 0;
	width: auto;
	height: auto;
	gap: 0;
	background: none;
	box-shadow: none;
	color: #f5c451;
}
.shanmp3-card__badge--icon .shanmp3-icon {
	width: 22px;
	height: 22px;
	filter: drop-shadow( 0 1px 2px rgba( 0, 0, 0, 0.6 ) );
}
/* End-aligned badge (top-right) so FEATURED never collides with the top-left
   PREMIUM badge when a card is both featured and premium. */
.shanmp3-card__badge--end {
	left: auto;
	right: 8px;
}
/* Bottom-aligned badge (bottom-left) so PROMOTED never collides with the
   top-left PREMIUM or top-right FEATURED badges. */
.shanmp3-card__badge--bottom {
	top: auto;
	bottom: 8px;
}

/* Artist-card avatar wrapper. The circular cover clips its image
   (overflow:hidden + border-radius:50%), so any badge placed INSIDE the cover
   is cut away by the circular mask — the featured/promoted marks were
   invisible on artist cards. The wrapper is the same box as the cover but
   does NOT clip, so the seals below sit on the avatar's edge, readable. */
.shanmp3-card--artist .shanmp3-card__avatar,
.shanmp3-card--credit-circle .shanmp3-card__avatar {
	position: relative;
	display: block;
	width: 100%;
	line-height: 0;
}
/* Circular icon "seal" stamped on the avatar edge — icon-only (no text), with
   a ring in the page background color so it reads as a separate mark floating
   off the circle. Featured (star, accent) anchors bottom-right; promoted
   (megaphone, amber) anchors bottom-left, so the two never collide. Qualified
   to (0,2,0)+ to override the base .shanmp3-card__badge corner offsets and the
   .shanmp3-badge--featured/--promoted pill padding/background at (0,1,0). */
.shanmp3-card--artist .shanmp3-card__badge--seal {
	top: auto;
	left: auto;
	bottom: 4%;
	right: 4%;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: clamp(22px, 26%, 36px);
	height: clamp(22px, 26%, 36px);
	padding: 0;
	gap: 0;
	border-radius: 50%;
	border: 2px solid var(--shanmp3-fg);
	box-shadow: var(--shanmp3-shadow-sm);
	pointer-events: none;
}
.shanmp3-card--artist .shanmp3-card__badge--seal .shanmp3-icon {
	width: 58%;
	height: 58%;
}
.shanmp3-card--artist .shanmp3-badge--featured.shanmp3-card__badge--seal {
	right: 4%;
	background: var(--shanmp3-accent);
	color: #ffffff;
}
.shanmp3-card--artist .shanmp3-badge--promoted.shanmp3-card__badge--seal {
	right: auto;
	left: 4%;
	background: #b98a1a;
	color: #1a1200;
}
/* Song-card PROMOTED badge: a smaller, white-label chip so the commercial tag
   reads as a compact, low-noise mark over the cover art. Scoped to the song
   card at (0,2,0) so it overrides the base .shanmp3-badge--promoted color and
   the .shanmp3-card__badge size, and the icon override at (0,3,0) beats
   .shanmp3-icon--sm (0,1,0) — without touching the same badge on album / artist
   / playlist / podcast cards. color:#fff also drives the megaphone icon via
   currentColor. */
.shanmp3-card--song .shanmp3-badge--promoted {
	font-size: 0.56rem;
	padding: 0.14em 0.42em;
	gap: 0.22em;
	color: #fff;
}
.shanmp3-card--song .shanmp3-badge--promoted .shanmp3-icon {
	width: 15px;
	height: 15px;
}

/* Premium gate card: shown on a protected song/album/playlist single when the
   acting user cannot access the content. Replaces the play controls with a
   lock + explanation + CTA. */
.shanmp3-premium-gate {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 0.75rem;
	padding: 1.75rem 1.25rem;
	margin: 1rem 0;
	border: 1px solid var(--shanmp3-border, rgba(255, 255, 255, 0.08));
	border-radius: var(--shanmp3-radius-lg, 0.75rem);
	background:
		radial-gradient(120% 120% at 50% 0%, rgba(245, 196, 81, 0.10), transparent 60%),
		var(--shanmp3-surface-2);
	text-align: center;
}
.shanmp3-premium-gate__icon {
	display: inline-flex;
	width: 52px;
	height: 52px;
	align-items: center;
	justify-content: center;
	border-radius: 50%;
	background: rgba(245, 196, 81, 0.16);
	color: #f5c451;
}
.shanmp3-premium-gate__icon svg {
	width: 26px;
	height: 26px;
}
.shanmp3-premium-gate__title {
	margin: 0;
	font-size: 1.15rem;
	font-weight: 700;
	color: var(--shanmp3-fg);
}
.shanmp3-premium-gate__text {
	margin: 0;
	font-size: 0.9rem;
	color: var(--shanmp3-muted);
	max-width: 38ch;
}
.shanmp3-premium-gate__cta {
	display: inline-flex;
	align-items: center;
	gap: 0.4em;
	margin-top: 0.25rem;
	padding: 0.6em 1.4em;
	border-radius: 999px;
	background: linear-gradient(135deg, #f5c451, #e0a328);
	color: #2a1c00;
	font-weight: 700;
	text-decoration: none;
	box-shadow: var(--shanmp3-shadow-md);
}
.shanmp3-premium-gate__cta:hover {
	filter: brightness(1.05);
}
.shanmp3-premium-gate--private .shanmp3-premium-gate__icon {
	background: rgba(255, 255, 255, 0.06);
	color: var(--shanmp3-muted);
}

/* =========================================================================
   Avatar
   ========================================================================= */
.shanmp3-avatar {
	display: inline-block;
	width: 40px;
	height: 40px;
	border-radius: 50%;
	object-fit: cover;
	background: var(--shanmp3-surface-2);
}
.shanmp3-avatar--sm { width: 28px; height: 28px; }
.shanmp3-avatar--lg { width: 160px; height: 160px; }

/* =========================================================================
   Forms
   ========================================================================= */
.shanmp3-form {
	display: flex;
	flex-direction: column;
	gap: 1rem;
}
.shanmp3-field {
	display: flex;
	flex-direction: column;
	gap: 0.4rem;
}
.shanmp3-label {
	font-size: 0.85rem;
	font-weight: 600;
	color: var(--shanmp3-fg);
}
.shanmp3-input,
.shanmp3-select,
.shanmp3-textarea {
	box-sizing: border-box;
	width: 100%;
	min-height: var(--shanmp3-touch);
	padding: 0.6em 0.85em;
	background: var(--shanmp3-surface);
	border: 1px solid var(--shanmp3-border);
	border-radius: var(--shanmp3-radius);
	color: var(--shanmp3-fg);
	font-family: inherit;
	font-size: 1rem;
	transition: border-color var(--shanmp3-transition-fast),
		box-shadow var(--shanmp3-transition-fast);
}
.shanmp3-textarea {
	min-height: 120px;
	resize: vertical;
}
.shanmp3-input::placeholder,
.shanmp3-textarea::placeholder {
	color: var(--shanmp3-muted);
}
.shanmp3-input:focus,
.shanmp3-select:focus,
.shanmp3-textarea:focus {
	outline: none;
	border-color: var(--shanmp3-accent);
	box-shadow: var(--shanmp3-shadow-focus);
}
.shanmp3-hint {
	font-size: 0.8rem;
	color: var(--shanmp3-muted);
}
.shanmp3-checkbox {
	display: inline-flex;
	align-items: center;
	gap: 0.5rem;
	min-height: var(--shanmp3-touch);
	color: var(--shanmp3-fg);
	cursor: pointer;
}
.shanmp3-checkbox input {
	width: 18px;
	height: 18px;
	accent-color: var(--shanmp3-accent);
}

/* =========================================================================
   Skeletons (loading placeholders)
   ========================================================================= */
.shanmp3-skeleton {
	position: relative;
	display: block;
	background: var(--shanmp3-skeleton-base);
	border-radius: var(--shanmp3-radius-sm);
	overflow: hidden;
}
.shanmp3-skeleton::after {
	content: "";
	position: absolute;
	inset: 0;
	transform: translateX(-100%);
	background: linear-gradient(
		90deg,
		transparent,
		var(--shanmp3-skeleton-shine),
		transparent
	);
	animation: shanmp3-shimmer 1.4s infinite;
}
.shanmp3-skeleton--text {
	height: 0.9rem;
	width: 100%;
}
.shanmp3-skeleton--title {
	height: 1.1rem;
	width: 70%;
}
.shanmp3-skeleton--rect {
	border-radius: var(--shanmp3-radius);
	aspect-ratio: 1 / 1;
}
.shanmp3-skeleton--circle {
	border-radius: 50%;
	width: 40px;
	height: 40px;
}
@keyframes shanmp3-shimmer {
	100% { transform: translateX(100%); }
}

/* A card-shaped skeleton for grid loading states. */
.shanmp3-skeleton-card {
	display: flex;
	flex-direction: column;
	gap: 0.6rem;
	padding: 1rem;
	background: var(--shanmp3-surface);
	border-radius: var(--shanmp3-radius-lg);
}

/* =========================================================================
   Empty states
   ========================================================================= */
.shanmp3-empty {
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: 0.75rem;
	padding: 3rem 1.5rem;
	text-align: center;
	color: var(--shanmp3-muted);
}
.shanmp3-empty__icon {
	display: inline-flex;
	width: 64px;
	height: 64px;
	align-items: center;
	justify-content: center;
	border-radius: 50%;
	background: var(--shanmp3-surface-2);
	color: var(--shanmp3-muted);
}
.shanmp3-empty__icon .shanmp3-icon {
	width: 32px;
	height: 32px;
}
.shanmp3-empty__title {
	margin: 0;
	font-size: 1.25rem;
	font-weight: 700;
	color: var(--shanmp3-fg);
}
.shanmp3-empty__text {
	margin: 0;
	max-width: 40ch;
	font-size: 0.95rem;
	line-height: 1.5;
}

/* =========================================================================
   Section (heading row + "see all" link)
   ========================================================================= */
.shanmp3-section {
	display: flex;
	flex-direction: column;
	gap: 1rem;
}
/*
 * Standard page width: every top-level content wrapper inside <main> is capped
 * at --shanmp3-page-width (1280px) — the same column the navbar and the player
 * bar align to (see style.css / shanmp3-player.css). theme.json sets contentSize
 * and wideSize to the same 1280px, so core's constrained layout already caps
 * these children; these rules are the explicit source of truth and use
 * `width: 100%` (not auto margins) so the wrappers also stretch when they are
 * flex items of a column container (e.g. .shanmp3-library) instead of being
 * left content-sized by auto margins. The (0,2,0) qualifier beats core's
 * `.wp-block-group.is-layout-constrained > :where(...)` cap.
 */
.shanmp3-main .wp-block-group.shanmp3-section {
	max-width: var(--shanmp3-page-width);
	width: 100%;
}
.shanmp3-main .wp-block-group.shanmp3-library {
	max-width: var(--shanmp3-page-width);
}
.shanmp3-main .wp-block-group.shanmp3-home-hero {
	max-width: var(--shanmp3-page-width);
	margin-inline: auto;
}
.shanmp3-main .wp-block-group.shanmp3-hero {
	max-width: var(--shanmp3-page-width);
	width: 100%;
}
.shanmp3-main .wp-block-group.shanmp3-artist-profile {
	max-width: var(--shanmp3-page-width);
	width: 100%;
}
.shanmp3-section__head {
	display: flex;
	align-items: baseline;
	justify-content: space-between;
	gap: 1rem;
}
.shanmp3-section__title {
	margin: 0;
	font-size: var(--wp--preset--font-size--large, 1.25rem);
	font-weight: 700;
	color: var(--shanmp3-fg);
}
.shanmp3-section__more {
	font-size: 0.85rem;
	font-weight: 600;
	color: var(--shanmp3-muted);
	text-decoration: none;
	white-space: nowrap;
	transition: color var(--shanmp3-transition-fast);
}
.shanmp3-section__more:hover {
	color: var(--shanmp3-accent);
}
/* Editorial tag on a section head (e.g. the "Editorial picks" label on the
   Featured section). Sits on the right via the head's space-between layout. */
.shanmp3-section__tag {
	display: inline-flex;
	align-items: center;
	gap: 0.35em;
	font-size: 0.75rem;
	font-weight: 600;
	letter-spacing: 0.04em;
	text-transform: uppercase;
	color: var(--shanmp3-accent);
	white-space: nowrap;
}
.shanmp3-section__tag .shanmp3-icon {
	color: var(--shanmp3-accent);
}
/* Sponsored tag on a promoted section — distinct amber so the section header
   never reads as an editorial pick. */
.shanmp3-section__tag--sponsored {
	color: #b98a1a;
}
.shanmp3-section__tag--sponsored .shanmp3-icon {
	color: #b98a1a;
}
/* Inline sponsored badge (arbitrary content). */
.shanmp3-sponsored-badge {
	vertical-align: middle;
}

/* =========================================================================
   Divider
   ========================================================================= */
.shanmp3-divider {
	border: 0;
	border-top: 1px solid var(--shanmp3-border);
	margin: 2rem 0;
}

/* =========================================================================
   Icons (inline SVG sizing)
   ========================================================================= */
.shanmp3-icon {
	display: inline-block;
	width: 24px;
	height: 24px;
	flex: 0 0 auto;
	vertical-align: middle;
	stroke-width: 2;
	stroke: currentColor;
	fill: none;
	stroke-linecap: round;
	stroke-linejoin: round;
}
.shanmp3-icon--sm { width: 18px; height: 18px; }
.shanmp3-icon--lg { width: 32px; height: 32px; }

/* =========================================================================
   Modal (accessible dialog)
   ========================================================================= */
.shanmp3-modal {
	display: none;
	position: fixed;
	inset: 0;
	z-index: var(--shanmp3-z-modal);
}
/* Native <dialog> element drives visibility: showModal()/close() toggle the
   `open` attribute, so the modal is shown only when [open]. Forcing display
   on a closed dialog leaks its content onto the page (the search box bug). */
.shanmp3-modal[open] {
	display: block;
}
.shanmp3-modal::backdrop {
	background: rgba(0, 0, 0, 0.6);
	backdrop-filter: blur(2px);
}
.shanmp3-modal__backdrop {
	position: fixed;
	inset: 0;
	background: rgba(0, 0, 0, 0.6);
}
.shanmp3-modal__dialog {
	position: relative;
	margin: auto;
	max-width: 520px;
	width: calc(100% - 2rem);
	max-height: calc(100vh - 2rem);
	overflow: auto;
	padding: 1.5rem;
	background: var(--shanmp3-surface);
	border: 1px solid var(--shanmp3-border);
	border-radius: var(--shanmp3-radius-lg);
	box-shadow: var(--shanmp3-shadow-lg);
}
.shanmp3-modal__head {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 1rem;
	margin-bottom: 1rem;
}
.shanmp3-modal__title {
	margin: 0;
	font-size: 1.25rem;
	font-weight: 700;
	color: var(--shanmp3-fg);
}
.shanmp3-modal__close {
	position: absolute;
	top: 0.75rem;
	inset-inline-end: 0.75rem;
}

/* =========================================================================
   Search modal (magnifier button → pop-up search, on the .shanmp3-modal primitive)
   ========================================================================= */
/* The <dialog> is .shanmp3-modal (open/close/backdrop/Esc handled by
   enhanceModals() via the [data-shanmp3-modal] hook). Reset the native <dialog>
   UA defaults that .shanmp3-modal doesn't touch: the UA gives a dialog a white
   background, a 3px solid black border, shrink-to-fit width, auto margins, and a
   max-height — all of which wrap the card in an undersized white box. Make the
   dialog a transparent, full-viewport stage so only the .shanmp3-search-modal__card
   shows. overflow:hidden clips the absolute suggestion dropdown at the viewport
   edge (the card itself never grows — suggestions are out-of-flow). */
.shanmp3-search-modal {
	margin: 0;
	border: 0;
	background: transparent;
	width: 100%;
	max-width: 100%;
	/* The <dialog> UA style sets height:fit-content, which shrinks the dialog to its
	   content (just the card) and makes `inset:0`'s bottom edge ignored. Without a
	   definite height the dialog is ~150px tall, so overflow:hidden clips the absolute
	   suggestion dropdown that drops below the card — results render but are invisible.
	   Force the dialog to fill the viewport so overflow:hidden clips at the viewport
	   bottom (the original design intent), not at the card's bottom edge. */
	height: 100%;
	max-height: none;
	overflow: hidden;
}
/* When open, center the card near the top of the viewport. (0,2,0) overrides the
   base .shanmp3-modal[open] { display:block }. */
.shanmp3-search-modal[open] {
	display: flex;
	justify-content: center;
	align-items: flex-start;
	padding: 6vh 1rem 1rem;
}
.shanmp3-search-modal__card {
	position: relative;
	width: calc(100% - 2rem);
	max-width: 600px;
	background: var(--shanmp3-surface);
	border: 1px solid var(--shanmp3-border);
	border-radius: var(--shanmp3-radius-lg);
	box-shadow: var(--shanmp3-shadow-lg);
	/* No overflow:hidden — the .shanmp3-search-suggest dropdown is absolutely
	   positioned below the field and must overlay content beyond the card. */
}
.shanmp3-search-modal__head {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 1rem;
	padding: 0.85rem 1.25rem;
	border-bottom: 1px solid var(--shanmp3-border);
}
.shanmp3-search-modal__title {
	margin: 0;
	font-size: 1.05rem;
	font-weight: 700;
	color: var(--shanmp3-fg);
}
.shanmp3-search-modal__close {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	flex: 0 0 auto;
	width: 32px;
	height: 32px;
	padding: 0;
	border: 0;
	border-radius: 50%;
	background: transparent;
	color: var(--shanmp3-muted);
	cursor: pointer;
	transition: color var(--shanmp3-transition-fast),
		background-color var(--shanmp3-transition-fast);
}
.shanmp3-search-modal__close svg {
	width: 20px;
	height: 20px;
}
.shanmp3-search-modal__close:hover,
.shanmp3-search-modal__close:focus-visible {
	background: var(--shanmp3-surface-2);
	color: var(--shanmp3-fg);
}
/* The search form. shanmp3-music.css's .shanmp3-search-bar sets display:flex +
   margin-bottom:1.5rem (for the archive filter bars); cancel the bottom margin
   here so the card's own padding sets the spacing. (0,2,0) beats music.css's
   (0,1,0) even though music.css loads after design.css. The suggestion dropdown
   anchors to the bar via its position:relative (also from shanmp3-music.css). */
.shanmp3-search-modal .shanmp3-search-bar {
	margin-bottom: 0;
	padding: 1rem 1.25rem 1.25rem;
}

/* Mobile: a full-screen panel with a sticky search field and a scrollable results
   list below it, so the on-screen keyboard has room and results stay usable. */
@media (max-width: 600px) {
	.shanmp3-search-modal[open] {
		padding: 0;
		align-items: stretch;
	}
	.shanmp3-search-modal__card {
		display: flex;
		flex-direction: column;
		max-width: none;
		width: 100%;
		min-height: 100vh;
		border-radius: 0;
		border: 0;
	}
	.shanmp3-search-modal__head {
		flex: 0 0 auto;
	}
	/* Stack the field above the results (override music.css's row flex) and let the
	   results panel own the scroll. (0,2,0) beats music.css's .shanmp3-search-bar. */
	.shanmp3-search-modal__bar {
		display: flex;
		flex-direction: column;
		flex: 1 1 auto;
		min-height: 0;
		padding: 0.75rem 1rem 1rem;
	}
	.shanmp3-search-modal__bar .shanmp3-search-field {
		flex: 0 0 auto;
	}
	.shanmp3-search-modal__bar .shanmp3-search-suggest {
		position: static;
		flex: 1 1 auto;
		max-height: none;
		margin-top: 0.5rem;
		border: 0;
		border-radius: 0;
		box-shadow: none;
	}
}

/* =========================================================================
   Dropdown (menu)
   ========================================================================= */
.shanmp3-dropdown {
	position: relative;
	display: inline-block;
}
.shanmp3-dropdown__menu {
	position: absolute;
	inset-inline-start: 0;
	top: calc(100% + 6px);
	z-index: var(--shanmp3-z-dropdown);
	min-width: 200px;
	padding: 0.35rem;
	background: var(--shanmp3-surface);
	border: 1px solid var(--shanmp3-border);
	border-radius: var(--shanmp3-radius);
	box-shadow: var(--shanmp3-shadow-lg);
	/* Clip item hover/focus backgrounds to the menu's rounded corners. The
	   menu radius (12px) is larger than its padding (0.35rem ≈ 5.6px), so
	   without clipping the first/last items' full-width hover rectangle pokes
	   out beyond the rounded corner cutouts (hover "spills outside the card").
	   overflow:hidden clips descendants to the border-radius; the menu's own
	   box-shadow still renders (it is not clipped by the element's overflow).
	   The song-card mobile bottom sheet overrides this with overflow-y:auto at
	   (0,2,0), so it keeps scrolling — only overflow-x stays clipped. */
	overflow: hidden;
	/* Hidden by default; JS toggles [data-open]. */
	display: none;
}
.shanmp3-dropdown[data-open="true"] .shanmp3-dropdown__menu {
	display: block;
}
.shanmp3-dropdown__item {
	display: flex;
	align-items: center;
	gap: 0.6rem;
	width: 100%;
	/* No global *{box-sizing} reset — width:100% is content-box by default, so
	   the padding would push the border-box past the container (body/menu
	   content box) and the hover rect escapes the dialog's rounded corners.
	   border-box keeps width:100% inclusive of the padding. */
	box-sizing: border-box;
	min-height: var(--shanmp3-touch);
	padding: 0.5em 0.75em;
	border: none;
	border-radius: var(--shanmp3-radius-sm);
	background: transparent;
	color: var(--shanmp3-fg);
	font-family: inherit;
	font-size: 0.9rem;
	text-align: start;
	text-decoration: none;
	cursor: pointer;
	transition: background-color var(--shanmp3-transition-fast);
}
.shanmp3-dropdown__item:hover,
.shanmp3-dropdown__item:focus-visible {
	background: var(--shanmp3-surface-2);
}
.shanmp3-dropdown__item--danger {
	color: var(--shanmp3-danger);
}
.shanmp3-dropdown__sep {
	height: 1px;
	margin: 0.35rem 0;
	background: var(--shanmp3-border);
}

/* =========================================================================
   Accessibility: respect reduced motion (disable shimmer + transforms)
   ========================================================================= */
@media (prefers-reduced-motion: reduce) {
	.shanmp3-skeleton::after { animation: none; }
	.shanmp3-btn,
	.shanmp3-card,
	.shanmp3-card__cover {
		transition: none !important;
	}
	/* No hover lift. */
	.shanmp3-card:hover {
		transform: none !important;
	}
	/* Keep the play button centered (translate) but drop the scale/transition. */
	.shanmp3-card__play {
		transition: none !important;
		transform: translate(-50%, -50%) !important;
	}
	/* Playlist & album play buttons are anchored bottom-right (no translate);
	 undo the centered translate above so they stay put under reduced motion. */
	.shanmp3-card--playlist .shanmp3-card__play,
	.shanmp3-card--album .shanmp3-card__play {
		transform: none !important;
	}
	.shanmp3-card:hover .shanmp3-card__play,
	.shanmp3-card:focus-within .shanmp3-card__play {
		opacity: 1;
	}
}
/* =========================================================================
   PWA affordances (Phase 12)
   ========================================================================= */
.shanmp3-pwa-affordance {
	position: fixed;
	right: 1rem;
	bottom: 6rem; /* sit above the persistent player bar */
	z-index: 40;
	display: inline-flex;
	align-items: center;
	gap: 0.4rem;
	padding: 0.5rem 0.9rem;
	border: 1px solid var(--shanmp3-accent, #a16207);
	border-radius: var(--shanmp3-radius-pill, 999px);
	background: var(--shanmp3-surface, #111);
	color: var(--shanmp3-accent, #a16207);
	font-size: var(--shanmp3-text-sm, 0.875rem);
	font-weight: 600;
	cursor: pointer;
	box-shadow: var(--shanmp3-shadow-md, 0 6px 24px rgba(0, 0, 0, 0.35));
}
.shanmp3-pwa-affordance__close {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 1.1rem;
	height: 1.1rem;
	margin-left: 0.2rem;
	border-radius: 50%;
	font-size: 0.9rem;
	line-height: 1;
	color: var(--shanmp3-muted, #888);
	cursor: pointer;
}
.shanmp3-pwa-affordance__close:hover {
	color: var(--shanmp3-text, #fff);
}
.shanmp3-pwa-toast {
	position: fixed;
	left: 50%;
	bottom: 6rem;
	transform: translateX(-50%);
	z-index: 45;
	display: inline-flex;
	align-items: center;
	gap: 0.75rem;
	padding: 0.6rem 0.9rem;
	border-radius: var(--shanmp3-radius-md, 0.5rem);
	background: var(--shanmp3-surface-2, #1c1c1c);
	border: 1px solid var(--shanmp3-border, #222);
	box-shadow: var(--shanmp3-shadow-md, 0 6px 24px rgba(0, 0, 0, 0.35));
}
.shanmp3-pwa-toast__text {
	font-size: var(--shanmp3-text-sm, 0.875rem);
}
@media (max-width: 48rem) {
	.shanmp3-pwa-affordance,
	.shanmp3-pwa-toast {
		bottom: 5.5rem;
	}
}

/* =========================================================================
   WeChat H5 mode
   =========================================================================
   When the page is running inside WeChat's WebView, <html> carries the
   `is-wechat` class (stamped server-side by Shanmp3_Theme_Environment, see
   inc/class-environment.php). These rules make the small targeted adjustments
   the WeChat H5 mode spec calls for — they do NOT redesign the site; the
   visual identity is unchanged. The JS guards in assets/js/islands/pwa.js
   already skip the install prompt in WeChat; this CSS is defense-in-depth so
   the affordance can never show even if the JS path changes. */
/* No PWA install path inside WeChat — never show the install affordance. */
.is-wechat .shanmp3-pwa-affordance--install {
	display: none;
}
/* Honor notched/home-indicator devices for fixed-to-bottom PWA chrome so it
   isn't obscured by the gesture bar (WeChat iOS in particular). */
.shanmp3-pwa-affordance {
	bottom: calc(6rem + env(safe-area-inset-bottom, 0));
}
.shanmp3-pwa-toast {
	bottom: calc(6rem + env(safe-area-inset-bottom, 0));
}
@media (max-width: 48rem) {
	.shanmp3-pwa-affordance,
	.shanmp3-pwa-toast {
		bottom: calc(5.5rem + env(safe-area-inset-bottom, 0));
	}
}

/* =========================================================================
   Offline shell (Phase 12)
   ========================================================================= */
.shanmp3-offline {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 1rem;
	padding: 4rem 1.5rem;
	text-align: center;
}
.shanmp3-offline__title {
	font-size: var(--shanmp3-text-xl, 1.5rem);
	margin: 0;
}
.shanmp3-offline__text {
	color: var(--shanmp3-muted, #888);
	max-width: 36rem;
}
.shanmp3-offline__actions {
	display: flex;
	gap: 0.75rem;
}

/* =========================================================================
   Front-end login page (templates/page-login.html) — split-screen
   ========================================================================= */
/* Full-height two-column grid: a music-brand gradient panel on the left, the
   auth card on the right. Overrides .shanmp3-main's max-width/padding so the
   split spans the viewport below the sticky header. */
.shanmp3-login {
	display: grid;
	grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
	width: 100%;
	max-width: none;
	margin: 0;
	padding: 0;
	min-height: calc(100dvh - var(--shanmp3-header-h, 64px));
}

/* --- Brand panel (left) --- */
.shanmp3-login-brand {
	position: relative;
	display: flex;
	flex-direction: column;
	justify-content: space-between;
	gap: 2rem;
	overflow: hidden;
	padding: clamp(2rem, 5vw, 4rem);
	color: #fff;
	/* Gold → violet, pulled dark enough that white text clears contrast in both
	   themes (theme tokens: --shanmp3-accent-fill gold, --shanmp3-accent-2 violet). */
	background:
		radial-gradient(130% 100% at 15% 0%, rgba(255, 255, 255, 0.14), transparent 55%),
		linear-gradient(160deg, color-mix(in srgb, var(--shanmp3-accent-fill) 78%, #0d0e18) 0%, color-mix(in srgb, var(--shanmp3-accent-2) 85%, #0d0e18) 100%);
}
/* Drifting highlight + shadow shapes behind the content (pure decoration). */
.shanmp3-login-brand__bg {
	position: absolute;
	inset: 0;
	background:
		radial-gradient(circle at 85% 18%, rgba(255, 255, 255, 0.08), transparent 32%),
		radial-gradient(circle at 8% 92%, rgba(0, 0, 0, 0.28), transparent 42%);
	pointer-events: none;
}
.shanmp3-login-brand__content {
	position: relative;
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	gap: 1.15rem;
	max-width: 30rem;
}
/* Vinyl badge: concentric-ring disc with a music note in the centre. */
.shanmp3-login-brand__mark {
	position: relative;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 88px;
	height: 88px;
	margin-bottom: 0.5rem;
}
.shanmp3-login-brand__disc {
	position: absolute;
	inset: 0;
	border-radius: 50%;
	background:
		radial-gradient(circle at 30% 25%, rgba(255, 255, 255, 0.24), transparent 42%),
		linear-gradient(160deg, #0d0e18 0%, #1e2a8a 60%, #3b3fd6 100%);
	box-shadow:
		inset 0 0 0 6px rgba(255, 255, 255, 0.14),
		inset 0 0 0 16px rgba(255, 255, 255, 0.07),
		0 12px 32px rgba(0, 0, 0, 0.35);
}
.shanmp3-login-brand__icon {
	position: relative;
	width: 34px;
	height: 34px;
	color: #fff;
}
.shanmp3-login-brand__title {
	margin: 0;
	font-size: clamp(1.9rem, 3.5vw, 2.7rem);
	line-height: 1.1;
	font-weight: 800;
	letter-spacing: -0.02em;
}
.shanmp3-login-brand__lead {
	margin: 0;
	max-width: 26rem;
	color: rgba(255, 255, 255, 0.82);
	font-size: 1.02rem;
	line-height: 1.55;
}
.shanmp3-login-brand__features {
	display: grid;
	gap: 0.85rem;
	margin: 0.5rem 0 0;
	padding: 0;
	list-style: none;
}
.shanmp3-login-brand__features li {
	display: flex;
	align-items: center;
	gap: 0.7rem;
	font-size: 0.95rem;
	font-weight: 500;
}
.shanmp3-login-brand__feature-icon {
	width: 20px;
	height: 20px;
	flex: 0 0 auto;
	color: var(--shanmp3-accent-fill);
}
/* Animated equalizer bars pinned to the panel's lower edge. */
.shanmp3-login-brand__eq {
	position: relative;
	display: flex;
	align-items: flex-end;
	gap: 6px;
	height: 40px;
	opacity: 0.85;
}
.shanmp3-login-brand__eq span {
	width: 6px;
	border-radius: 3px;
	background: rgba(255, 255, 255, 0.9);
	transform-origin: bottom;
	animation: shanmp3-eq 1.1s ease-in-out infinite;
}
.shanmp3-login-brand__eq span:nth-child(1) { height: 40%; animation-delay: 0s; }
.shanmp3-login-brand__eq span:nth-child(2) { height: 80%; animation-delay: 0.18s; }
.shanmp3-login-brand__eq span:nth-child(3) { height: 55%; animation-delay: 0.36s; }
.shanmp3-login-brand__eq span:nth-child(4) { height: 100%; animation-delay: 0.12s; }
.shanmp3-login-brand__eq span:nth-child(5) { height: 65%; animation-delay: 0.48s; }
@keyframes shanmp3-eq {
	0%, 100% { transform: scaleY(0.35); }
	50% { transform: scaleY(1); }
}

/* --- Auth card (right) --- */
.shanmp3-login-card {
	align-self: center;
	justify-self: center;
	box-sizing: border-box;
	width: min(420px, 100%);
	padding: clamp(1.75rem, 4vw, 2.5rem);
	background: var(--shanmp3-surface);
	border: 1px solid var(--shanmp3-border);
	border-radius: var(--shanmp3-radius-lg);
	box-shadow: var(--shanmp3-shadow-lg);
}
.shanmp3-login-card__head {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 0.35rem;
	margin-bottom: 1.75rem;
	text-align: center;
}
.shanmp3-login-card__icon-wrap {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 52px;
	height: 52px;
	margin-bottom: 0.85rem;
	border-radius: 50%;
	background: color-mix(in srgb, var(--shanmp3-accent) 14%, transparent);
}
.shanmp3-login-card__icon {
	width: 24px;
	height: 24px;
	color: var(--shanmp3-accent);
}
.shanmp3-login-card__title {
	margin: 0;
	font-size: 1.45rem;
	font-weight: 800;
	letter-spacing: -0.01em;
}
.shanmp3-login-card__subtitle {
	margin: 0;
	color: var(--shanmp3-muted);
	font-size: 0.9rem;
}
.shanmp3-login-card__form {
	gap: 1.1rem;
}
/* Icon-inlaid inputs: leading icon on the left, the password toggle on the right. */
.shanmp3-login-card__control {
	position: relative;
}
.shanmp3-login-card__control .shanmp3-input {
	padding-left: 2.75rem;
}
.shanmp3-login-card__control--password .shanmp3-input {
	padding-right: 2.9rem;
}
.shanmp3-login-card__control-icon {
	position: absolute;
	top: 50%;
	left: 0.9rem;
	transform: translateY(-50%);
	width: 20px;
	height: 20px;
	color: var(--shanmp3-muted);
	pointer-events: none;
}
.shanmp3-login-card__pw-toggle {
	position: absolute;
	top: 50%;
	right: 0.35rem;
	transform: translateY(-50%);
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 40px;
	height: 40px;
	padding: 0;
	border: 0;
	border-radius: var(--shanmp3-radius-pill);
	background: transparent;
	color: var(--shanmp3-muted);
	cursor: pointer;
	transition: color var(--shanmp3-transition-fast),
		background-color var(--shanmp3-transition-fast);
}
.shanmp3-login-card__pw-toggle:hover {
	color: var(--shanmp3-fg);
	background: var(--shanmp3-surface-2);
}
.shanmp3-login-card__pw-eye,
.shanmp3-login-card__pw-eye-off {
	width: 20px;
	height: 20px;
}
.shanmp3-login-card__pw-eye-off { display: none; }
.shanmp3-login-card__pw-toggle[aria-pressed="true"] .shanmp3-login-card__pw-eye { display: none; }
.shanmp3-login-card__pw-toggle[aria-pressed="true"] .shanmp3-login-card__pw-eye-off { display: inline-block; }

.shanmp3-login-card__row {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 1rem;
	flex-wrap: wrap;
}
.shanmp3-login-card__row .shanmp3-checkbox {
	min-height: auto;
	font-size: 0.88rem;
}

.shanmp3-login__link {
	font-size: 0.85rem;
	color: var(--shanmp3-accent);
	text-decoration: none;
	font-weight: 600;
}
.shanmp3-login__link:hover {
	text-decoration: underline;
}

/* Submit button: label + arrow, with a spinner that swaps in while submitting. */
.shanmp3-login-card__submit {
	position: relative;
}
.shanmp3-login-card__submit-arrow {
	width: 20px;
	height: 20px;
	transition: transform var(--shanmp3-transition-fast);
}
.shanmp3-login-card__submit:hover .shanmp3-login-card__submit-arrow {
	transform: translateX(3px);
}
.shanmp3-login-card__spinner {
	display: none;
	width: 18px;
	height: 18px;
	border: 2px solid rgba(255, 255, 255, 0.35);
	border-top-color: #fff;
	border-radius: 50%;
	animation: shanmp3-spin 0.7s linear infinite;
}
.shanmp3-login-card__form.is-submitting .shanmp3-login-card__submit-arrow {
	display: none;
}
.shanmp3-login-card__form.is-submitting .shanmp3-login-card__spinner {
	display: inline-block;
}
@keyframes shanmp3-spin {
	to { transform: rotate(360deg); }
}

/* Register / secondary links row. */
.shanmp3-login__links {
	display: flex;
	align-items: center;
	justify-content: center;
	gap: 0.75rem;
	flex-wrap: wrap;
	margin-top: 1.5rem;
	padding-top: 1.5rem;
	border-top: 1px solid var(--shanmp3-border);
}
.shanmp3-login__links-text {
	font-size: 0.9rem;
	color: var(--shanmp3-muted);
}

/* Social-login divider + provider buttons. */
.shanmp3-login__divider {
	display: flex;
	align-items: center;
	gap: 0.85rem;
	margin: 1.4rem 0 1rem;
	color: var(--shanmp3-muted);
	font-size: 0.8rem;
	text-transform: uppercase;
	letter-spacing: 0.06em;
}
.shanmp3-login__divider::before,
.shanmp3-login__divider::after {
	content: "";
	flex: 1 1 auto;
	height: 1px;
	background: var(--shanmp3-border);
}
.shanmp3-login__social {
	display: flex;
	flex-direction: column;
	gap: 0.6rem;
}
.shanmp3-login__social-btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: 0.6rem;
	width: 100%;
	padding: 0.7rem 1rem;
	border-radius: var(--shanmp3-radius);
	border: 1px solid var(--shanmp3-border);
	background: var(--shanmp3-surface);
	color: var(--shanmp3-fg);
	font-size: 0.95rem;
	font-weight: 600;
	text-decoration: none;
	transition: border-color 0.15s ease, background 0.15s ease, transform 0.05s ease;
}
.shanmp3-login__social-btn:hover {
	border-color: var(--shanmp3-border-strong, var(--shanmp3-border));
	background: var(--shanmp3-surface-2, var(--shanmp3-surface));
}
.shanmp3-login__social-btn:active {
	transform: translateY(1px);
}
.shanmp3-login__social-btn:focus-visible {
	outline: 2px solid var(--shanmp3-accent);
	outline-offset: 2px;
}
.shanmp3-login__social-icon {
	flex: 0 0 auto;
	width: 18px;
	height: 18px;
}
/* Provider accents on the icon swatch only — keep the button neutral so the
   set reads as one group. */
.shanmp3-login__social-btn--google:hover { border-color: #4285F4; }
.shanmp3-login__social-btn--facebook:hover { border-color: #1877F2; }
.shanmp3-login__social-btn--wechat:hover { border-color: #07C160; }

/* Notices (error / logged-out). */
.shanmp3-login__error,
.shanmp3-login__notice {
	display: flex;
	align-items: center;
	gap: 0.5rem;
	margin: 0 0 1.25rem;
	padding: 0.7rem 0.9rem;
	border-radius: var(--shanmp3-radius);
	font-size: 0.88rem;
	line-height: 1.4;
}
.shanmp3-login__error {
	background: color-mix(in srgb, var(--shanmp3-danger) 14%, transparent);
	color: var(--shanmp3-danger);
	border: 1px solid color-mix(in srgb, var(--shanmp3-danger) 30%, transparent);
}
.shanmp3-login__error-icon {
	width: 18px;
	height: 18px;
	flex: 0 0 auto;
}
.shanmp3-login__notice {
	background: var(--shanmp3-surface-2);
	color: var(--shanmp3-fg);
	border: 1px solid var(--shanmp3-border);
}

/* "You're already logged in" variant. */
.shanmp3-login-card--done {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 1rem;
	padding: clamp(2rem, 4vw, 3rem);
	text-align: center;
}
.shanmp3-login-card--done .shanmp3-login-card__title {
	color: var(--shanmp3-fg);
	font-size: 1.45rem;
}
.shanmp3-login-card--done .shanmp3-login-card__text {
	color: var(--shanmp3-muted);
	margin: 0;
	max-width: 24rem;
}

@media (max-width: 48rem) {
	.shanmp3-login {
		/* Single column: the brand panel drops out and the form fills the viewport. */
		grid-template-columns: 1fr;
		min-height: auto;
		align-items: start;
		/* Give the card a side frame so it isn't flush with the screen edges. */
		padding: 0 1.25rem;
		box-sizing: border-box;
	}
	/* Hide the decorative brand panel entirely on mobile — only the form shows. */
	.shanmp3-login-brand {
		display: none;
	}
	.shanmp3-login-card {
		width: min(420px, 100%);
		margin: 2rem auto 3.5rem;
	}
}

@media (prefers-reduced-motion: reduce) {
	.shanmp3-login-brand__eq span,
	.shanmp3-login-card__spinner {
		animation: none;
	}
	.shanmp3-login-card__submit:hover .shanmp3-login-card__submit-arrow {
		transform: none;
	}
}

/* =========================================================================
   Profile account details (templates/page-profile.html)
   ========================================================================= */
.shanmp3-profile__account {
	margin-top: 1.5rem;
	padding: 1.5rem;
	background: var(--shanmp3-surface);
	border: 1px solid var(--shanmp3-border);
	border-radius: var(--shanmp3-radius-lg);
}
.shanmp3-profile__account-title {
	margin: 0 0 1rem;
	font-size: 1.05rem;
	font-weight: 700;
	color: var(--shanmp3-fg);
}
.shanmp3-profile__account-list {
	display: grid;
	gap: 0;
	margin: 0;
}
.shanmp3-profile__account-row {
	display: flex;
	align-items: baseline;
	justify-content: space-between;
	gap: 1rem;
	padding: 0.75rem 0;
	border-top: 1px solid var(--shanmp3-border);
}
.shanmp3-profile__account-row:first-child {
	border-top: 0;
}
.shanmp3-profile__account-label {
	margin: 0;
	font-size: 0.85rem;
	color: var(--shanmp3-muted);
	font-weight: 500;
}
.shanmp3-profile__account-value {
	margin: 0;
	font-size: 0.95rem;
	color: var(--shanmp3-fg);
	font-weight: 600;
	word-break: break-word;
	text-align: right;
}
@media (max-width: 30rem) {
	.shanmp3-profile__account-row {
		flex-direction: column;
		gap: 0.15rem;
	}
	.shanmp3-profile__account-value {
		text-align: left;
	}
}

/* =========================================================================
   Profile quick-link cards → /library/
   ========================================================================= */
.shanmp3-quicklinks {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
	gap: 0.75rem;
}
.shanmp3-quicklink {
	display: flex;
	align-items: center;
	gap: 0.85rem;
	padding: 1rem 1.1rem;
	background: var(--shanmp3-surface);
	border: 1px solid var(--shanmp3-border);
	border-radius: var(--shanmp3-radius);
	color: var(--shanmp3-fg);
	text-decoration: none;
	font-weight: 600;
	transition: border-color var(--shanmp3-transition-fast),
		background-color var(--shanmp3-transition-fast),
		transform var(--shanmp3-transition-fast);
}
.shanmp3-quicklink:hover {
	border-color: var(--shanmp3-accent);
	background: var(--shanmp3-surface-2);
	transform: translateY(-2px);
}
.shanmp3-quicklink__icon {
	flex: 0 0 auto;
	width: 38px;
	height: 38px;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	border-radius: 50%;
	background: linear-gradient(160deg, #1e2a8a, #5356ff);
	color: #fff;
	font-size: 1.1rem;
	line-height: 1;
}
.shanmp3-quicklink__label {
	flex: 1 1 auto;
}
.shanmp3-quicklink__arrow {
	flex: 0 0 auto;
	color: var(--shanmp3-muted);
	transition: transform var(--shanmp3-transition-fast);
}
.shanmp3-quicklink:hover .shanmp3-quicklink__arrow {
	transform: translateX(3px);
	color: var(--shanmp3-accent);
}

/* =========================================================================
   Artist analytics (Phase 08) — period selector, stat cards, SVG chart
   ========================================================================= */
.shanmp3-analytics {
	display: flex;
	flex-direction: column;
	gap: 1rem;
}
.shanmp3-analytics__period {
	display: inline-flex;
	gap: 0.25rem;
	background: var(--shanmp3-surface-2);
	border-radius: var(--shanmp3-radius-pill);
	padding: 0.25rem;
	align-self: flex-start;
}
.shanmp3-analytics__tab {
	border: 0;
	background: transparent;
	color: var(--shanmp3-muted);
	font: inherit;
	font-size: 0.85rem;
	font-weight: 600;
	padding: 0.4em 1em;
	border-radius: var(--shanmp3-radius-pill);
	cursor: pointer;
	transition: background var(--shanmp3-transition-fast), color var(--shanmp3-transition-fast);
}
.shanmp3-analytics__tab.is-active {
	background: var(--shanmp3-accent);
	color: #fff;
}
.shanmp3-analytics__loading,
.shanmp3-analytics__error {
	display: inline-flex;
	align-items: center;
	gap: 0.4rem;
	color: var(--shanmp3-muted);
	font-size: 0.9rem;
	margin: 0;
}
.shanmp3-analytics__error {
	color: #c0392b;
}
.shanmp3-analytics__cards {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
	gap: 0.75rem;
}
.shanmp3-analytics__card {
	display: flex;
	flex-direction: column;
	gap: 0.2rem;
	padding: 0.9rem 1rem;
	background: var(--shanmp3-surface-2);
	border-radius: var(--shanmp3-radius-lg);
}
.shanmp3-analytics__card-icon {
	color: var(--shanmp3-accent);
}
.shanmp3-analytics__card-value {
	font-size: 1.5rem;
	font-weight: 700;
	line-height: 1.1;
	color: var(--shanmp3-fg);
}
.shanmp3-analytics__card-label {
	font-size: 0.8rem;
	color: var(--shanmp3-muted);
}
.shanmp3-analytics__top {
	margin-top: 0.5rem;
}
.shanmp3-analytics__top-title,
.shanmp3-analytics__chart-title {
	margin: 0 0 0.5rem;
	font-size: 1rem;
	font-weight: 600;
	color: var(--shanmp3-fg);
}
.shanmp3-analytics__top-list {
	margin: 0;
	padding: 0;
	list-style: none;
	display: flex;
	flex-direction: column;
	gap: 0.3rem;
}
.shanmp3-analytics__top-list li {
	display: flex;
	justify-content: space-between;
	gap: 1rem;
	font-size: 0.9rem;
}
.shanmp3-analytics__top-name {
	color: var(--shanmp3-fg);
}
.shanmp3-analytics__top-plays {
	color: var(--shanmp3-muted);
	font-variant-numeric: tabular-nums;
}
.shanmp3-analytics__svg {
	width: 100%;
	height: 160px;
	display: block;
}
.shanmp3-analytics__svg rect {
	fill: var(--shanmp3-accent);
}

/* -----------------------------------------------------------------
 * Advertising — AdSlot (Phase 9)
 * Clearly-identified ad placement. Distinct background + dashed border
 * so it is never indistinguishable from organic/editorial content. A
 * min-height reserves space and avoids layout shift; the image is lazy-
 * loaded. Ads are Free-members-only (server-gated) and never appear in
 * the core music player — this block lives in page sections only.
 * --------------------------------------------------------------- */
.shanmp3-ad-slot {
	display: flex;
	flex-direction: column;
	gap: 0.5rem;
	padding: 0.75rem;
	background: var(--shanmp3-surface-2);
	border: 1px dashed var(--shanmp3-border);
	border-radius: var(--shanmp3-radius-lg);
	/* Fluid banner aspect ratio: the creative box keeps a deliberate shape
	 * that scales with the container width (short on a phone, wider on a
	 * desktop) instead of a fixed 220px everywhere. Tweaked per breakpoint
	 * below. aspect-ratio also reserves the box before the lazy image loads,
	 * so no extra min-height is needed to prevent layout shift. */
	--shanmp3-ad-ratio: 2 / 1;
	min-height: 80px;
}
.shanmp3-ad-slot__label {
	display: inline-flex;
	align-items: center;
	gap: 0.3em;
	font-size: 0.7rem;
	letter-spacing: 0.04em;
	text-transform: uppercase;
	color: var(--shanmp3-muted);
}
.shanmp3-ad-slot__link {
	display: block;
	border-radius: var(--shanmp3-radius-md, var(--shanmp3-radius-lg));
	overflow: hidden;
}
.shanmp3-ad-slot__link:focus-visible {
	outline: 2px solid var(--shanmp3-accent);
	outline-offset: 2px;
}
.shanmp3-ad-slot__img {
	display: block;
	width: 100%;
	/* Definite, fluid height from aspect-ratio lets object-fit:cover actually
	 * crop the creative into the banner shape (with the old height:auto it was
	 * a no-op, and max-height:220px squished tall creatives). The box scales
	 * with the container, so the ad is genuinely responsive. */
	height: auto;
	aspect-ratio: var(--shanmp3-ad-ratio, 2 / 1);
	object-fit: cover;
}
.shanmp3-ad-slot__placeholder {
	display: flex;
	width: 100%;
	min-height: 80px;
	aspect-ratio: var(--shanmp3-ad-ratio, 2 / 1);
	align-items: center;
	justify-content: center;
	color: var(--shanmp3-muted);
	background: var(--shanmp3-surface);
	border-radius: var(--shanmp3-radius-md, var(--shanmp3-radius-lg));
}

/* Ad banner shape adapts to the viewport: a touch taller on phones so the
 * creative stays legible, flattening to a short leaderboard on desktop. */
@media (min-width: 600px) {
	.shanmp3-ad-slot {
		--shanmp3-ad-ratio: 3 / 1;
	}
}
@media (min-width: 768px) {
	.shanmp3-ad-slot {
		--shanmp3-ad-ratio: 4 / 1;
	}
}
@media (min-width: 1024px) {
	.shanmp3-ad-slot {
		--shanmp3-ad-ratio: 5 / 1;
	}
}

/* =========================================================================
 * Support / Contact Admin — shanmp3/support-contact block
 * Flat / borderless: a head row (icon disc + title + one line) and a list of
 * hairline-divided channel rows (icon · label · action). No card box.
 * ====================================================================== */
.shanmp3-support {
	display: flex;
	flex-direction: column;
	gap: var(--shanmp3-gap, 16px);
}
.shanmp3-support__head {
	display: flex;
	align-items: flex-start;
	gap: var(--shanmp3-gap, 16px);
}
.shanmp3-support__icon {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 44px;
	height: 44px;
	flex: 0 0 auto;
	border-radius: 50%;
	background: var(--shanmp3-accent);
	color: #fff;
}
.shanmp3-support__icon .shanmp3-icon,
.shanmp3-support__icon-svg {
	width: 22px;
	height: 22px;
}
.shanmp3-support__body {
	flex: 1 1 0;
	min-width: 0;
}
.shanmp3-support__title {
	margin: 0 0 4px;
	font-size: 1.05rem;
	font-weight: 700;
	color: var(--shanmp3-text);
}
.shanmp3-support__text {
	margin: 0;
	font-size: 0.95rem;
	color: var(--shanmp3-muted);
}
.shanmp3-support__channels {
	list-style: none;
	margin: 0;
	padding: 0;
}
.shanmp3-support__channel {
	display: flex;
	align-items: center;
	gap: 12px;
	padding: 14px 0;
	border-bottom: 1px solid var(--shanmp3-border);
	color: var(--shanmp3-text);
	text-decoration: none;
}
.shanmp3-support__channels li:last-child .shanmp3-support__channel {
	border-bottom: none;
}
.shanmp3-support__channel:hover,
.shanmp3-support__channel:focus-visible {
	color: var(--shanmp3-accent);
}
.shanmp3-support__channel-icon {
	display: inline-flex;
	flex: 0 0 auto;
	color: var(--shanmp3-muted);
}
.shanmp3-support__channel:hover .shanmp3-support__channel-icon,
.shanmp3-support__channel:focus-visible .shanmp3-support__channel-icon {
	color: var(--shanmp3-accent);
}
.shanmp3-support__channel-icon-svg {
	width: 20px;
	height: 20px;
}
.shanmp3-support__channel-label {
	flex: 1 1 0;
	min-width: 0;
	font-weight: 600;
}
.shanmp3-support__channel-action {
	flex: 0 0 auto;
	font-size: 0.9rem;
	color: var(--shanmp3-muted);
}
.shanmp3-support__channel:hover .shanmp3-support__channel-action,
.shanmp3-support__channel:focus-visible .shanmp3-support__channel-action {
	color: var(--shanmp3-accent);
}
.shanmp3-support--unavailable {
	opacity: 0.85;
}
.shanmp3-support--unavailable .shanmp3-support__icon {
	background: var(--shanmp3-muted);
}

/* Flat intro on the /support/ page (no box, just type). */
.shanmp3-support-page__intro {
	margin: 0 0 var(--shanmp3-gap, 16px);
}
.shanmp3-support-page__title {
	margin: 0 0 8px;
	font-size: 2rem;
	font-weight: 800;
	color: var(--shanmp3-text);
}
.shanmp3-support-page__lead {
	margin: 0;
	max-width: 60ch;
	font-size: 1.05rem;
	color: var(--shanmp3-muted);
}

/* Wrapper around the support block when placed under the login form. */
.shanmp3-login-support {
	width: 100%;
	max-width: 420px;
	margin: 16px auto 0;
}
.shanmp3-section--support {
	margin-top: 32px;
}

/* =========================================================================
 * Footer — now just the hidden PWA bootstrap marker (the legal/support links
 * moved into the settings popup on the app sidebar). Neutralize core's 24px
 * block margin on .wp-site-blocks > * so the empty footer adds no scroll.
 * ====================================================================== */
.shanmp3-footer {
	margin: 0;
	border-top: 1px solid var(--shanmp3-border);
}
/* Legal/info link row — a centered set of pill chips (icon + label). Lean by
   design: this polishes the plain muted link row, it does NOT re-introduce the
   fuller brand + support-card footer removed earlier at the owner's request.
   Each chip sits on surface-2 with a hairline border; hover/focus lifts it to
   the gold accent so the four pages read as a deliberate, scannable set. */
.shanmp3-footer__legal {
	display: flex;
	flex-wrap: wrap;
	justify-content: center;
	align-items: center;
	gap: 0.375rem;
	max-width: 1200px;
	margin: 0 auto;
	padding: 0.625rem 1rem;
}
.shanmp3-footer__legal-link {
	display: inline-flex;
	align-items: center;
	gap: 0.35rem;
	padding: 0.25rem 0.65rem;
	border: 1px solid var(--shanmp3-border);
	border-radius: var(--shanmp3-radius-pill);
	background: var(--shanmp3-surface-2);
	color: var(--shanmp3-muted);
	font-size: 0.75rem;
	font-weight: 500;
	text-decoration: none;
	transition: color var(--shanmp3-transition-fast),
		border-color var(--shanmp3-transition-fast),
		background-color var(--shanmp3-transition-fast);
}
.shanmp3-footer__legal-icon {
	width: 13px;
	height: 13px;
	opacity: 0.85;
	flex: 0 0 auto;
}
.shanmp3-footer__legal-link:hover,
.shanmp3-footer__legal-link:focus-visible {
	color: var(--shanmp3-accent);
	border-color: var(--shanmp3-accent);
	background: var(--shanmp3-surface);
}
.shanmp3-footer__legal-link:focus-visible {
	outline: none;
	box-shadow: var(--shanmp3-shadow-focus);
}
@media (prefers-reduced-motion: reduce) {
	.shanmp3-footer__legal-link {
		transition: none;
	}
}

/* =========================================================================
 * Announcements — shanmp3/announcements feed + page chrome
 * ====================================================================== */
.shanmp3-announcements-page__intro {
	margin-bottom: 24px;
}
.shanmp3-announcements-page__feed-head {
	margin: 8px 0 16px;
	border-bottom: 1px solid var(--shanmp3-border);
	padding-bottom: 8px;
}
.shanmp3-announcements-page__feed-title {
	margin: 0;
	font-size: 1.25rem;
	font-weight: 700;
	color: var(--shanmp3-text);
}
.shanmp3-announcements__list {
	list-style: none;
	margin: 0;
	padding: 0;
	display: flex;
	flex-direction: column;
	gap: 4px;
}
.shanmp3-announcements__item {
	border-bottom: 1px solid var(--shanmp3-border);
}
.shanmp3-announcements__item:last-child {
	border-bottom: none;
}
.shanmp3-announcements__item-link {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: 6px 16px;
	padding: 14px 4px;
	text-decoration: none;
}
.shanmp3-announcements__item-link:hover,
.shanmp3-announcements__item-link:focus-visible {
	background: var(--shanmp3-surface-2);
}
.shanmp3-announcements__date {
	flex: 0 0 auto;
	font-size: 0.8rem;
	color: var(--shanmp3-muted);
	white-space: nowrap;
}
.shanmp3-announcements__title {
	flex: 1 1 100%;
	font-weight: 600;
	color: var(--shanmp3-text);
}
.shanmp3-announcements__item-link:hover .shanmp3-announcements__title,
.shanmp3-announcements__item-link:focus-visible .shanmp3-announcements__title {
	color: var(--shanmp3-accent);
}
@media (min-width: 600px) {
	.shanmp3-announcements__title {
		flex: 1 1 auto;
	}
}
.shanmp3-announcements--empty,
.shanmp3-announcements--empty p {
	margin: 0;
	padding: 24px 0;
	color: var(--shanmp3-muted);
}

/* =========================================================================
 * Admin: pending-approval badge (Users list table "Status" column)
 * ====================================================================== */
.shanmp3-badge {
	display: inline-block;
	padding: 2px 10px;
	border-radius: 999px;
	font-size: 0.78rem;
	font-weight: 600;
	line-height: 1.6;
}
.shanmp3-badge--pending {
	background: #fff3cd;
	color: #856404;
}
.shanmp3-badge--active {
	background: #d4edda;
	color: #155724;
}

/* =========================================================================
   Screen-reader-only utility
   ========================================================================= */
.shanmp3-sr-only {
	position: absolute;
	width: 1px;
	height: 1px;
	padding: 0;
	margin: -1px;
	overflow: hidden;
	clip: rect(0, 0, 0, 0);
	white-space: nowrap;
	border: 0;
}

/* =========================================================================
   Soft-navigation loading indicator
   -------------------------------------------------------------------------
   While a soft-nav fetch is in flight, soft-nav.js stamps
   <html class="shanmp3-nav-loading">. A thin accent bar indeterminately
   sweeps across the very top of the viewport so the user sees that the click
   registered (the persistent player keeps playing, so without this there'd be
   no visible feedback until the swap completes). Fixed + high z so it sits
   above the sticky header and admin bar.
   ========================================================================= */
html.shanmp3-nav-loading::before {
	content: '';
	position: fixed;
	inset: 0 0 auto 0;
	height: 3px;
	z-index: 100000;
	background: var(--shanmp3-accent);
	transform-origin: 0 50%;
	animation: shanmp3-nav-load 1s ease-in-out infinite;
}
@keyframes shanmp3-nav-load {
	0%   { transform: scaleX(0); }
	50%  { transform: scaleX(0.7); }
	100% { transform: scaleX(1); }
}
@media (prefers-reduced-motion: reduce) {
	html.shanmp3-nav-loading::before {
		animation: none;
		transform: scaleX(1);
		opacity: 0.6;
	}
}
