:root {
    /* "Vault" — same palette as the theme-vault-default doc every new tenant is seeded
       with (workers/provisioning) and DV's own site. Only used before that theme's fetch
       resolves, or if it's ever missing entirely — applyTheme()/applyDvTheme() overwrite
       these at runtime with whatever's actually configured. */
    --bg: #0b0b0d;
    --surface: rgba(138, 138, 143, 0.06);
    --surface-hover: rgba(138, 138, 143, 0.1);
    --border: rgba(138, 138, 143, 0.18);
    --text: #f2f2f0;
    --subheading: #a8a8ad;
    --body-text: #c9c9cc;
    --accent: #c9a24b;
    --accent-text: #f2f2f0;
    /* Derived from --accent itself (relative color, not a fresh hex) so hover/active always
       read as a real shift even when a theme doesn't supply its own accentHover/accentActive --
       most real tenant themes don't (confirmed 2026-09-27: 4 of 5 on dvtenant left these null,
       which made hover/active collapse to plain --accent with no visible state change at all).
       JS still overwrites these when a theme *does* supply real values (Ivory does), since an
       inline style on :root always wins over this stylesheet default. */
    --accent-hover: var(--accent);
    --accent-active: var(--accent);
    /* Matches dv-style.css's fallback exactly -- missing entirely before this port
       (2026-09-02), so .empty-state--error/.pcf-status--error had no real color. */
    --danger: #c94b4b;
    /* Also missing entirely before this port -- used in 5+ places already in this file with
       no fallback, resolving to nothing/black. Same value dv-style.css and --subheading here
       already both use; not theme-driven (never set dynamically in either app.js). */
    --muted-text: #a8a8ad;
    --display: Arial, -apple-system, BlinkMacSystemFont, sans-serif;
    /* Accessibility audit (2026-09-22, matching band-website-project's own fix) -- a visible
       focus ring, theme-aware since it's derived from --accent (which applyTheme() overwrites
       at runtime). Used wherever a focus style previously just removed the outline with no
       real replacement. */
    --focus-ring: 0 0 0 3px color-mix(in srgb, var(--accent) 45%, transparent);
}

* {
    box-sizing: border-box;
}

/* The one fixed anchor point in the whole file -- everything else, including body itself, is
   em from here down. 20px, matching the established base this whole file's em values are
   already written against (CSS-UI-STANDARDS.md/WORKFLOW.md: "1em = 20px"). Without this, body's
   own em would resolve against the browser's uncontrolled default instead (usually 16px, not
   guaranteed) -- pinning it here is what actually fixes the 2026-09-09 regression, not
   abandoning em. Matches dv-style.css's own copy of this fix (2026-09-10). */
html {
    font-size: 18px;
}

body {
    margin: 0;
    font-size: 1.3333334em;
    font-family: var(--font-body, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif);
    font-weight: 300;
    background: var(--bg);
    color: var(--text);
    line-height: 2;
}

a {
    color: inherit;
    text-decoration: none;
}

/* Header/logo + nav — .site-bar wraps both in the HTML, stacked as two rows: a big centered
   logo row on top, a thin nav row underneath with an accent underline stripe. */
.site-bar {
    display: flex;
    flex-direction: column;
    margin: 0;
}

.site-header {
    display: flex;
    justify-content: center;
    background: #000;
    padding: 20px 0;
}

.site-logo {
    height: 250px;
    width: auto;
    display: block;
}

/* Height reserved unconditionally (not toggled via JS or :empty) so the bar never pops in
   after first paint and shifts everything below it down — confirmed as a real mis-click
   cause in testing 2026-08-23: a click aimed at the Log out button's pre-shift position
   landed on the wrong element once the bar populated a moment later. */
.site-auth-status {
    min-height: 39px;
    text-align: center;
    padding: 10px 16px;
    margin: 0 0 16px;
    color: var(--body-text);
    background: var(--surface);
    border-bottom: 1px solid var(--border);
    box-sizing: border-box;
}

.site-auth-status a {
    color: var(--accent);
}

/* Nav — its own row below the big logo, centered, with an accent underline stripe. Site
   name and nav links stack (name on top, links below), not side by side. */
.site-nav {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
    padding: 18px 64px;
    border-bottom: 3px solid var(--accent);
}

.site-nav .brand {
    font-family: var(--display);
    letter-spacing: 0.02em;
    font-size: 0.65em;
    text-transform: uppercase;
}

.site-nav .links {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 24px;
}

/* Mobile nav overflow (found in a responsiveness audit, 2026-09-22) -- .site-nav's fixed
   64px side padding alone ate ~128px of a 375px phone viewport, and .links had no wrap, so
   the nav items ran off both edges instead of fitting. No hamburger/drawer menu exists in
   this nav at all, so the fix is CSS-only: shrink the side padding and let links wrap onto a
   second line (already centered) instead of overflowing. */
@media (max-width: 640px) {
    .site-nav {
        padding: 18px 16px;
    }
    .site-nav .links {
        gap: 12px 20px;
    }
}

.site-nav .links a {
    color: var(--body-text);
    font-size: 0.65em;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    transition: color 0.15s ease;
}

.site-nav .links a:hover,
.site-nav .links a:focus-visible,
.site-nav .links a.active {
    color: var(--accent);
}

/* Skip-to-content link (accessibility audit, 2026-09-22, matching band-website-project's own
   fix) -- visually hidden off-screen until keyboard-focused, then jumps a screen-reader/
   keyboard user straight past the header/nav to #main-content. */
.skip-link {
    position: absolute;
    left: -9999px;
    top: 0;
    z-index: 1000;
    background: var(--accent);
    color: var(--accent-text);
    padding: 12px 20px;
    border-radius: 0 0 8px 0;
    font-size: 0.7em;
    text-decoration: none;
}

.skip-link:focus {
    left: 0;
}

/* Turnstile widget (signup/login/contact) -- centered under whatever form it follows, same
   treatment everywhere it appears. */
.cf-turnstile {
    display: flex;
    justify-content: center;
    margin-top: 20px;
}

/* Layout */
main {
    max-width: var(--content-max-width, 880px);
    margin: 0 auto;
    padding: 48px 24px 96px;
}

/* Pages that open with the full-width slideshow (home, custom pages): no top gap between the
   nav and the slideshow (owner, 2026-10-10). Only while the slideshow is actually showing --
   a hidden one (style="display:none") keeps the normal spacing. */
main:has(> .hero-slider-bleed:first-child > .hero-slider:not([style*="none"])) {
    padding-top: 0;
}

/* Custom pages only (page.html) -- the actual content wrapper inside <main>, nested padding on
   top of main's own (intentional, matches DV's real page exactly). Missing entirely before
   this port (2026-09-02). */
.page-content {
    padding: 30px 60px 60px;
}

@media (max-width: 1100px) {
    .page-content {
        padding: 30px 10px 60px;
    }
}

h1 {
    font-family: var(--display);
    font-weight: 300;
    font-size: 2em;
    /* --accent-active is only set by JS when a tenant's theme doc has that optional field
       (app.js's applyTheme) -- most existing tenants haven't set it, so this falls back to
       the always-defined --accent instead of resolving to nothing (found 2026-09-09, before
       this shipped anywhere -- would have made titles invisible on most tenant sites). */
    color: var(--accent-active, var(--accent));
    text-transform: lowercase;
    margin: 0 0 8px;
}

.page-subtitle {
    color: var(--body-text);
    margin: 0 0 40px;
}

/* Listen page hero banner (frontend/listen.html + listen.js) -- title/subtitle overlaid on an
   optional background image, ported from the reference band-website-project site's own
   .hero-section/.hero-image-wrapper/.hero-content pattern (same field data: title, subtitle,
   heroImage). Deliberately a separate class family from custom-page.js's .hero-slider/
   .hero-slide -- that engine has no text-overlay capability at all (its page title renders as
   plain .page-title text below the image, not on top of it), so reusing it here would have
   meant losing the overlay entirely. Works with no image too -- .hero-banner-image-wrap's img
   stays hidden (listen.js) and the banner just shows centered text on --bg. */
.hero-banner {
    position: relative;
    padding: 60px 20px;
    text-align: center;
    background: var(--bg);
    overflow: hidden;
    min-height: 270px;
    display: flex;
    align-items: center;
    justify-content: center;
    margin: 0 0 30px;
    border-radius: 8px;
}

.hero-banner-image-wrap {
    position: absolute;
    inset: 0;
    z-index: 0;
    border-radius: 8px;
    overflow: hidden;
}

.hero-banner-image-wrap img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

.hero-banner-content {
    position: relative;
    z-index: 1;
    min-width: 0;
    max-width: 100%;
}

.hero-banner-title {
    font-size: 2em;
    margin: 0;
    color: var(--text);
    overflow-wrap: break-word;
}

.hero-banner-subtitle {
    font-size: 1em;
    color: var(--body-text);
    margin: 10px 0 0;
    overflow-wrap: break-word;
}

/* Sub-headings within page content (Pricing/Bulk Search's "What's included", "Add-ons", etc).
   Plain element selector, not a class -- every h2 inside .page-content gets this
   automatically, no need to remember to tag each one. Same visual family as .page-title --
   var(--display) (Arial), lowercase, accent color -- just smaller since it's a section
   heading, not the page's own title. Not ".section-heading" -- that class name is already
   taken by studio.html's unrelated Studio-panel Label styling (frontend/studio-app.js), scoped
   to #studio-root. */
.page-content h2 {
    font-family: var(--display);
    font-weight: 300;
    font-size: 1em;
    color: var(--accent-active, var(--accent));
    text-transform: lowercase;
    margin: 0 0 12px;
}

#about-content p {
    color: var(--body-text);
    line-height: 1.6;
}

/* Content box — optional tenant-editable text above the discography listing
   (discographyPageContent.contentBox in the Studio). Hidden entirely when unset. */
/* Matches dv-style.css's real recipe exactly (owner direction 2026-09-02: "use dv-main's page
   css") -- box-shadow, no border. Previously diverged (border-based) for no recorded reason;
   this affects the discography page's own content-box too, not just custom pages, per the
   same "consistent across every tenant" direction as the content-max-width port. */
.content-box {
    background: var(--surface);
    border-radius: 8px;
    box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
    padding: 30px;
    margin-bottom: 32px;
    color: var(--body-text);
    line-height: 1.65;
    white-space: pre-line;
}

.content-box h2:first-child {
    margin-top: 0;
}

.content-box p:last-child {
    margin: 0;
}

/* An inline link inside body copy (content-box/row-content paragraphs), e.g. Pricing's link
   to Bulk Search. */
.text-link {
    color: var(--accent);
    text-decoration: underline;
}

.row-content ul {
    padding-left: 20px;
}

.text-center {
    text-align: center;
}

/* Strips browser default <button> chrome for a button styled via .button (e.g. Bulk Search's
   Buy Now, which must be a real <button> for form validation, not an <a>). */
.button-reset {
    border: none;
    cursor: pointer;
    font-family: inherit;
}

.mt-16 {
    margin-top: 16px;
}

.text-danger {
    color: var(--danger);
}

.text-subheading {
    color: var(--subheading);
}

.text-sm {
    font-size: 0.65em;
}

.mt-8 {
    margin-top: 8px;
}

/* Promoted from signup.html's own local <style> block (2026-09-22) -- login.html needed the
   exact same two classes, not worth duplicating locally in a second file. */
.accent-link {
    color: var(--accent);
}

/* Links inside a line of text are underlined, not told apart by color alone (WCAG 1.4.1,
   2026-10-25 launch test). */
.accent-link,
#auth-status a {
    text-decoration: underline;
    text-underline-offset: 2px;
}

.login-link-line {
    font-size: 0.7em;
    color: var(--subheading);
}

.form-status-note {
    margin-top: 16px;
    font-size: 0.7em;
}

/* Search */
.search-bar {
    width: 100%;
    padding: 12px 16px;
    font-size: 0.65em;
    background: var(--surface);
    border: 1px solid var(--accent);
    border-radius: 8px;
    color: var(--text);
    margin-bottom: 28px;
}

.search-bar:focus {
    outline: none;
    border-color: var(--accent);
}

.search-bar::placeholder {
    color: var(--subheading);
}

/* Artwork slider — a horizontal, browsable strip of release covers above the accordion.
   Hidden entirely (empty, no display) when no release has artwork. */
.artwork-slider-wrap {
    display: none;
    align-items: center;
    gap: 12px;
    margin-bottom: 32px;
}

/* Gallery-specific -- #gallery-wrap (page.html) reuses .artwork-slider-wrap above, shared with
   Discography's own artwork slider (#artwork-slider-wrap). Scoped to the ID, not the shared
   class, so this gap above the gallery doesn't also shift Discography's slider. */
#gallery-wrap {
    margin-top: 30px;
}

.artwork-slider {
    display: flex;
    gap: 16px;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    scroll-behavior: smooth;
    padding-bottom: 4px;
    flex: 1;
    scrollbar-width: none;
}

.artwork-slider::-webkit-scrollbar {
    display: none;
}

.artwork-slide {
    flex: 0 0 auto;
    width: 160px;
    scroll-snap-align: start;
    cursor: pointer;
    display: flex;
    flex-direction: column;
    gap: 8px;
    background: none;
    border: none;
    padding: 0;
    text-align: left;
    font: inherit;
    color: inherit;
}

.artwork-slide img {
    width: 160px;
    height: 160px;
    object-fit: cover;
    border-radius: 6px;
    border: 1px solid var(--border);
    display: block;
    transition: border-color 0.15s ease;
}

.artwork-slide:hover img {
    border-color: var(--accent);
}

.artwork-slide .slide-title {
    font-size: 0.65em;
    color: var(--subheading);
    text-align: center;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.slider-btn {
    flex-shrink: 0;
    width: 36px;
    height: 36px;
    border-radius: 50%;
    border: 1px solid var(--border);
    background: var(--surface);
    color: var(--text);
    font-size: 0.65em;
    line-height: 1;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
}

.slider-btn:hover {
    background: var(--surface-hover);
    border-color: var(--accent);
}

@media (max-width: 640px) {
    .artwork-slide, .artwork-slide img {
        width: 120px;
        height: 120px;
    }
}

/* Accordion */
.accordion {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.accordion-item {
    border: 1px solid var(--border);
    border-radius: 8px;
    background: var(--surface);
    overflow: hidden;
}

.accordion-header {
    width: 100%;
    display: flex;
    align-items: center;
    gap: 16px;
    padding: 16px 18px;
    background: none;
    border: none;
    cursor: pointer;
    text-align: left;
    color: var(--text);
    font-size: 0.7em;
}

.accordion-header:hover {
    background: var(--surface-hover);
}

.accordion-header .artwork-thumb {
    width: 40px;
    height: 40px;
    border-radius: 4px;
    object-fit: cover;
    background: var(--border);
    flex-shrink: 0;
}

/* Placeholder for missing/broken images (app.js emptyImage + error handler). Sized per spot below. */
.img-empty {
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--border);
    color: var(--accent);
    border-radius: 6px;
    flex-shrink: 0;
}

.img-empty svg {
    width: 35%;
    height: 35%;
}

.accordion-header .img-empty svg {
    width: 60%;
    height: 60%;
}

.hero-slide .img-empty,
.video-card-thumb .img-empty {
    width: 100%;
    height: 100%;
    border-radius: 0;
}

.gallery-slide .img-empty {
    height: 35vh;
    max-height: 450px;
    aspect-ratio: 4 / 3;
}

.artwork-slide .img-empty {
    width: 160px;
    height: 160px;
}

img.zoomable {
    cursor: zoom-in;
}

.img-popup {
    position: fixed;
    inset: 0;
    z-index: 1000;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 16px;
    background: rgba(0, 0, 0, 0.85);
    cursor: zoom-out;
}

.img-popup img {
    max-width: 100%;
    max-height: 100%;
    object-fit: contain;
    border-radius: 6px;
}

.row-image .img-empty {
    width: 100%;
    aspect-ratio: 4 / 3;
    max-height: 450px;
}

.accordion-header .header-text {
    flex: 1;
    min-width: 0;
}

.accordion-header .release-title {
    font-family: var(--display);
    font-size: 0.85em;
    letter-spacing: 0.01em;
}

.accordion-header .release-meta {
    color: var(--subheading);
    font-size: 0.65em;
    margin-top: 2px;
}

.accordion-header .chevron {
    color: var(--subheading);
    transition: transform 0.15s ease;
    flex-shrink: 0;
}

.accordion-item.open .chevron {
    transform: rotate(180deg);
}

.accordion-body {
    max-height: 0;
    opacity: 0;
    overflow: hidden;
    padding: 0 18px 18px;
    color: var(--body-text);
    line-height: 2;
    font-size: 0.65em;
    transition: max-height 0.3s ease, opacity 0.25s ease;
}

.accordion-item.open .accordion-body {
    max-height: 2000px;
    opacity: 1;
}

.accordion-body dl {
    display: grid;
    grid-template-columns: auto 1fr auto 1fr;
    gap: 4px 12px;
    margin: 0 0 12px;
}

@media (max-width: 640px) {
    .accordion-body dl {
        grid-template-columns: auto 1fr;
    }
}

.accordion-body dt {
    color: var(--subheading);
    font-weight: 300;
}

.accordion-body dd {
    margin: 0;
    color: var(--text);
}

.empty-state {
    color: var(--body-text);
    text-align: center;
    padding: 48px 0;
}

.empty-state--error {
    color: var(--danger);
}

/* Player section (local audio and/or streaming platforms) */
.player-section {
    margin-top: 8px;
}

.player-select-label {
    display: block;
    font-size: 0.65em;
    color: var(--subheading);
    margin-bottom: 8px;
}

.player-select {
    display: block;
    margin-top: 4px;
    font-size: 0.65em;
    color: var(--text);
    background: var(--background);
    border: 1px solid var(--accent);
    border-radius: 6px;
    padding: 4px 8px;
}

.stream-embed {
    display: block;
    border-radius: 6px;
}

.streaming-link {
    display: inline-block;
    font-size: 0.65em;
    color: var(--accent-text);
    background: var(--accent);
    padding: 4px 10px;
    border-radius: 999px;
    text-decoration: none;
}

/* Audio files */
.audio-list {
    display: flex;
    flex-direction: column;
    gap: 12px;
    margin-top: 8px;
}

/* Compact play/pause/stop player (2026-09-08, matches the same .audio-btn/.audio-buttons
   pattern custom pages' row-audio already uses) -- the native <audio> element stays in the DOM
   for real playback, just visually hidden since it has no `controls` attribute of its own. */
.audio-item audio {
    display: none;
}

.audio-item .audio-buttons {
    margin-bottom: 6px;
}

.audio-item .audio-meta {
    display: flex;
    justify-content: space-between;
    font-size: 0.65em;
    color: var(--subheading);
    margin-top: 4px;
}

.audio-item .audio-meta a {
    color: var(--accent);
}

.audio-item .audio-meta .not-downloadable {
    color: var(--subheading);
    font-style: italic;
}

/* Home */
.hero {
    padding: 64px 0;
    text-align: center;
}

.hero h1 {
    font-family: var(--display);
    font-weight: 300;
    font-size: 2em;
    text-transform: uppercase;
    letter-spacing: 0.01em;
    margin: 0 0 12px;
}

.hero p {
    color: var(--body-text);
    max-width: 520px;
    margin: 0 auto 28px;
}

.button {
    display: inline-block;
    padding: 12px 24px;
    background: var(--accent);
    color: var(--accent-text);
    border: none;
    border-radius: 8px;
    font-weight: 300;
    font-size: 0.65em;
    cursor: pointer;
    font-family: inherit;
}

/* Shows page ticket links (added 2026-10-04) -- one button per seller, wrapping onto new
   lines on narrow screens. */
.ticket-links {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-top: 16px;
}

/* Sold out / not on sale yet -- same shape as .button, visibly inactive, not clickable. */
.button-disabled {
    opacity: 0.5;
    cursor: default;
}

/* Free entry / at the door with no link -- plain text, same size as a button label. */
.ticket-note {
    display: inline-block;
    padding: 12px 0;
    font-size: 0.65em;
}

/* Everything below this line was ported verbatim from dv-style.css 2026-09-02 (owner
   direction: shared/consistent styling across every tenant, not a per-tenant fork) --
   previously DV-only, with no equivalent here at all. CSS only for now, inert until the
   generic frontend/page.html + custom-page.js template that uses it is actually built (the
   real .content-box above stays untouched -- it's discography's own, already-decided
   version). */

/* Only the custom page's intro (right after the title) — not discography's content-box,
   which shares the .content-box class but should stay full width. */
#page-intro {
    width: 75%;
    margin-left: auto;
    margin-right: auto;
    margin-bottom: 60px;
    font-size: 1em;
    line-height: 1.75em;
    padding: 30px;
}

@media (max-width: 1100px) {
    #page-intro {
        width: 100%;
        text-align: center;
    }
}

/* Footer — site-wide (globalSettings.socialLinks), rendered by app.js's renderFooter() into
   every page's #site-footer element. */
.site-footer {
    margin-top: 24px;
    padding: 32px 24px 40px;
    border-top: 1px solid var(--border);
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 14px;
    text-align: center;
}

.site-footer .footer-group {
    display: flex;
    gap: 20px;
    flex-wrap: wrap;
    justify-content: center;
}

.site-footer a {
    color: var(--subheading);
    font-size: 0.65em;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    text-decoration: none;
}

.site-footer a.social-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

.footer-newsletter {
    display: flex;
    gap: 8px;
}

.footer-newsletter input {
    padding: 8px 12px;
    font-size: 0.65em;
    background: color-mix(in srgb, var(--bg) 45%, var(--muted, var(--bg)));
    border: 1px solid var(--accent);
    border-radius: 6px;
    color: var(--text);
}

.footer-newsletter input::placeholder {
    color: var(--subheading);
}

.footer-newsletter button {
    padding: 8px 16px;
    font-size: 0.65em;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    background: var(--accent);
    color: var(--accent-text);
    border: none;
    border-radius: 6px;
    cursor: pointer;
}

.footer-newsletter-status {
    color: var(--subheading);
    font-size: 0.65em;
}

.footer-newsletter-error {
    display: block;
    color: var(--subheading);
    font-size: 0.65em;
    margin-top: 6px;
}

.site-footer a:hover {
    color: var(--text);
}

.site-footer .footer-label {
    font-size: 0.65em;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--subheading);
}

/* Custom pages (frontend/page.html + custom-page.js) — hero slider, content rows.
   Gallery-placement media reuses .artwork-slider/.artwork-slide, no new CSS needed. */

/* Breaks the hero out of main's content column to span the full viewport width, capped at
   2000px — text/content rows below stay narrow and readable, only the hero goes wide. The
   outer wrapper does the breakout (must not carry its own max-width, or the vw-based margins
   and the cap fight each other and throw off centering); .hero-slider itself just centers
   within that full-bleed space up to the cap. */
.hero-slider-bleed {
    width: 100vw;
    position: relative;
    left: 50%;
    right: 50%;
    margin-left: -50vw;
    margin-right: -50vw;
}

.hero-slider {
    position: relative;
    width: 100%;
    max-width: 2000px;
    margin: 0 auto 30px;
    aspect-ratio: 16/6;
    overflow: hidden;
}

.hero-slider.has-video {
    aspect-ratio: 16/9;
}

.hero-slide {
    position: absolute;
    inset: 0;
    opacity: 0;
    transition: opacity 0.3s ease;
    pointer-events: none;
}

.hero-slide.active {
    opacity: 1;
    pointer-events: auto;
}

.hero-slide img {
    width: 100%;
    height: 100%;
    object-fit: contain;
    display: block;
}

.hero-slide-embed-wrap {
    width: 100%;
    height: 100%;
    background: var(--background);
}

.hero-slide-embed-wrap iframe {
    width: 100%;
    height: 100%;
    display: block;
    border: none;
}

.hero-slider .slider-btn {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    z-index: 2;
}

.hero-slider .slider-prev {
    left: 12px;
}

.hero-slider .slider-next {
    right: 12px;
}

.hero-dots {
    position: absolute;
    bottom: 12px;
    left: 50%;
    transform: translateX(-50%);
    display: flex;
    gap: 6px;
    z-index: 2;
}

.hero-dots button {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    border: none;
    padding: 0;
    background: var(--border);
    cursor: pointer;
}

.hero-dots button.active {
    background: var(--accent);
}

.content-rows {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 32px 60px;
}

@media (max-width: 1100px) {
    .content-rows {
        grid-template-columns: 1fr;
    }
}

.content-row {
    display: flex;
    flex-direction: column;
    gap: 16px;
    padding-bottom: 24px;
    border-bottom: 1px solid #999;
}

/* A single content row has no second box to sit beside — span the full grid width so it can
   center itself, but cap its own size to half and center it, rather than leaving it stuck in
   the left column with empty space on the right. Container width is untouched. */
.content-row:nth-child(odd):last-child {
    grid-column: 1 / -1;
    width: 75%;
    margin-left: auto;
    margin-right: auto;
}

@media (max-width: 1100px) {
    .content-row:nth-child(odd):last-child {
        width: 100%;
    }
}

/* Image floats left inside the content block, text wraps around it (always left, never
   alternates — flip-flopping sides was removed per feedback). */
.row-image {
    float: left;
    /* Capped so the text beside it keeps most of the row (owner, 2026-10-11) -- was free to
       grow almost full width, squeezing the text to a few words per line. */
    max-width: 40%;
    margin: 0 30px 30px 0;
    border-radius: 8px;
    overflow: hidden;
    background: var(--surface);
    box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
}

.row-image img {
    display: block;
    width: auto;
    max-width: 100%;
    height: auto;
    max-height: 450px;
}

@media (max-width: 1100px) {
    .row-image {
        float: none;
        max-width: 100%;
        width: fit-content;
        margin: 0 auto 20px;
    }

    .row-content {
        text-align: center;
    }
}

.row-content {
    overflow: hidden;
    position: relative;
    color: var(--body-text);
    line-height: 1.75em;
    white-space: pre-line;
    padding-top: 0;
}

.row-content.has-audio {
    padding-bottom: 64px;
}

/* Fill the whole panel so the audio player sits at the panel's bottom, level with the panel
   beside it, not right under a short bit of text (owner, 2026-10-11). */
.content-row > .row-content {
    flex: 1;
}

.row-audio {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    width: fit-content;
    margin: 0 auto;
    display: flex;
    align-items: center;
    gap: 12px;
}

.row-audio .fname {
    font-size: 0.65em;
    color: var(--muted-text);
}

.audio-buttons {
    display: flex;
    gap: 8px;
    flex: 0 0 auto;
}

.audio-btn {
    width: 36px;
    height: 36px;
    border-radius: 50%;
    border: 1px solid var(--border);
    background: var(--surface);
    color: var(--text);
    font-size: 0.65em;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
}

.audio-btn:hover {
    background: var(--surface-hover);
}

/* Contact layout form — copied verbatim from frontend/contact.html's inline styles, minus
   the artist/release-count fields (specific to DV's own white-glove contact form). */
.contact-field {
    display: block;
    margin-bottom: 20px;
}

.contact-field label {
    display: block;
    font-size: 0.65em;
    color: var(--muted-text);
    margin-bottom: 6px;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.contact-field input,
.contact-field textarea {
    width: 100%;
    padding: 10px 12px;
    background: var(--surface);
    border: 1px solid var(--accent);
    border-radius: 6px;
    color: var(--text);
    font-size: 0.65em;
    font-family: inherit;
    box-sizing: border-box;
}

/* Page contact form (Contact Form switch on a custom page): same width and label color as the
   DV site's page form and the main Contact page (owner, 2026-10-11). */
#contact-form-wrap {
    max-width: 560px;
    margin: 32px auto 0;
}

#page-contact-form .contact-field label {
    color: var(--subheading);
}

.contact-field textarea {
    resize: vertical;
    min-height: 100px;
}

/* Gallery (custom pages) — fully independent from .artwork-slide/.artwork-slider above,
   deliberately not reused (a Gallery-only sizing change should never leak into Discography's
   own artwork slider). Missed in the first port pass 2026-09-02 -- these five rules simply
   didn't exist here at all, which is why the gallery rendered unstyled. .artwork-slider-wrap
   and .slider-btn above are shared with Discography's slider and were already present. */
.gallery-slider {
    display: flex;
    gap: 16px;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    scroll-behavior: smooth;
    padding-bottom: 4px;
    flex: 1;
    scrollbar-width: none;
}

.gallery-slider::-webkit-scrollbar {
    display: none;
}

/* Was a fixed 450x450 square with object-fit:cover, center-cropping every non-square upload
   regardless of shape (owner discussion 2026-09-06). Fixed height, auto width instead -- a
   normal photo (square, 4:3, 3:2, 16:9) renders uncropped at its own natural width, filling the
   space without distortion. max-width caps how wide any single image can get (a 2:1 image hits
   it right at its natural size, so cover never actually crops a normal upload) -- pure defensive
   fallback for pre-existing wide images already in data from before the panel started rejecting
   new ones over that ratio at upload (studio-app.js's ArrayItemImageThumb maxAspectRatio). */
.gallery-slide {
    flex: 0 0 auto;
    scroll-snap-align: start;
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.gallery-slide img {
    height: 35vh;
    max-height: 450px;
    width: auto;
    border-radius: 6px;
    display: block;
    transition: border-color 0.15s ease;
}

.gallery-slide:hover img {
    border-color: var(--accent);
}

.gallery-slide .slide-title {
    font-size: 0.65em;
    color: var(--muted-text);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

@media (max-width: 640px) {
    .gallery-slide img {
        height: 28vh;
        max-height: 220px;
        width: auto;
    }
}

/* Contact-layout page status message (custom-page.js's renderContactForm). Ported for
   completeness even though the form itself can't actually submit yet on tenant subdomains --
   see custom-page.js's own comment on /api/contact. */
.pcf-status {
    margin-top: 16px;
    font-size: 0.65em;
}

.pcf-status--success,
.pcf-status--error {
    display: inline-block;
    padding: 10px 14px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 6px;
}

.pcf-status--success {
    color: var(--text);
}

.pcf-status--error {
    color: var(--danger);
    border-color: var(--danger);
}

/* ===== LISTEN PAGE ===== */
/* Exact port of the reference band-website-project site's own .listen-grid/.embed-container
   rules (frontend/style.css there) -- Spotify + Bandcamp side by side, SoundCloud/Apple Music
   full-width below. var(--surface)/var(--border)/var(--text) are dv-main's equivalents of the
   reference's var(--card-bg)/var(--border)/var(--text-primary) (dv-main's --border is a bare
   color, not a full shorthand like the reference's, hence "1px solid" here). */
.listen-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 30px;
    margin: 20px 0;
}

/* Phones: one player per row -- side by side they were ~110px wide and ran off the screen
   (2026-10-27 test). */
@media (max-width: 640px) {
    .listen-grid { grid-template-columns: 1fr; }
}

.embed-container {
    min-width: 0;
    padding: 20px;
    background: var(--surface);
    border-radius: 8px;
    border: 1px solid var(--border);
}

.embed-container.empty {
    display: none;
}

.embed-container h2 {
    margin: 0 0 15px 0;
    color: var(--text);
    font-size: 1em;
    text-transform: uppercase;
    letter-spacing: 1px;
}

.embed-container iframe {
    width: 100%;
    border: 0;
    min-height: 152px;
}

/* ===== VIDEOS PAGE / FADE SLIDESHOW ===== */
#fade-gallery-wrapper {
    position: relative;
    width: 100%;
    max-width: 2000px;
    margin: 0 auto 32px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 8px;
    overflow: hidden;
}

#fade-gallery {
    position: relative;
    width: 100%;
    aspect-ratio: 16 / 9;
    overflow: hidden;
}

.fade-slide {
    position: absolute;
    inset: 0;
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transition: opacity 1s ease;
    z-index: 1;
}

.fade-slide.active {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    z-index: 2;
}

.video-wrapper {
    width: 100%;
    height: 100%;
    background: #000;
}

.video-wrapper iframe {
    width: 100%;
    height: 100%;
    border: 0;
}

.slide-arrow {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    z-index: 10;
    width: 36px;
    height: 36px;
    border-radius: 50%;
    border: none;
    background: rgba(0, 0, 0, 0.5);
    color: #fff;
    font-size: 1.2em;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
}

.slide-arrow:hover {
    background: rgba(0, 0, 0, 0.75);
}

.slide-prev { left: 16px; }
.slide-next { right: 16px; }

#slide-dots {
    position: absolute;
    bottom: 16px;
    left: 50%;
    transform: translateX(-50%);
    z-index: 10;
    display: flex;
    gap: 8px;
}

#slide-dots .dot {
    width: 10px;
    height: 10px;
    border-radius: 50%;
    border: none;
    padding: 0;
    background: rgba(255, 255, 255, 0.4);
    cursor: pointer;
}

#slide-dots .dot.active {
    background: #fff;
}

.video-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, 280px);
    gap: 20px;
    margin: 20px 0;
}

.video-card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 8px;
    overflow: hidden;
    cursor: pointer;
}

.video-card:hover {
    border-color: var(--accent);
}

.video-card-thumb {
    position: relative;
    aspect-ratio: 16 / 9;
    background: #000;
}

.video-card-thumb img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

.video-card-content {
    padding: 16px;
}

.video-card-title {
    color: var(--text);
    font-weight: 300;
    margin-bottom: 4px;
}

.video-card-description {
    color: var(--muted-text);
    font-size: 1em;
}

.channel-link-wrap {
    text-align: center;
    margin: 24px 0;
}

/* Credits and dates use the font bundle's "session sheet" face when one is chosen (Dashboard ->
   Fonts, owner 2026-10-10); Classic leaves them on the body font. */
.release-meta,
main dl {
    font-family: var(--font-mono, inherit);
}

/* Store (2026-10-27): featured products at the bottom of every page's content. Products themselves
   are drawn by Shopify's Buy Button inside its own frames; this only lays out the row around them. */
.featured-products {
    margin-top: 40px;
    padding-top: 30px;
}

.featured-products h2 {
    text-align: center;
    margin: 0 0 20px;
}

/* Shopify products, one Buy Button each: 4 across, 2 on phones. */
.shopify-products {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 20px;
}

.shopify-products > div {
    flex: 0 1 calc(25% - 15px);
    min-width: 0;
}

@media (max-width: 640px) {
    .shopify-products > div {
        flex-basis: calc(50% - 10px);
    }
}
