/* ===========================
   CSS Custom Properties
   =========================== */
:root {
    --color-bg: #201d1d;
    --color-bar-bg: #000;
    --color-rule: rgba(255, 255, 255, 0.35);
    --rule: 1px solid var(--color-rule);
    --color-text: #fff;
    --color-overlay: rgba(0, 0, 0, 0.85);
    --font-display: 'Oswald', sans-serif;
    /* Newspaper type pair (Family, Release, Gigs only — never body/navbar/banner):
       a sturdy serif for prose, a typewriter face for headlines/roles/bylines. */
    --font-body: 'Libre Baskerville', 'Georgia', serif;
    --font-mono: 'Courier Prime', 'Courier New', monospace;
    --gradient-height: 100px;
    --nav-padding: 1.2rem 4rem;
    --icon-size: 24px;
    --icon-gap: 1.25rem;
    --logo-height: 64px;
    --topbar-height: 46px;
    --bottom-bar-height: 48px;
    --mobile-bar-height: 60px;
    --container: 1140px;
}

/* ===========================
   Reset & Base
   =========================== */
*,
*::before,
*::after {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

html {
    height: 100%;
    scroll-behavior: smooth;
    scroll-padding-top: calc(var(--topbar-height) + 1rem);
}

@media (prefers-reduced-motion: reduce) {
    html {
        scroll-behavior: auto;
    }
}

/* The loading overlay (below) visually covers the page but doesn't stop it
   being scrollable on its own - this class (toggled by the inline script in
   banner_block.html) locks the real page's scrollbar away while it's up. */
html.has-loading-overlay,
html.has-loading-overlay body {
    overflow: hidden;
}

/* Sticky-footer layout: `.copyright` (the only other in-flow child --
   everything else in the frame is position:fixed) gets margin-top:auto
   below, so it always sits at the true bottom of the viewport even when
   the page content doesn't fill it, instead of floating mid-page. */
body {
    min-height: 100%;
    display: flex;
    flex-direction: column;
    background: var(--color-bg);
    color: var(--color-text);
}

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

ul {
    list-style: none;
}

button {
    background: none;
    border: none;
    cursor: pointer;
    color: inherit;
}

/* ===========================
   Torn Paper
   A magazine-cutout treatment shared by the Family member photos and the
   Release cover art. Four nested layers, because CSS applies `filter`
   before `mask`: a drop-shadow set on a masked element is clipped away
   by that element's own silhouette, so every shadow here has to sit on
   an unmasked wrapper.

       .torn          unmasked, casts the shadow onto the page
       .torn__paper   masked with the outer contour -> paper backing
       .torn__lift    unmasked, casts the photo's shadow onto the paper
       .torn__photo   masked with the inner contour -> the image

   Layout (sizing, positioning) stays with the host component; only the
   material lives here. Each .torn--N supplies a pair of contours cut
   from one tear, so the rim between them varies in width the way real
   torn paper does. Add .torn--tilted to also take the variant's angle.
   =========================== */
.torn {
    /* Two tight contact shadows hugging the torn edge, so the cutout
       reads as paper resting on the page rather than a hole in it. Kept
       deliberately short-throw: a wide, far-offset shadow would float
       it well above the page instead. The page is #201d1d, not true
       black, so a dark shadow still registers against it. */
    filter:
        drop-shadow(0 1px 1px rgba(0, 0, 0, 0.65))
        drop-shadow(0 2px 4px rgba(0, 0, 0, 0.5));
}

.torn--tilted {
    transform: rotate(var(--torn-rotate));
}

.torn__paper {
    background: #cec6b4;
    mask-image: var(--torn-shape);
    mask-repeat: no-repeat;
    mask-size: 100% 100%;
    -webkit-mask-image: var(--torn-shape);
    -webkit-mask-repeat: no-repeat;
    -webkit-mask-size: 100% 100%;
}

/* The image sits a hair proud of its backing, so it casts its own small
   shadow onto the paper rim. */
.torn__lift {
    filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.45));
}

.torn__photo {
    mask-image: var(--torn-photo-shape);
    mask-repeat: no-repeat;
    mask-size: 100% 100%;
    -webkit-mask-image: var(--torn-photo-shape);
    -webkit-mask-repeat: no-repeat;
    -webkit-mask-size: 100% 100%;
}

.torn--1 {
    --torn-shape: url("../img/torn/torn-1.49403260d62f.svg");
    --torn-photo-shape: url("../img/torn/torn-1-photo.1dc5216ac1ac.svg");
    --torn-rotate: -3deg;
}

.torn--2 {
    --torn-shape: url("../img/torn/torn-2.edf83d23d556.svg");
    --torn-photo-shape: url("../img/torn/torn-2-photo.360821d29d61.svg");
    --torn-rotate: 2deg;
}

.torn--3 {
    --torn-shape: url("../img/torn/torn-3.27c1b1634a13.svg");
    --torn-photo-shape: url("../img/torn/torn-3-photo.6d0136ed6db5.svg");
    --torn-rotate: -2deg;
}

.torn--4 {
    --torn-shape: url("../img/torn/torn-4.86a3d939bf34.svg");
    --torn-photo-shape: url("../img/torn/torn-4-photo.1a9b46677c56.svg");
    --torn-rotate: 3deg;
}

.torn--5 {
    --torn-shape: url("../img/torn/torn-5.3207fb9e8f1d.svg");
    --torn-photo-shape: url("../img/torn/torn-5-photo.446a9f040509.svg");
    --torn-rotate: -1.5deg;
}

.torn--6 {
    --torn-shape: url("../img/torn/torn-6.899a8f155451.svg");
    --torn-photo-shape: url("../img/torn/torn-6-photo.1ef43737ce05.svg");
    --torn-rotate: 1.5deg;
}

/* Square-cut variants. The masks stretch to whatever box they're given,
   so the portrait contours above would flatten and bite unevenly into
   square artwork -- release covers use these instead. */
.torn--sq-1 {
    --torn-shape: url("../img/torn/torn-sq-1.818e1be812f5.svg");
    --torn-photo-shape: url("../img/torn/torn-sq-1-photo.eb9ffb3a7d62.svg");
    --torn-rotate: -2deg;
}

.torn--sq-2 {
    --torn-shape: url("../img/torn/torn-sq-2.c9977ac669fd.svg");
    --torn-photo-shape: url("../img/torn/torn-sq-2-photo.025d230bbedc.svg");
    --torn-rotate: 2.5deg;
}

.torn--sq-3 {
    --torn-shape: url("../img/torn/torn-sq-3.ed2476323048.svg");
    --torn-photo-shape: url("../img/torn/torn-sq-3-photo.5f032dd8d0a1.svg");
    --torn-rotate: -1.5deg;
}

/* ===========================
   Banner Section
   =========================== */
.banner {
    position: relative;
    width: 100%;
    height: 100vh;
    height: 100dvh;
    border-bottom: var(--rule);
}

.banner__image {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    filter: grayscale(1);
    /* object-position is set inline per image, from its Wagtail focal point */
}

/* Sequence (2-5 images, ADR-0005): stacked and cross-faded, no controls. A
   single-image Banner never gets these rules - display:block above already
   renders it full-bleed with no added cost. */
.banner--sequence .banner__image {
    position: absolute;
    inset: 0;
    opacity: 0;
    transition: opacity 1.5s ease;
}

.banner--sequence .banner__image.is-active {
    opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
    .banner--sequence .banner__image {
        transition: none;
    }
}

.banner__copy {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: 2rem;
}

.banner__heading {
    font-family: var(--font-display);
    font-size: clamp(2rem, 6vw, 4.5rem);
    text-shadow: 0 2px 12px rgba(0, 0, 0, 0.6);
    text-transform: uppercase;
}

/* ===========================
   Loading Overlay
   Only ever exists in the DOM if JS cloned it out of the <template> in
   banner_block.html (see the inline script there) - so with JS disabled or
   broken, nothing ever shows. Hidden on window "load", not on the banner
   image alone, so it covers page-load in general, not just the banner.
   =========================== */
.loading-overlay {
    position: fixed;
    inset: 0;
    z-index: 1000;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 1.5rem;
    overflow: hidden;
    background: var(--color-bg);
    transition: opacity 0.5s ease;
}

.loading-overlay.is-hidden {
    opacity: 0;
    pointer-events: none;
}

.loading-overlay__logo {
    height: 96px;
    width: auto;
    animation: loading-overlay-zoom 0.9s ease-in-out infinite;
}

@keyframes loading-overlay-zoom {
    0%,
    100% {
        transform: scale(0.85);
    }
    50% {
        transform: scale(1.1);
    }
}

/* "Zoom through": the logo keeps scaling up while fading, rather than
   reversing - reads as flying into and past it, not just disappearing.
   Scale(80) takes a 96px logo well past 7680px, so it overshoots the
   viewport on any screen size instead of just growing to a
   bigger-but-still-bounded icon. A dedicated keyframe animation (not a
   transition off the idle animation) is used deliberately: swapping
   `animation: none` + a transition in the same class toggle is a known
   cross-browser trap where the transition can skip straight to its end
   value instead of playing - a fresh named animation always plays out
   its own keyframes in full. */
.loading-overlay.is-hidden .loading-overlay__logo {
    animation: loading-overlay-zoom-through 0.9s ease-in forwards;
}

@keyframes loading-overlay-zoom-through {
    0% {
        transform: scale(1);
        opacity: 1;
    }
    100% {
        transform: scale(100);
        opacity: 0;
    }
}

.loading-overlay__text {
    font-family: var(--font-display);
    text-transform: uppercase;
    letter-spacing: 0.1em;
    font-size: 0.9rem;
    opacity: 0.7;
    display: flex;
}

.loading-overlay__dots {
    display: inline-flex;
    width: 1.5em;
}

.loading-overlay__dot:nth-child(1) {
    opacity: 1;
}

.loading-overlay__dot:nth-child(2) {
    opacity: 0;
    animation: loading-overlay-dot-2 1.5s linear infinite;
}

.loading-overlay__dot:nth-child(3) {
    opacity: 0;
    animation: loading-overlay-dot-3 1.5s linear infinite;
}

@keyframes loading-overlay-dot-2 {
    0%,
    33.32% {
        opacity: 0;
    }
    33.34%,
    100% {
        opacity: 1;
    }
}

@keyframes loading-overlay-dot-3 {
    0%,
    66.65% {
        opacity: 0;
    }
    66.67%,
    100% {
        opacity: 1;
    }
}

@media (prefers-reduced-motion: reduce) {
    .loading-overlay__logo {
        animation: none;
    }

    .loading-overlay.is-hidden .loading-overlay__logo {
        animation: none;
        opacity: 0;
        transition: opacity 0.6s ease;
    }
}

/* ===========================
   Release Section
   =========================== */
.release {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 3rem;
    max-width: var(--container);
    margin: 0 auto;
    padding: 4rem;
    font-family: var(--font-body);
    font-size: 1.05rem;
    line-height: 1.7;
}

/* The torn layers are plain block boxes here (unlike the Family gallery,
   which stacks crossfading images and so positions them absolutely) --
   the cover art itself is in flow and gives all four layers its size. */
.release__cover {
    flex: 0 1 320px;
    min-width: 0;
    max-width: 480px;
    width: 100%;
}

.release__cover-art {
    display: block;
    width: 100%;
    height: auto;
}

.release__copy {
    display: flex;
    flex: 1 1 320px;
    flex-direction: column;
    align-items: center;
    gap: 1rem;
    min-width: 0;
    max-width: 40rem;
    text-align: center;
}

.release__heading {
    font-family: var(--font-display);
    font-size: clamp(1.75rem, 4vw, 3rem);
    text-transform: uppercase;
}

.release__legend {
    font-size: 1.1rem;
    opacity: 0.85;
}

.release__platform-links {
    display: flex;
    align-items: center;
    gap: 1.5rem;
    margin-top: 1rem;
}

.release__platform-link {
    display: inline-flex;
    transition: opacity 0.2s ease;
}

.release__platform-link:hover {
    opacity: 0.7;
}

/* Logos rendered large, per the ticket: the real brand marks at a consistent size. */
.platform-link__logo {
    width: 48px;
    height: 48px;
}

/* ===========================
   Family Section
   =========================== */
/* Family goes light - paper, not the site's dark chrome (Banner/topbar/
   bottom-bar/mobile-nav stay black; Release and Gigs stay dark too, since
   they're data display rather than the storytelling this contrast is
   for). The noise is a tiled, desaturated feTurbulence SVG compressed into
   a near-white band (see the feComponentTransfer below) and multiplied
   over the ivory base, so it reads as visible paper fiber/grain without
   tipping into static. Overriding --color-rule here (rather than editing
   every rule/divider individually) is deliberate: every var(--rule)
   consumer already inside .family - the Article's headline border, its
   two-column divider - picks up this ivory-appropriate value for free,
   through the same custom-property cascade that already threads --rule
   through the rest of the page. */
.family {
    /* Full-bleed - the paper runs edge to edge; .family__inner below is
       what actually gets capped to the page's usual reading column. */
    background-color: #f2ede1;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch' result='noise'/%3E%3CfeColorMatrix in='noise' type='saturate' values='0'/%3E%3CfeComponentTransfer%3E%3CfeFuncR type='linear' slope='0.45' intercept='0.6'/%3E%3CfeFuncG type='linear' slope='0.45' intercept='0.6'/%3E%3CfeFuncB type='linear' slope='0.45' intercept='0.6'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
    background-blend-mode: multiply;
    color: #2a2420;
    /* Solid, not translucent: a low-alpha tint (the site's usual --rule)
       reads as a fairly light gray once composited over ivory rather than
       the dark backdrop it's tuned for - the same backdrop-dependent
       math that bit the Phrase above. A real column rule is a solid line,
       not a faint one, anyway. */
    --rule: 1px solid #1c191774;
}

.family__inner {
    display: flex;
    flex-direction: column;
    gap: 3rem;
    max-width: var(--container);
    margin: 0 auto;
    padding: 4rem;
    font-family: var(--font-body);
    font-size: 0.95rem;
    line-height: 1.7;
}

.family__heading {
    font-family: var(--font-display);
    font-size: clamp(1.75rem, 4vw, 3rem);
    text-align: center;
    text-transform: uppercase;
}

/* ---- Article: a headline + prose set in newspaper columns ---- */

.article {
    width: 100%;
}

.article__headline {
    font-family: var(--font-mono);
    font-size: clamp(1rem, 2.2vw, 1.3rem);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    margin-bottom: 1rem;
    padding-bottom: 0.5rem;
    border-bottom: var(--rule);
}

.article__prose {
    opacity: 0.9;
}

.article__prose p + p {
    margin-top: 1em;
}

/* Draftail's "bold" feature writes <b>, not <strong> (its contentstate
   converter maps BOLD -> "b"), so the highlight has to match both. */
.article__prose strong,
.article__prose b {
    background: rgba(178, 34, 34, 0.3);
    padding: 0.05em 0.3em;
    border-radius: 0.2em;
}

/* True newspaper flow: text is one continuous river poured into both
   columns, so a paragraph may break across the boundary and the columns
   stay flush at the bottom. A one-column Article runs the same full
   width as a two-column one - a real newspaper's opening lede is set
   exactly this wide, just as a single wide column. */
.article--columns-two .article__prose,
.article--columns-one .article__prose {
    text-align: justify;
    hyphens: auto;
}

.article--columns-two .article__prose {
    columns: 20rem 2;
    column-gap: 2.5rem;
    column-rule: var(--rule);
}

/* One drop cap per Article, on its opening paragraph only - not every
   paragraph, the way a real newspaper piece opens. `background: transparent`
   guards against ::first-letter ever picking up the bold-highlight rule
   above when a paragraph happens to open on highlighted text. */
.article__prose > p:first-of-type::first-letter {
    float: left;
    font-family: var(--font-body);
    font-weight: 700;
    font-size: 3.6em;
    line-height: 0.82;
    margin: 0.05em 0.08em 0 0;
    background: transparent;
}

/* ---- Lineup: one or more Band Members, carousel only when there are 2+ ---- */

.lineup__carousel {
    display: flex;
    align-items: center;
    gap: 1.5rem;
}

.lineup__members {
    flex: 1;
    min-width: 0;
}

.lineup__member {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 2rem;
}

.lineup__member[hidden] {
    display: none;
}

.lineup__gallery {
    position: relative;
    width: 100%;
    max-width: 320px;
    aspect-ratio: 3 / 4;
    flex-shrink: 0;
}

.lineup__gallery-paper,
.lineup__gallery-photo-lift,
.lineup__gallery-photo {
    position: absolute;
    inset: 0;
}

.lineup__gallery-image {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    opacity: 0;
    transition: opacity 1s ease;
}

.lineup__gallery-image.is-active {
    opacity: 1;
}

.lineup__copy {
    display: flex;
    flex-direction: column;
    gap: 1rem;
    max-width: 32rem;
}

.lineup__name {
    font-family: var(--font-body);
    font-size: clamp(1.4rem, 3vw, 2rem);
}

.lineup__role {
    font-family: var(--font-mono);
    text-transform: uppercase;
    letter-spacing: 0.1em;
    opacity: 0.6;
    font-size: 0.9rem;
}

.lineup__bio {
    opacity: 0.85;
    line-height: 1.7;
}

.lineup__nav {
    font-family: var(--font-display);
    font-size: 2rem;
    line-height: 1;
    opacity: 0.6;
    transition: opacity 0.2s ease;
    flex-shrink: 0;
}

.lineup__nav:hover {
    opacity: 1;
}

.lineup__dots {
    display: flex;
    justify-content: center;
    gap: 0.6rem;
}

.lineup__dot {
    width: 10px;
    height: 10px;
    border-radius: 50%;
    /* Ink-based, not the site's usual white dot - a Lineup only ever
       renders inside the (now light) Family. */
    background: rgba(42, 36, 32, 0.25);
    transition: background 0.2s ease;
    padding: 0;
}

.lineup__dot.is-active {
    background: #2a2420;
}

@media (prefers-reduced-motion: reduce) {
    .lineup__gallery-image {
        transition: none;
    }
}

/* ===========================
   Gigs Section
   =========================== */
.gigs {
    display: flex;
    flex-direction: column;
    gap: 2rem;
    max-width: var(--container);
    margin: 0 auto;
    padding: 4rem;
    font-family: var(--font-body);
    font-size: 1.05rem;
    line-height: 1.7;
}

.gigs__heading {
    font-family: var(--font-display);
    font-size: clamp(1.75rem, 4vw, 3rem);
    text-transform: uppercase;
}

/* Table-like alignment: `.gigs__item` is `display: contents` so its
   fields become direct grid children of `.gigs__list`, lining up in
   columns across rows. Every field is always rendered (even empty) so
   the column count per row never shifts. Collapses to a stacked card
   per gig below 768px — see the mobile media query. */
.gigs__list {
    display: grid;
    grid-template-columns: 8rem 1fr 1fr 1fr auto;
    column-gap: 1.5rem;
}

.gigs__list--past {
    grid-template-columns: 5rem 8rem 1fr 1fr 1fr;
    opacity: 0.5;
}

.gigs__item {
    display: contents;
}

.gigs__date,
.gigs__event-name,
.gigs__venue,
.gigs__location,
.gigs__ticket-cta-cell,
.gigs__past-label {
    display: flex;
    align-items: center;
    padding: 0.75rem 0;
    border-bottom: 1px solid rgba(255, 255, 255, 0.12);
}

.gigs__list > .gigs__item:last-child > * {
    border-bottom: none;
}

.gigs__date {
    font-family: var(--font-display);
}

.gigs__event-name {
    font-family: var(--font-display);
    text-transform: uppercase;
    letter-spacing: 0.05em;
}

.gigs__venue {
    font-weight: 700;
}

.gigs__location {
    opacity: 0.6;
}

.gigs__ticket-cta-cell {
    justify-content: flex-end;
}

.gigs__ticket-cta {
    text-transform: uppercase;
    letter-spacing: 0.1em;
    font-size: 0.85rem;
    border: 1px solid rgba(255, 255, 255, 0.3);
    padding: 0.5rem 1.2rem;
    transition: border-color 0.2s ease, background 0.2s ease;
}

.gigs__ticket-cta:hover {
    border-color: rgba(255, 255, 255, 0.6);
    background: rgba(255, 255, 255, 0.05);
}

.gigs__empty {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 1rem;
}

.gigs__empty-message {
    opacity: 0.85;
    max-width: 32rem;
}

.gigs__past-label {
    text-transform: uppercase;
    letter-spacing: 0.1em;
    font-size: 0.75rem;
}

/* ===========================
   Topbar (Desktop) - fixed at every scroll depth
   =========================== */
.topbar {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    height: var(--topbar-height);
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0 4rem;
    z-index: 300;
    transition: background-color 0.3s ease, border-color 0.3s ease;
    border-bottom: 1px solid transparent;
}

/* Gradient scrim over the banner; replaced by a solid bar + rule once scrolled
   past the banner (frame.js toggles .is-scrolled via one IntersectionObserver) */
.topbar::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: var(--gradient-height);
    background: linear-gradient(to bottom, rgba(0, 0, 0, 0.98), rgba(0, 0, 0, 0.6), transparent);
    pointer-events: none;
    z-index: -1;
    opacity: 1;
    transition: opacity 0.3s ease;
}

.topbar.is-scrolled {
    background-color: var(--color-bar-bg);
    border-bottom-color: var(--color-rule);
}

.topbar.is-scrolled::before {
    opacity: 0;
}

.menu-items {
    display: flex;
    gap: 2rem;
    align-items: center;
}

.menu-items a {
    font-family: var(--font-display);
    font-weight: 800;
    font-size: 1.15rem;
    text-transform: uppercase;
    letter-spacing: 0.15em;
    opacity: 0.6;
    transition: opacity 0.2s ease;
}

.menu-items a:hover {
    opacity: 1;
}

.nav-logo img {
    height: var(--logo-height);
    width: auto;
    filter: grayscale(1);
    transition: filter 0.3s ease;
}

.nav-logo:hover img {
    filter: grayscale(0);
}

.topbar__left {
    display: flex;
    gap: 1.25rem;
    align-items: center;
}

/* Straddles the bar's bottom edge: half the logo sits inside the bar, half
   floats over the page below it. No shrink-on-scroll. */
.topbar__logo {
    position: absolute;
    left: 50%;
    top: 100%;
    transform: translate(-50%, -48%);
}

.topbar__right {
    display: flex;
    align-items: center;
}

.icon-link {
    display: flex;
    align-items: center;
    justify-content: center;
    width: var(--icon-size);
    height: var(--icon-size);
    opacity: 0.6;
    transition: opacity 0.2s ease;
    padding: 0;
}

/* Desktop topbar shrink (its icons stay smaller than the mobile bar's) */
.topbar .icon-link {
    width: 20px;
    height: 20px;
}

.icon-link:hover {
    opacity: 1;
}

.icon-link svg {
    width: 100%;
    height: 100%;
}

.topbar__cta {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    opacity: 0.7;
    transition: opacity 0.2s ease;
}

.topbar__cta:hover {
    opacity: 1;
}

.topbar__cta svg {
    width: 20px;
    height: 20px;
    flex-shrink: 0;
}

.topbar__cta-label {
    font-family: var(--font-display);
    font-weight: 800;
    font-size: 0.85rem;
    text-transform: uppercase;
    letter-spacing: 0.1em;
}

.topbar__divider {
    opacity: 0.35;
    font-weight: 300;
}

.locale-switcher {
    display: flex;
    gap: 0.5rem;
}

.locale-switcher__pill {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 1.5rem;
    height: 1.5rem;
    border-radius: 50%;
    border: 1px solid rgba(255, 255, 255, 0.35);
    font-family: 'Courier New', Courier, monospace;
    font-size: 0.7rem;
    letter-spacing: 0.02em;
    opacity: 0.6;
    transition: opacity 0.2s ease, border-color 0.2s ease;
}

.locale-switcher__pill:hover,
.locale-switcher__pill[aria-current="page"] {
    opacity: 1;
    border-color: rgba(255, 255, 255, 0.7);
}

/* In-flow, right after the page content - no background/border of its own,
   just extra bottom padding so it clears the fixed bottom bar.
   margin-top: auto (body is a flex column) pins it to the true bottom of
   the viewport on short pages, and to right after the content on long
   ones -- same as a plain in-flow element once content overflows. */
.copyright {
    font-family: 'Courier New', Courier, monospace;
    font-size: 0.8rem;
    font-weight: 300;
    letter-spacing: 0.03em;
    opacity: 0.25;
    margin: auto 0 0;
    padding: 2rem 1rem calc(var(--bottom-bar-height) + 2rem);
    text-align: center;
    white-space: nowrap;
}

/* ===========================
   Bottom Bar (Desktop + Mobile) - fixed, social links only
   =========================== */
.bottom-bar {
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    height: var(--bottom-bar-height);
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--icon-gap);
    z-index: 300;
    transition: background-color 0.3s ease, border-color 0.3s ease;
    border-top: 1px solid transparent;
}

.bottom-bar::before {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: var(--gradient-height);
    background: linear-gradient(to top, rgba(0, 0, 0, 0.98), rgba(0, 0, 0, 0.6), transparent);
    pointer-events: none;
    z-index: -1;
    opacity: 1;
    transition: opacity 0.3s ease;
}

.bottom-bar.is-scrolled,
.bottom-bar.menu-open {
    background-color: var(--color-bar-bg);
    border-top-color: var(--color-rule);
}

.bottom-bar.is-scrolled::before,
.bottom-bar.menu-open::before {
    opacity: 0;
}

.bottom-bar a {
    display: flex;
    align-items: center;
    justify-content: center;
    transition: opacity 0.2s ease;
}

.bottom-bar a:hover {
    opacity: 0.6;
}

.bottom-bar svg {
    width: 20px;
    height: 20px;
}

/* ===========================
   Mobile Navigation Bar
   =========================== */
.mobile-nav-bar {
    display: none;
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    height: var(--mobile-bar-height);
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    padding: 0 1.25rem;
    z-index: 300;
    transition: background-color 0.3s ease, border-color 0.3s ease;
    border-bottom: 1px solid transparent;
}

/* Gradient scrim over the banner; replaced by a solid bar + rule once scrolled
   past the banner */
.mobile-nav-bar::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background: linear-gradient(to bottom, rgba(0, 0, 0, 0.9), transparent);
    pointer-events: none;
    z-index: -1;
    opacity: 1;
    transition: opacity 0.3s ease;
}

.mobile-nav-bar.is-scrolled,
.mobile-nav-bar.menu-open {
    background-color: var(--color-bar-bg);
    border-bottom-color: var(--color-rule);
}

.mobile-nav-bar.is-scrolled::before,
.mobile-nav-bar.menu-open::before {
    opacity: 0;
}

.mobile-nav-bar__left {
    display: flex;
    gap: 1rem;
    align-items: center;
    justify-self: start;
}

.mobile-logo {
    justify-self: center;
}

.mobile-logo img {
    height: 40px;
    width: auto;
}

/* ===========================
   Hamburger Button
   =========================== */
.hamburger-button {
    display: flex;
    flex-direction: column;
    justify-content: center;
    justify-self: end;
    gap: 5px;
    width: 28px;
    height: 28px;
    padding: 0;
}

.hamburger-button span {
    display: block;
    width: 100%;
    height: 2px;
    background: var(--color-text);
    transition: transform 0.3s ease, opacity 0.3s ease;
    transform-origin: center;
}

.hamburger-button.active span:nth-child(1) {
    transform: translateY(7px) rotate(45deg);
}

.hamburger-button.active span:nth-child(2) {
    opacity: 0;
}

.hamburger-button.active span:nth-child(3) {
    transform: translateY(-7px) rotate(-45deg);
}

.mobile-home-link {
    justify-self: end;
    font-family: 'Courier New', Courier, monospace;
    font-size: 0.85rem;
    text-transform: lowercase;
    letter-spacing: 0.05em;
    color: var(--color-text);
}

/* ===========================
   Mobile Menu Overlay
   =========================== */
.mobile-menu-overlay {
    display: none;
    position: fixed;
    /* Starts below the mobile bar and stops above the bottom bar rather than
       covering them, so the hamburger (now shown as an X) and the bottom
       bar's social links stay visible/usable instead of being ghosted behind
       this backdrop. */
    top: var(--mobile-bar-height);
    left: 0;
    right: 0;
    bottom: var(--bottom-bar-height);
    background: var(--color-overlay);
    z-index: 500;
    flex-direction: column;
    align-items: center;
    justify-content: center;
}

.mobile-menu-overlay.active {
    display: flex;
}

.overlay-content {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 3rem;
}

.overlay-menu-items {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 1.5rem;
}

.overlay-menu-items a {
    font-family: var(--font-display);
    font-weight: 700;
    font-size: 2rem;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    transition: opacity 0.2s ease;
}

.overlay-menu-items a:hover {
    opacity: 0.6;
}

.locale-switcher--overlay .locale-switcher__pill {
    width: 2.2rem;
    height: 2.2rem;
    font-size: 0.85rem;
}

/* ===========================
   Scrolling Phrases (Desktop) - left edge only (CONTEXT.md's Phrase)
   =========================== */
.scrolling-phrases {
    position: fixed;
    z-index: 400;
    overflow: hidden;
    pointer-events: none;
    top: 0;
    bottom: 0;
    left: 0;
    width: 44px;
    /* Fades out near the top/bottom bars without knowing their exact pixel
       heights, so the strip never has to be coordinated with the frame. */
    mask-image: linear-gradient(transparent, black 30%, black 70%, transparent);
    -webkit-mask-image: linear-gradient(transparent, black 30%, black 70%, transparent);
}

.scrolling-phrases__track {
    display: flex;
    flex-direction: column;
    gap: 1.5rem;
    white-space: nowrap;
    will-change: transform;
}

.scrolling-phrases__item {
    font-family: var(--font-display);
    font-size: 1rem;
    text-transform: uppercase;
    letter-spacing: 0.15em;
    /* A mid-tone, not white, is the deliberate choice here: difference-blend
       is |source - backdrop|, so a source near one extreme (white) is
       nearly IDENTICAL to a light backdrop (diff ~ 0) and can only be made
       visible there by cranking opacity so high it overshoots to solid
       black, while also being maximally loud against the dark chrome. A
       source near the midpoint between the dark chrome (~#201d1d) and the
       Family's ivory paper (~#f2ede1) gives the same diff magnitude against
       both - the fixed result then reads as light relative to the dark
       chrome and dark relative to the light paper, which is the inversion
       we're after, without any opacity tuning. */
    color: #8a857e;
    writing-mode: vertical-rl;
    transform: rotate(180deg);
    /* Computed continuously per pixel against whatever's actually behind
       the strip at that point, rather than a single toggle for the whole
       strip (which can't be right the moment a dark/light Section boundary
       sits mid-viewport). Accepted trade-off: over the Banner's actual
       photo colors, a difference blend can look tinted/off rather than a
       clean gray - not a bug, just what this technique costs against a
       photo instead of a flat color. */
    mix-blend-mode: difference;
}

/* ===========================
   Corner Link Buttons
   =========================== */
.corner-link {
    font-family: 'Courier New', Courier, monospace;
    font-weight: 300;
    font-size: 1.2rem;
    text-transform: lowercase;
    letter-spacing: 0.03em;
    opacity: 0.35;
    transition: opacity 0.2s ease;
    text-align: left;
    padding: 0;
}

.corner-link:hover {
    opacity: 0.8;
}

/* ===========================
   Form Modal Overlay
   =========================== */
.form-modal {
    display: none;
    position: fixed;
    inset: 0;
    z-index: 600;
    align-items: center;
    justify-content: center;
}

.form-modal.active {
    display: flex;
}

.form-modal__backdrop {
    position: absolute;
    inset: 0;
    background: rgba(0, 0, 0, 0.94);
}

.form-modal__content {
    position: relative;
    width: 100%;
    max-width: 460px;
    padding: 3rem 2.5rem;
    max-height: 100vh;
    overflow-y: auto;
}

/* Fixed to the viewport corner (not the narrow centered content card) so it
   lands in a comfortable, consistent spot at every screen size - same idea
   as the hamburger button's corner position. */
.form-modal__close {
    position: fixed;
    top: 1.5rem;
    right: 1.5rem;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    font-size: 2.75rem;
    opacity: 0.4;
    transition: opacity 0.2s ease;
    padding: 0;
    line-height: 1;
}

.form-modal__close:hover {
    opacity: 0.8;
}

.form-modal__title {
    font-family: 'Courier New', Courier, monospace;
    font-weight: 300;
    font-size: 1.4rem;
    text-transform: lowercase;
    letter-spacing: 0.05em;
    margin-bottom: 0.5rem;
}

.form-modal__subtitle {
    font-family: 'Courier New', Courier, monospace;
    font-weight: 300;
    font-size: 0.85rem;
    opacity: 0.4;
    margin-bottom: 1.5rem;
}

.form-modal__unavailable {
    font-family: 'Courier New', Courier, monospace;
    font-weight: 300;
    font-size: 0.9rem;
    opacity: 0.7;
    line-height: 1.5;
}

.form-modal__form {
    display: flex;
    flex-direction: column;
    gap: 1rem;
}

.form-field {
    display: flex;
    flex-direction: column;
}

.form-field input,
.form-field select,
.form-field textarea {
    font-family: 'Courier New', Courier, monospace;
    font-size: 0.9rem;
    color: #fff;
    background: rgba(255, 255, 255, 0.1);
    border: 1px solid rgba(255, 255, 255, 0.12);
    border-radius: 0;
    padding: 0.75rem 0.8rem;
    outline: none;
    transition: border-color 0.2s ease, background 0.2s ease;
    width: 100%;
}

.form-field input:focus,
.form-field select:focus,
.form-field textarea:focus {
    border-color: rgba(255, 255, 255, 0.35);
    background: rgba(255, 255, 255, 0.08);
}

.form-field input::placeholder,
.form-field textarea::placeholder {
    color: rgba(255, 255, 255, 0.3);
}

.form-field select {
    appearance: none;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath fill='rgba(255,255,255,0.4)' d='M6 8L0 0h12z'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 0.8rem center;
    padding-right: 2.5rem;
}

.form-field select option {
    background: #111;
    color: #fff;
}

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

.form-field.has-error input,
.form-field.has-error select,
.form-field.has-error textarea {
    border-color: #cc4444;
}

.form-field__error {
    font-family: 'Courier New', Courier, monospace;
    font-size: 0.75rem;
    color: #cc4444;
    margin-top: 0.3rem;
    min-height: 0;
}

.form-modal__submit {
    display: inline-flex;
    align-items: center;
    gap: 0.5em;
    font-family: 'Courier New', Courier, monospace;
    font-size: 0.85rem;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    padding: 0.75rem 1.5rem;
    border: 1px solid rgba(255, 255, 255, 0.2);
    background: transparent;
    color: #fff;
    cursor: pointer;
    transition: border-color 0.2s ease, background 0.2s ease;
    margin-top: 0.5rem;
}

.form-modal__submit:hover {
    border-color: rgba(255, 255, 255, 0.5);
    background: rgba(255, 255, 255, 0.05);
}

.form-modal__submit:disabled {
    opacity: 0.4;
    cursor: default;
}

.form-modal__submit-spinner {
    display: none;
    width: 0.9em;
    height: 0.9em;
    flex: none;
    border: 2px solid rgba(255, 255, 255, 0.3);
    border-top-color: #fff;
    border-radius: 50%;
    animation: form-modal-spin 0.6s linear infinite;
}

.form-modal__submit--sending .form-modal__submit-spinner {
    display: inline-block;
}

@keyframes form-modal-spin {
    to {
        transform: rotate(360deg);
    }
}

@media (prefers-reduced-motion: reduce) {
    .form-modal__submit-spinner {
        animation: none;
    }
}

.form-modal__status {
    font-family: 'Courier New', Courier, monospace;
    font-size: 0.8rem;
    min-height: 1.2em;
}

.form-modal__status--success {
    color: #66cc66;
}

.form-modal__status--error {
    color: #cc4444;
}

.form-modal__privacy {
    font-family: 'Courier New', Courier, monospace;
    font-size: 0.7rem;
    opacity: 0.3;
    margin-top: 1.5rem;
}

/* reCAPTCHA v2 Invisible: badge hidden per Google's terms, replaced by the
   disclosure text below (required whenever the badge isn't shown). */
.grecaptcha-badge {
    visibility: hidden;
}

.form-modal__recaptcha-disclosure {
    font-family: 'Courier New', Courier, monospace;
    font-size: 0.6rem;
    opacity: 0.3;
    margin-top: 0.3rem;
}

.form-modal__recaptcha-disclosure a {
    color: inherit;
}

/* ===========================
   Success Toast Notification
   =========================== */
.success-toast {
    position: fixed;
    bottom: 2rem;
    left: 50%;
    transform: translateX(-50%) translateY(100px);
    background: rgba(46, 204, 113, 0.95);
    color: white;
    padding: 1rem 2rem;
    border-radius: 8px;
    font-size: 1rem;
    font-family: 'Courier New', Courier, monospace;
    opacity: 0;
    transition: all 0.3s ease;
    z-index: 10000;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}

.success-toast.show {
    opacity: 1;
    transform: translateX(-50%) translateY(0);
}

/* ===========================
   Maintenance Mode Banner (staff only - see CONTEXT.md's Maintenance mode)
   =========================== */
.maintenance-banner {
    position: fixed;
    left: 0;
    right: 0;
    bottom: var(--bottom-bar-height);
    z-index: 350;
    background: #c0392b;
    color: #fff;
    text-align: center;
    padding: 0.6rem 1rem;
    font-family: var(--font-display);
    font-weight: 700;
    font-size: 0.85rem;
    text-transform: uppercase;
    letter-spacing: 0.1em;
}

/* ===========================
   Share Page (Share Link landing - /share/<token>/, issue #100/#101)
   =========================== */

/* Prototype (issue #101 follow-up, black/newsprint variant): true black
   page, not the site's usual off-black -- scoped to this page only so the
   rest of the site is untouched. */
body:has(.share-page) {
    background: #1c1b1b;
}

/* Prototype: the band's own home page banner, barely visible, fixed
   behind the file list -- pushed toward a hard 2-tone (no midtone gray)
   so it reads as a badly-inked newspaper photo rather than a clean
   desaturated one. `contrast()` this high clips most of the image to
   pure black or white, with just enough gradient left at the edges to
   still register as a photo rather than a flat silhouette; a true
   threshold would need an SVG filter, not worth it for a prototype.
   Kept genuinely faint -- a first pass at opacity 0.16 (screenshot-
   verified) rendered as a normal faded photo competing with the page
   content, not the barely-there "vanished print" the brief asked for. */
.share-page__watermark {
    position: fixed;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: center;
    filter: grayscale(1) contrast(8) brightness(1.3);
    opacity: 0.07;
    z-index: 0;
    pointer-events: none;
}

/* Newsprint dot-screen + grain, layered above the watermark on its own
   fixed sheet so the texture reads at full strength regardless of the
   photo's own opacity -- a halftone screen sits over the *whole* page in
   real print, not just over the ink. The noise layer is an inline SVG
   feTurbulence data URI (the standard CSS-grain trick, no image asset
   needed); the dot layer is a tiled radial-gradient. `screen` (not
   `overlay`) blend mode: overlay's formula collapses light content
   toward black wherever the backdrop is already dark, which on this
   page's pure-black background crushed the whole texture to nothing;
   screen instead lets light pixels lift off a dark backdrop regardless
   of how dark it is. The SVG carries explicit square width/height so
   `background-size` scales it uniformly -- left at the default (non-
   square) intrinsic size, the stretch turned the turbulence into a
   coarse plaid pattern instead of fine grain. */
.share-page__watermark-screen {
    position: fixed;
    inset: 0;
    width: 100%;
    height: 100%;
    z-index: 0;
    pointer-events: none;
    opacity: 0.12;
    mix-blend-mode: screen;
    background-image:
        url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='1.4' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E"),
        radial-gradient(circle, rgba(255, 255, 255, 0.7) 0.5px, transparent 0.5px);
    background-size: 120px 120px, 2.5px 2.5px;
    background-repeat: repeat, repeat;
}

.share-page {
    position: relative;
    z-index: 1;
    /* `body` is a column flexbox (see the sticky-footer comment above); a
       horizontal auto margin on a flex item disables cross-axis stretch,
       so without an explicit width this shrinks to fit its content instead
       of filling up to max-width - collapsing `.share-page__grid`'s
       `auto-fill` tracks to a single column (issue #136). */
    width: 100%;
    /* Wide enough for 4 168px grid tiles (plus gaps and padding) to sit
       in one row with room to breathe, per issue #136 follow-up
       feedback. */
    max-width: 920px;
    margin: 0 auto;
    padding: calc(var(--topbar-height) + 4rem) 1.5rem 4rem;
    text-align: center;
}

/* The title/subtitle/notes sit directly on the watermark with no card
   behind them (unlike the tiles/list rows below) -- a text-shadow keeps
   them legible over the photo's lighter patches without boxing them in. */
.share-page__title {
    font-family: var(--font-display);
    font-size: 1.8rem;
    margin-bottom: 0.5rem;
    text-shadow: 0 1px 10px rgba(0, 0, 0, 0.9), 0 1px 2px rgba(0, 0, 0, 0.9);
}

.share-page__subtitle {
    font-family: 'Courier New', Courier, monospace;
    opacity: 0.7;
    margin-bottom: 2rem;
    text-shadow: 0 1px 10px rgba(0, 0, 0, 0.9), 0 1px 2px rgba(0, 0, 0, 0.9);
}

/* The "Request a new link" CTA on the expired-link page (issue #142) --
   a real labeled button, unlike the icon-only `.share-page__icon-btn`s in
   the toolbar, since it's the only interactive element on that page. */
.share-page__cta {
    display: inline-block;
    margin-top: 1rem;
    padding: 0.75rem 1.75rem;
    border: var(--rule);
    border-radius: 2px;
    background: transparent;
    color: var(--color-text);
    font-family: var(--font-display);
    font-size: 0.95rem;
    letter-spacing: 0.03em;
    cursor: pointer;
    transition: background 0.2s ease;
}

.share-page__cta:hover {
    background: rgba(255, 255, 255, 0.1);
}

/* Optional per-link message (ShareLink.notes) -- below the file list
   (grid/list view), not the greeting. Same font as the "Aquí están tus
   archivos:" subtitle above, italicized so it still reads as an aside
   rather than another line of the same UI copy. Only rendered at all
   when notes is set, so pages without one are unaffected. */
.share-page__notes {
    font-family: 'Courier New', Courier, monospace;
    font-style: italic;
    font-size: 0.85rem;
    opacity: 0.75;
    margin: 2rem auto 0;
    text-shadow: 0 1px 10px rgba(0, 0, 0, 0.9), 0 1px 2px rgba(0, 0, 0, 0.9);
}

/* `{{ notes|richtext_new_tab }}` (a RichTextField, features=["link"]) renders its
   own `<p>` per paragraph -- the site's global reset zeroes all margins,
   so consecutive paragraphs would otherwise touch with no gap. */
.share-page__notes p + p {
    margin-top: 0.75rem;
}

/* The editor-inserted link the rich text field exists for -- the site's
   global `a { text-decoration: none; }` would otherwise leave it looking
   like plain text, so give it back an underline here to read as a
   link. */
.share-page__notes a {
    text-decoration: underline;
    text-underline-offset: 2px;
}

.share-page__notes a:hover {
    opacity: 0.8;
}

/* `notes` also allows bold/italic/ol/ul now (issue #149) -- the site's
   global reset zeroes list margins/padding and strips markers, which
   left a `<ul>`/`<ol>` here rendering as unindented, marker-less lines. */
.share-page__notes ul,
.share-page__notes ol {
    margin: 0.75rem 0 0;
    padding-left: 1.25rem;
}

.share-page__notes ul {
    list-style: disc;
}

.share-page__notes ol {
    list-style: decimal;
}

.share-page__notes li + li {
    margin-top: 0.35rem;
}

.share-page__toolbar {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    margin-bottom: 2rem;
}

.share-page__icon-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.5rem;
    height: 2.5rem;
    flex-shrink: 0;
    border: 1px solid rgba(255, 255, 255, 0.3);
    border-radius: 8px;
    background: transparent;
    color: rgba(255, 255, 255, 0.7);
    cursor: pointer;
    transition: border-color 0.2s ease, background 0.2s ease, color 0.2s ease;
}

.share-page__icon-btn:hover {
    border-color: #fff;
    color: #fff;
}

/* The `_action_icon.html` partial hardcodes 16px (sized for the grid
   tile's small hover overlay) - bump it up to match the toolbar's 20px
   icons when that same partial is reused inside a full-size icon button
   (the list view's per-row download/external-link button). */
.share-page__icon-btn svg {
    width: 20px;
    height: 20px;
}

.share-page__icon-btn.is-active {
    border-color: #fff;
    background: rgba(255, 255, 255, 0.08);
    color: #fff;
}

.share-page__view-switch {
    display: inline-flex;
    gap: 0.5rem;
    margin-left: auto;
}

/* Grid is the default view (no-JS fallback) - share-view.js flips
   data-share-view on `.share-page__views` once it reads localStorage. */
.share-page__list-view {
    display: none;
}

.share-page__views[data-share-view="list"] .share-page__grid-view {
    display: none;
}

.share-page__views[data-share-view="list"] .share-page__list-view {
    display: block;
}

.share-page__grid {
    display: grid;
    /* `auto-fit` (not `auto-fill`) collapses unused trailing tracks to 0
       width, so a short row's tiles are centered flush against each other
       instead of hugging the left edge of a full-width row of phantom
       empty tracks. */
    grid-template-columns: repeat(auto-fit, 168px);
    justify-content: center;
    gap: 1.5rem;
    margin-bottom: 0.5rem;
}

.share-page__tile {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 1rem;
    width: 168px;
    /* A fixed height (not `aspect-ratio`) keeps every tile the same size
       regardless of title length - a long, line-clamped title can't grow
       the box the way it would with only an aspect-ratio hint. */
    height: 168px;
    padding: 1.5rem 1rem;
    border-radius: 16px;
    border: 1px solid rgba(255, 255, 255, 0.14);
    /* Not the fully-translucent glass-card tint this had pre-prototype --
       at that low an alpha the noisy watermark/newsprint texture showed
       straight through and made the title text unreadable (issue #136
       black/newsprint follow-up) -- but not fully opaque either, so a
       hint of the backdrop still comes through. */
    background: rgba(0, 0, 0, 0.7);
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
    color: #fff;
    text-decoration: none;
    text-align: center;
    transition: transform 0.18s ease, border-color 0.18s ease,
        background 0.18s ease, box-shadow 0.18s ease;
}

.share-page__tile:hover {
    transform: translateY(-3px);
    border-color: rgba(255, 255, 255, 0.35);
    background: rgba(0, 0, 0, 0.8);
    box-shadow: 0 10px 24px rgba(0, 0, 0, 0.5);
}

.share-page__tile-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 64px;
    height: 64px;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.08);
    color: rgba(255, 255, 255, 0.85);
    transition: background 0.18s ease;
}

.share-page__tile:hover .share-page__tile-icon {
    background: rgba(255, 255, 255, 0.14);
}

.share-page__tile-icon svg {
    width: 30px;
    height: 30px;
}

.share-page__tile-title {
    font-family: 'Courier New', Courier, monospace;
    font-size: 0.8rem;
    line-height: 1.3;
    /* Clamp to 2 lines with an ellipsis instead of wrapping further, so a
       long filename can't grow the tile past its fixed height (the full
       title is still available via the tile's `title` attribute). */
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    word-break: break-word;
}

/* Hover/focus affordance hinting at what the tile does - a plain download
   for a document item, or an external-link icon for a link item (which
   opens in a new tab, see the `target="_blank"` in the template). */
.share-page__tile-action {
    position: absolute;
    top: 0.6rem;
    right: 0.6rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.75rem;
    height: 1.75rem;
    border-radius: 50%;
    background: rgba(0, 0, 0, 0.55);
    color: #fff;
    opacity: 0;
    transform: translateY(-2px);
    transition: opacity 0.15s ease, transform 0.15s ease;
}

.share-page__tile:hover .share-page__tile-action,
.share-page__tile:focus-visible .share-page__tile-action {
    opacity: 1;
    transform: translateY(0);
}

.share-page__list {
    display: flex;
    flex-direction: column;
    gap: 1rem;
    text-align: left;
}

.share-page__item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding: 1rem 1.25rem;
    border-radius: 10px;
    border: 1px solid rgba(255, 255, 255, 0.2);
    /* Same reasoning as `.share-page__tile` above -- rows had no fill at
       all, so the watermark showed straight through them too. */
    background: rgba(0, 0, 0, 0.7);
}

.share-page__item-info {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    min-width: 0;
}

.share-page__item-icon {
    display: inline-flex;
    flex-shrink: 0;
    color: rgba(255, 255, 255, 0.7);
}

.share-page__item-title {
    font-family: 'Courier New', Courier, monospace;
    word-break: break-word;
}

/* ===========================
   Responsive: Topbar CTA labels drop below 1024px, icons only from there
   down to the 900px mobile-bar handoff
   =========================== */
@media (max-width: 1024px) {
    .topbar__cta-label {
        display: none;
    }
}

/* ===========================
   Responsive: hamburger handoff (<= 900px)
   Switches from the topbar to the hamburger menu earlier than the rest of
   the mobile styles below it, so the three menu items don't end up
   squeezed against the icons/logo/locale switcher in a narrowing topbar.
   =========================== */
@media (max-width: 900px) {
    html {
        scroll-padding-top: calc(var(--mobile-bar-height) + 1rem);
    }

    .topbar {
        display: none !important;
    }

    .mobile-nav-bar {
        display: grid !important;
    }
}

/* ===========================
   Responsive: Mobile (<= 768px)
   =========================== */
@media (max-width: 768px) {
    .scrolling-phrases {
        display: none;
    }

    .form-modal__content {
        padding: 2rem 1.5rem;
    }

    /* Gigs collapse from a grid "table" to one stacked card per gig —
       `.gigs__item` stops being `display: contents` so its fields group
       together instead of lining up in columns. */
    .gigs__list {
        display: flex;
        flex-direction: column;
        gap: 1.5rem;
    }

    .gigs__item {
        display: flex;
        flex-direction: column;
        gap: 0.35rem;
        padding-bottom: 1.5rem;
        border-bottom: 1px solid rgba(255, 255, 255, 0.12);
    }

    .gigs__list > .gigs__item:last-child {
        border-bottom: none;
    }

    .gigs__date,
    .gigs__event-name,
    .gigs__venue,
    .gigs__location,
    .gigs__ticket-cta-cell,
    .gigs__past-label {
        padding: 0;
        border-bottom: none;
    }

    .gigs__ticket-cta-cell {
        justify-content: flex-start;
    }

    .gigs__ticket-cta-cell:empty {
        display: none;
    }

    .share-page {
        padding-left: 1rem;
        padding-right: 1rem;
    }

    .share-page__grid {
        grid-template-columns: repeat(auto-fit, 136px);
        gap: 1rem;
    }

    .share-page__tile {
        width: 136px;
        height: 136px;
        padding: 1.1rem 0.75rem;
    }

    .share-page__item {
        padding: 0.85rem 1rem;
    }
}