/* Landing page (static SSR at "/"). Self-contained: this file is the only stylesheet the page
   loads, so it carries its own reset rather than leaning on Bootstrap. */

/* Endor is the face the branding is set in, converted from the client's own
   Assets/Lib/Fonts/ENDOR___.ttf. Headers only — it is a blackletter display face and unreadable
   at paragraph length. Below the fold in every case, so swapping in late costs nothing. */
@font-face {
    font-family: 'Endor';
    src: url('/fonts/endor.woff2') format('woff2');
    font-weight: 400;
    font-style: normal;
    font-display: swap;
}

/* The six named colours are the client's own UI palette, straight out of
   Art/Branding/Textures/T_GameUIPallette.png: a violet group and a teal group, every one of which
   carries white text at AA. The key art behind the page is violet with a lime cast, so the world
   and the interface agree on hue and part company on green — which is how the game itself reads. */
:root {
    --indigo: #21124f;
    --plum: #421549;
    --slate: #2b285c;
    --pine: #0d3729;
    --teal: #2c645a;
    --mist: #4b7a7a;

    /* Derived: a ground below the darkest client violet, and the palest client teal lifted until
       it clears AA as link text on that ground (10.4:1 against --void). */
    --void: #0a0620;
    --edge: #3a3168;
    --signal: #7fc9bd;
    --ink: #f3effa;
    --ink-dim: #a79dc2;
    --panel: #1a1136;
    --blurple: #5865f2;
    --shell: min(1160px, 100% - 3rem);
    --display: 'Endor', 'Palatino Linotype', 'Book Antiqua', Palatino, Georgia, serif;
    /* Running text keeps a serif: Palatino's letterforms are drawn with a pen rather than cut like
       Georgia's, so it sits with the blackletter without trying to be it. */
    --prose: 'Palatino Linotype', 'Book Antiqua', 'Iowan Old Style', 'Hoefler Text', Palatino, Georgia, serif;
    --body: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
    /* Every plane is oversized by the same amount so parallax never exposes an edge. Equal across
       all three layers, because unequal scaling would break their registration. */
    --overscan: 1.06;
    /* Shared by object-position and the glimmer's mask; they must agree or the mask slides off. */
    --art-focus: center 40%;
}

*, *::before, *::after { box-sizing: border-box; }

body {
    margin: 0;
    background: var(--void);
    color: var(--ink);
    font-family: var(--body);
    line-height: 1.6;
    -webkit-font-smoothing: antialiased;
    overflow-x: hidden;
}

img { max-width: 100%; display: block; }
a { color: var(--signal); }

h1, h2, h3 { font-family: var(--display); font-weight: 400; line-height: 1.14; letter-spacing: 0.01em; margin: 0; }

.shell { width: var(--shell); margin-inline: auto; }

/* ---- Key art ------------------------------------------------------------- */

/* Fixed to the viewport: the art holds still and the page scrolls over it. The background layer
   never leaves — sections are translucent over it, so the city reads as the page's backdrop all
   the way down rather than as hero decoration. */
.hero__art {
    position: fixed;
    inset: 0;
    z-index: -2;
    overflow: hidden;
    pointer-events: none;
}

.hero__plane {
    position: absolute;
    inset: 0;
    transform: scale(var(--overscan));
    will-change: transform, opacity;
}

.hero__layer {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: var(--art-focus);
}

/* Light catching the foreground silhouette. The mask is static and the sweep inside it moves, so
   the browser only has to recomposite a transform. */
.hero__glimmer {
    position: absolute;
    inset: 0;
    overflow: hidden;
    mix-blend-mode: screen;
    -webkit-mask-image: url("/branding/city-fg-768.webp");
    mask-image: url("/branding/city-fg-768.webp");
    -webkit-mask-size: cover;
    mask-size: cover;
    -webkit-mask-position: var(--art-focus);
    mask-position: var(--art-focus);
    -webkit-mask-repeat: no-repeat;
    mask-repeat: no-repeat;
}

.hero__glimmer span {
    position: absolute;
    top: -30%;
    bottom: -30%;
    left: -40%;
    width: 26%;
    background: linear-gradient(100deg,
        rgba(127, 201, 189, 0) 0%,
        rgba(243, 239, 250, 0.3) 45%,
        rgba(127, 201, 189, 0.16) 62%,
        rgba(151, 255, 137, 0) 100%);
    transform: translate3d(0, 0, 0) skewX(-11deg);
    animation: glimmer 11s ease-in-out 2s infinite;
    will-change: transform;
}

@keyframes glimmer {
    0%   { transform: translate3d(0, 0, 0) skewX(-11deg); }
    100% { transform: translate3d(560%, 0, 0) skewX(-11deg); }
}

/* Held clear at rest so the first view is the art alone, then ramped in by script as the page
   scrolls — same reason as the veil, but shaped so the weight lands under the calls to action. */
.hero__scrim {
    position: absolute;
    inset: 0;
    opacity: 0;
    background: linear-gradient(to bottom,
        rgba(10, 6, 32, 0) 25%,
        rgba(10, 6, 32, 0.78) 80%,
        rgba(10, 6, 32, 0.92) 100%);
}

.hero__motes { position: absolute; inset: 0; }

.mote {
    position: absolute;
    left: var(--x);
    top: var(--y);
    width: var(--s);
    height: var(--s);
    border-radius: 50%;
    background: radial-gradient(circle, rgba(168, 226, 214, 0.9), rgba(127, 201, 189, 0) 70%);
    opacity: 0;
    animation: mote var(--t) ease-in-out var(--d) infinite;
    will-change: transform, opacity;
}

@keyframes mote {
    0%   { opacity: 0; transform: translate3d(0, 0, 0) scale(0.5); }
    20%  { opacity: 0.8; }
    65%  { opacity: 0.3; }
    100% { opacity: 0; transform: translate3d(10px, -90px, 0) scale(1.15); }
}

/* Ramped up by script as the hero scrolls away, so body copy further down never fights the art. */
.hero__veil {
    position: fixed;
    inset: 0;
    z-index: -1;
    background: var(--void);
    opacity: 0;
    pointer-events: none;
}

/* ---- Hero ---------------------------------------------------------------- */

.hero {
    position: relative;
    min-height: 100svh;
    display: grid;
    align-items: end;
}

/* Logo, trailer and calls to action as one column, held over the empty half of the frame: the
   item cascade and the train both sit left of centre, so the right third is the only clear sky.
   Held just above centre, so the Steam widget stays above the fold. */
.hero__mark {
    --logo-w: min(38rem, 56vw);
    --widget-scale: 0.94;
    position: absolute;
    left: 66.67%;
    top: calc(50% - 50px);
    transform: translate(-50%, -50%);
    width: var(--logo-w);
    display: grid;
    /* Pinned rather than auto: the buttons size to their own content and would otherwise widen
       the track, taking the logo with them. */
    grid-template-columns: 100%;
    justify-items: center;
    gap: 1.4rem;
}

.hero__title { margin: 0; width: 100%; }

.hero__logo {
    width: 100%;
    height: auto;
    /* The mark sits straight on the vortex with no background of its own to separate it. */
    filter: drop-shadow(0 0 2.5rem rgba(10, 6, 32, 0.95)) drop-shadow(0 0.25rem 0.6rem rgba(10, 6, 32, 0.8));
}

/* A short viewport gives up logo and trailer size to keep the calls to action above the fold. */
@media (max-height: 52rem) {
    .hero__mark { --logo-w: min(26rem, 48vw); --widget-scale: 0.8; }
}

@media (max-height: 46rem) {
    .hero__mark { --widget-scale: 0.72; }
}

@media (max-width: 45rem) {
    .hero__mark { --logo-w: min(22rem, 80vw); left: 50%; }
}

/* The buttons are the one part of the stack that does not scale with the logo, so they size to
   their own content instead of wrapping inside a narrow mark. */
.hero__mark .hero__ctas { width: max-content; max-width: 92vw; }

.hero__press {
    position: absolute;
    top: 1.25rem;
    right: 1.5rem;
    z-index: 1;
    padding: 0.4rem 1rem;
    border: 1px solid rgba(243, 239, 250, 0.35);
    border-radius: 999px;
    background: rgba(10, 6, 32, 0.45);
    color: var(--ink);
    font-size: 0.9rem;
    text-decoration: none;
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
    transition: border-color 120ms ease, color 120ms ease;
}

.hero__press:hover { border-color: var(--signal); color: var(--signal); }

.hero__cue {
    position: absolute;
    bottom: 1.5rem;
    left: 50%;
    width: 1px;
    height: 3.5rem;
    overflow: hidden;
    background: rgba(243, 239, 250, 0.14);
}

.hero__cue span {
    display: block;
    width: 100%;
    height: 40%;
    background: var(--signal);
    animation: cue 2.6s ease-in-out infinite;
}

@keyframes cue {
    0%   { transform: translateY(-100%); opacity: 0; }
    35%  { opacity: 1; }
    100% { transform: translateY(250%); opacity: 0; }
}

/* ---- Calls to action ----------------------------------------------------- */

.hero__ctas { display: flex; flex-wrap: wrap; gap: 0.9rem; align-items: center; }
.hero__ctas--center { justify-content: center; }

.cta {
    display: inline-flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.85rem 1.6rem;
    border-radius: 0.4rem;
    border: 1px solid transparent;
    font-weight: 600;
    font-size: 1.05rem;
    text-decoration: none;
    transition: transform 120ms ease, background-color 120ms ease, border-color 120ms ease;
}

.cta:hover { transform: translateY(-2px); }

.cta__mark { width: 1.3em; height: 1.3em; flex: none; }

/* Both buttons wear their destination's own colours, with the mark in white on top — the form
   each brand's guidelines bless. */
.cta--steam {
    background: linear-gradient(to bottom, #67c1f5, #417a9b);
    color: #ffffff;
    box-shadow: 0 0.5rem 1.4rem rgba(10, 6, 32, 0.6);
}

.cta--steam:hover { background: linear-gradient(to bottom, #83d0ff, #4d8cb0); }

.cta--discord {
    background: var(--blurple);
    color: #ffffff;
    box-shadow: 0 0.5rem 1.4rem rgba(10, 6, 32, 0.6);
}

.cta--discord:hover { background: #6f7bf7; }

/* Placeholder for a destination that does not exist yet: laid out at full size, visibly inert. */
.cta.is-stub { cursor: default; opacity: 0.5; filter: saturate(0.55); }
.cta.is-stub:hover { transform: none; }
.cta--steam.is-stub:hover { background: linear-gradient(to bottom, #67c1f5, #417a9b); }
.cta--discord.is-stub:hover { background: var(--blurple); }

/* Steam lays its widget out at a fixed 646x190, so it is scaled as a whole rather than resized. */
.steam-widget {
    --scale: var(--widget-scale, 1);
    width: calc(646px * var(--scale));
    height: calc(190px * var(--scale));
    overflow: hidden;
}

.steam-widget iframe {
    display: block;
    width: 646px;
    height: 190px;
    border: 0;
    transform: scale(var(--scale));
    transform-origin: 0 0;
}

.hero__mark .trailer { width: calc(646px * var(--widget-scale)); max-width: none; }

.hero__ctas:has(.steam-widget) { flex-direction: column; }
.cta--steam-fallback { display: none; }

/* Discord and the icon links share a line beneath the widget; elsewhere they flow with the rest. */
.hero__ctas-row { display: contents; }
.hero__ctas:has(.steam-widget) .hero__ctas-row {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    align-items: center;
    gap: 0.9rem;
}

.social {
    display: inline-grid;
    place-items: center;
    width: 3.2rem;
    height: 3.2rem;
    border: 1px solid rgba(243, 239, 250, 0.35);
    border-radius: 50%;
    background: rgba(10, 6, 32, 0.55);
    color: var(--ink);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
    transition: transform 120ms ease, border-color 120ms ease, color 120ms ease;
}

.social:hover { transform: translateY(-2px); border-color: var(--signal); color: var(--signal); }

.social__mark { width: 1.35rem; height: 1.35rem; }

/* Scaled to fit a phone, the widget's text is too small to read. */
@media (max-width: 45rem) {
    .steam-widget { display: none; }
    .cta--steam-fallback { display: inline-flex; }
    .hero__mark .trailer { width: 100%; }
}

.cta__note {
    font-size: 0.8rem;
    font-weight: 400;
    border: 1px solid currentColor;
    border-radius: 0.2rem;
    padding: 0 0.4rem;
    opacity: 0.75;
}

/* ---- Trailer ------------------------------------------------------------- */

.trailer {
    position: relative;
    aspect-ratio: 16 / 9;
    width: 100%;
    max-width: 26rem;
    border: 1px solid var(--edge);
    border-radius: 0.6rem;
    overflow: hidden;
    background: var(--panel);
    cursor: pointer;
}

.trailer iframe { width: 100%; height: 100%; border: 0; }
.trailer img { width: 100%; height: 100%; object-fit: cover; }
.trailer.is-playing { cursor: default; }

.trailer__play {
    position: absolute;
    inset: 0;
    display: grid;
    place-items: center;
    font-size: 3.5rem;
    color: var(--ink);
    text-shadow: 0 2px 18px rgba(0, 0, 0, 0.8);
    background: rgba(10, 6, 32, 0.3);
}

.trailer.is-playing .trailer__play { display: none; }

.trailer--stub { cursor: default; }
.trailer--stub img { filter: grayscale(0.6) brightness(0.45); }
.trailer--stub .trailer__play { opacity: 0.4; }

.trailer__caption {
    position: absolute;
    inset-inline: 0;
    top: 0.9rem;
    margin: 0;
    text-align: center;
    font-size: 0.9rem;
    color: var(--ink);
    text-shadow: 0 1px 6px rgba(10, 6, 32, 0.9);
}

/* ---- Cinema view --------------------------------------------------------- */

/* A native modal dialog, so Esc and the inert backdrop come from the platform. */
.cinema {
    position: fixed;
    inset: 0;
    width: 100%;
    height: 100%;
    max-width: 100%;
    max-height: 100%;
    margin: 0;
    padding: 0;
    border: 0;
    background: transparent;
    overflow: hidden;
}

.cinema::backdrop { background: rgba(4, 4, 3, 0.9); }

.cinema__frame {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    /* Whichever of width or height binds first keeps the 16:9 frame inside the viewport. */
    width: min(94vw, 158vh);
    aspect-ratio: 16 / 9;
    background: #000;
    border: 1px solid var(--edge);
    border-radius: 0.5rem;
    overflow: hidden;
    box-shadow: 0 2.5rem 6rem rgba(0, 0, 0, 0.75);
}

.cinema__frame iframe { display: block; width: 100%; height: 100%; border: 0; }
.cinema__placeholder { width: 100%; height: 100%; object-fit: cover; filter: grayscale(0.6) brightness(0.5); }

.cinema__close {
    position: absolute;
    top: 1.25rem;
    right: 1.5rem;
    width: 2.75rem;
    height: 2.75rem;
    display: grid;
    place-items: center;
    font-size: 1.9rem;
    line-height: 1;
    color: var(--ink);
    background: rgba(26, 17, 54, 0.75);
    border: 1px solid var(--edge);
    border-radius: 50%;
    cursor: pointer;
}

.cinema__close:hover { border-color: var(--signal); color: var(--signal); }

.cinema[open] .cinema__frame { animation: cinema-rise 260ms ease-out; }
.cinema[open]::backdrop { animation: cinema-fade 260ms ease-out; }

@keyframes cinema-rise {
    from { opacity: 0; transform: translate(-50%, -46%) scale(0.96); }
}

@keyframes cinema-fade {
    from { opacity: 0; }
}

/* ---- Sections ------------------------------------------------------------ */

.section {
    --band: rgba(10, 6, 32, 0.76);
    position: relative;
    padding-block: clamp(4rem, 9vw, 7rem);
    /* The band is painted by ::before, which runs wider than the section so it can drift; the
       section clips that overhang. */
    overflow: hidden;
}

/* A wash of paint rather than a filled rectangle: the sheet carries the band colour and three
   mask layers — a torn top edge, a solid middle, a torn bottom edge — and drifts sideways, so
   the profile passing any given point keeps changing. Transform only, so it stays on the
   compositor. Adjacent sections meet torn edge to torn edge, with the key art in the gap. */
.section::before {
    content: '';
    position: absolute;
    inset: 0 -1120px;
    background: var(--band);
    -webkit-mask-image: url("/branding/paint-top.webp"), linear-gradient(#000, #000), url("/branding/paint-bottom.webp");
    mask-image: url("/branding/paint-top.webp"), linear-gradient(#000, #000), url("/branding/paint-bottom.webp");
    /* The tile is narrower than the texture is wide, which packs more of the brush's wander into
       each screen. The middle layer overlaps both edge zones by a little: butting them exactly
       leaves a sub-pixel gap that reads as a hairline across the band. */
    -webkit-mask-size: 620px 3.6rem, auto calc(100% - 6.4rem), 620px 3.6rem;
    mask-size: 620px 3.6rem, auto calc(100% - 6.4rem), 620px 3.6rem;
    -webkit-mask-position: 0 0, 0 3.2rem, 0 100%;
    mask-position: 0 0, 0 3.2rem, 0 100%;
    -webkit-mask-repeat: repeat-x;
    mask-repeat: repeat-x;
    animation: paint-drift 38s linear infinite;
    will-change: transform;
}

/* Neighbours drift the other way and at their own pace, so the page never marches in step. */
.section:nth-of-type(even)::before { animation-duration: 51s; animation-direction: reverse; }

@keyframes paint-drift {
    0%   { transform: translate3d(0, 0, 0); }
    25%  { transform: translate3d(-155px, 4px, 0); }
    50%  { transform: translate3d(-310px, 0, 0); }
    75%  { transform: translate3d(-465px, -4px, 0); }
    100% { transform: translate3d(-620px, 0, 0); }
}

/* Content sits above the wash: both are positioned, and the shell comes later in the tree. */
.shell { position: relative; }

.section--panel { --band: rgba(33, 10, 37, 0.82); }
.section--tide { --band: rgba(9, 32, 27, 0.82); }

.section__title { font-size: clamp(1.75rem, 2.9vw, 2.4rem); }
.section__body { color: var(--ink-dim); max-width: 44ch; margin-top: 1.2rem; font-size: 1.08rem; }

/* Copy beside its carousel once there is room for two columns, alternating sides down the page.
   Below that the grid collapses and they stack in source order. */
@media (min-width: 62rem) {
    .section__split {
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: clamp(2.5rem, 5vw, 5rem);
        align-items: center;
    }

    .section__split .carousel { margin-top: 0; }
    .section__split--flip .section__copy { order: 2; }
    .section__split--flip .carousel { order: 1; }
}

/* The worldbuilding slug that opens the page. Lighter than the sections under it, so the key art
   still carries through the handover from the hero. */
.section--lede { --band: rgba(10, 6, 32, 0.62); text-align: center; }

.lede {
    max-width: 50ch;
    margin: 0 auto;
    font-family: var(--prose);
    font-size: clamp(1.2rem, 2.5vw, 1.85rem);
    line-height: 1.55;
}

.lede--hook {
    margin-top: 1.6rem;
    font-size: clamp(1.05rem, 2vw, 1.45rem);
    color: var(--signal);
}

/* ---- Art carousel -------------------------------------------------------- */

/* Base state is a plain horizontal scroll strip — what no-script and reduced-motion visitors
   keep. Script adds .is-live and the cells become a 3D carousel instead. */
.carousel {
    --cell: clamp(9rem, 18vw, 15rem);
    position: relative;
    margin-top: 2.5rem;
}

.carousel__track {
    display: flex;
    gap: 1rem;
    overflow-x: auto;
    padding-bottom: 0.75rem;
    scrollbar-width: thin;
}

.carousel__cell { flex: 0 0 var(--cell); }

/* Items are board tiles, so they keep the board's corners. */
.carousel__art {
    position: relative;
    aspect-ratio: 1;
    border-radius: 0.15rem;
    overflow: hidden;
}

/* Skills are presented inside a circle in game, so their frame matches. */
.carousel--round .carousel__art { border-radius: 50%; }

.carousel__plate,
.carousel__sprite {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: contain;
}

/* The plate is the entity's own backdrop, so it sits under the sprite in the same box. */
.carousel__plate { z-index: 0; }
.carousel__sprite { z-index: 1; }

.carousel__nav { display: none; }

/* Upgraded: the track stops scrolling and script places every cell in 3D around the centre. */
.carousel.is-live {
    perspective: 1400px;
    height: calc(var(--cell) * 1.35);
    overflow: hidden;
    touch-action: pan-y;
    cursor: grab;
    /* Dragging the carousel must not start a native image drag. */
    user-select: none;
    -webkit-user-select: none;
}

.carousel.is-live img {
    -webkit-user-drag: none;
    pointer-events: none;
}

.carousel.is-live.is-dragging { cursor: grabbing; }

.carousel.is-live .carousel__track {
    display: block;
    overflow: visible;
    padding: 0;
    height: 100%;
    transform-style: preserve-3d;
}

.carousel.is-live .carousel__cell {
    position: absolute;
    top: 50%;
    left: 50%;
    width: var(--cell);
    margin: calc(var(--cell) / -2) 0 0 calc(var(--cell) / -2);
    backface-visibility: hidden;
    will-change: transform, opacity;
}

/* The shadow here is depth information, not decoration: it is the only cue separating a card
   from the ones stacked behind it on the ring. Monsters and events ship without a plate, so
   there is no card for either to sit on and the sprite floats instead. */
.carousel.is-live .carousel__art:has(.carousel__plate) {
    background: rgba(10, 6, 32, 0.45);
    box-shadow: 0 1.5rem 3rem rgba(10, 6, 32, 0.62);
}

.carousel.is-live .carousel__art:not(:has(.carousel__plate)) .carousel__sprite {
    filter: drop-shadow(0 1rem 2rem rgba(10, 6, 32, 0.8));
}

.carousel.is-live .carousel__nav {
    display: flex;
    align-items: center;
    justify-content: center;
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    z-index: 5;
    width: 2.75rem;
    height: 2.75rem;
    padding: 0;
    font-family: inherit;
    font-size: 1.7rem;
    line-height: 1;
    color: var(--ink-dim);
    background: rgba(10, 6, 32, 0.62);
    border: 1px solid var(--edge);
    border-radius: 50%;
    cursor: pointer;
}

.carousel__nav:hover { color: var(--signal); border-color: var(--signal); }
.carousel__nav--prev { left: 0; }
.carousel__nav--next { right: 0; }

/* ---- Screenshot carousel ------------------------------------------------- */

/* The game itself, on the same ring as the art strips but at the size and shape it is played at.
   One card holds the column; its neighbours are clipped by the shell and read as depth. */
.carousel--wide {
    --cell: min(46rem, 84vw);
    margin-top: 3rem;
}

.carousel--wide .carousel__art {
    aspect-ratio: 16 / 9;
    border-radius: 0.35rem;
    border: 1px solid var(--edge);
}

.carousel__shot {
    display: block;
    width: 100%;
    height: 100%;
    /* Cover, not contain: a capture off-shape would otherwise letterbox against the card. */
    object-fit: cover;
}

/* Screenshots have no plate, so the shadow that separates a card from the ones behind it has to
   be asked for here rather than inherited from the art carousel's plate rule. */
.carousel--wide.is-live {
    height: calc(var(--cell) * 0.66);
}

/* The art carousel centres a cell by pulling it up half its own width, which is only its height
   while the cells are square. These are 16:9, so half of that. */
.carousel--wide.is-live .carousel__cell {
    margin-top: calc(var(--cell) * -0.28125);
}

.carousel--wide.is-live .carousel__art {
    background: var(--void);
    box-shadow: 0 1.5rem 3.5rem rgba(10, 6, 32, 0.72);
}

/* ---- Close & footer ------------------------------------------------------ */

.close { text-align: center; }
.close .hero__ctas { margin-top: 2.6rem; }

.footer {
    position: relative;
    border-top: 1px solid var(--edge);
    padding-block: 2rem;
    color: var(--ink-dim);
    font-size: 0.87rem;
    display: flex;
    flex-wrap: wrap;
    gap: 1rem;
    justify-content: space-between;
}

.footer a { color: var(--ink-dim); }
.footer a:hover { color: var(--signal); }

.footer__links {
    display: flex;
    flex-wrap: wrap;
    gap: 1.5rem;
    margin: 0;
    padding: 0;
    list-style: none;
}

@media (prefers-reduced-motion: reduce) {
    .section::before { animation: none; }
    .cta:hover { transform: none; }
    .hero__plane { transform: scale(var(--overscan)) !important; }
    .hero__glimmer span, .mote, .hero__cue span { animation: none; }
    .cinema[open] .cinema__frame, .cinema[open]::backdrop { animation: none; }
    .mote { opacity: 0.35; }
}
