#tpl_home {
    position: relative;
    min-height: 100vh;
    width: 100%;
    background: #0a0021;
    font-family: 'Baloo 2', cursive;
}

/* ---------------------------------------------------------------- */
/* Debug panel (?debug=1): every effect below is OFF by default in   */
/* this mode, toggle one at a time to isolate what causes the lag.   */
/* ---------------------------------------------------------------- */

.fx-debug {
    position: fixed;
    top: 12px;
    left: 12px;
    z-index: 999;
    background: rgba(0, 0, 0, 0.8);
    color: #fff;
    font-family: 'Share Tech Mono', monospace;
    font-size: 12px;
    padding: 10px 14px;
    border-radius: 10px;
    line-height: 1.7;
    max-width: 230px;
}

.fx-debug-title {
    font-weight: bold;
    margin-bottom: 6px;
    color: #39ff6a;
}

.fx-debug label {
    display: block;
    cursor: pointer;
}

.fx-debug input {
    margin-right: 6px;
}

/* ---------------------------------------------------------------- */
/* Boot screen: the old ASCII pony, before the magic kicks in        */
/* ---------------------------------------------------------------- */

#boot-screen {
    position: fixed;
    inset: 0;
    z-index: 100;
    background: #000;
    color: #39ff6a;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    font-family: 'Share Tech Mono', monospace;
    text-shadow: 0 0 6px rgba(57, 255, 106, 0.8);
    transition: opacity 0.55s ease, filter 0.55s ease;
}

#boot-ascii {
    margin: 0 0 16px;
    font-size: 13px;
    font-weight: 700;
    line-height: 1.15;
    white-space: pre;
    max-width: 92vw;
    overflow: auto;
    /* Overrides the inherited glow from #boot-screen: at this small a size,
       backslashes/quotes read as barely-there hairlines with only a thin
       6px shadow. Bolder weight thickens the strokes themselves; a slightly
       stronger glow (but not too much — it starts blurring the shape back
       out) makes them pop against the black background. */
    text-shadow:
        0 0 3px #39ff6a,
        0 0 7px rgba(57, 255, 106, 0.7);
}

#boot-text {
    font-size: 14px;
    letter-spacing: 2px;
}

@keyframes bootDots {
    0%, 20% { opacity: 0.2; }
    50% { opacity: 1; }
    100% { opacity: 0.2; }
}

#boot-dots {
    animation: bootDots 1s steps(1) infinite;
}

#boot-screen.boot-glitch-out {
    animation: bootGlitch 0.55s steps(6) forwards;
}

@keyframes bootGlitch {
    0%   { opacity: 1; filter: none; clip-path: inset(0 0 0 0); }
    20%  { clip-path: inset(10% 0 40% 0); filter: hue-rotate(90deg); }
    40%  { clip-path: inset(60% 0 5% 0); filter: hue-rotate(180deg); }
    60%  { clip-path: inset(20% 0 70% 0); filter: hue-rotate(270deg); }
    80%  { clip-path: inset(0 0 0 0); opacity: 0.6; }
    100% { opacity: 0; clip-path: inset(0 0 100% 0); }
}

/* ---------------------------------------------------------------- */
/* The party                                                         */
/* ---------------------------------------------------------------- */

#party {
    position: relative;
    isolation: isolate; /* gives .bg-canvas (z-index: -1) and .arc-canvas (z-index: 0) a stacking context to sit in, above #party's own background */
    min-height: 100vh;
    width: 100%;
    overflow: hidden;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 60px 20px 40px;
    text-align: center;
    background: #23145a; /* plain fallback matching the sunset gradient's own top colour, in case JS/canvas is unavailable */
    opacity: 0;
}

#party.party-in {
    opacity: 1;
    transition: opacity 0.6s ease;
}

/*
 * Sunset sky background: deep indigo at the top fading down through violet
 * and magenta to a warm peachy orange near the bottom, matching a pixel-art
 * sunset-sky reference (indigo top, pink clouds, warm horizon glow). A
 * previous version cycled through the whole rainbow via a rotating canvas
 * gradient — this one is static (drawn once, redrawn only on resize or
 * when the "bgbands" toggle changes, no rAF loop). Kept on canvas rather
 * than a plain CSS `background` so the smooth/banded switch
 * shares the same rendering approach as arc-canvas/starfield-canvas.
 * Unlike those two, this isn't gated behind its own on/off debug checkbox
 * — it's the page's actual background now, always drawn — so it stays
 * `display: block` by default; #party's own flat `background` above is
 * only the fallback for the instant before JS runs.
 */
.bg-canvas {
    position: absolute;
    inset: 0;
    z-index: -1;
    width: 100%;
    height: 100%;
    display: block;
}

/* Sky: stars + clouds */

.sky {
    position: absolute;
    inset: 0;
    overflow: hidden;
    pointer-events: none;
    z-index: 1;
    contain: layout style paint;
}

/*
 * Stars and clouds both used to be DOM elements animated with CSS: 40 star
 * elements (own GPU layer each), then 2 with a giant box-shadow list (each
 * layer's bounding box covering almost the whole screen); 3 cloud elements
 * that still cost real GPU even without a filter. Each is now its own
 * canvas, drawn as pixels via a throttled JS redraw (~12fps), not a CSS
 * animation — previously *shared* one canvas/context, which let a
 * resize/render toggled by one effect bleed into the other; two fully
 * independent canvases removes that entirely. Always visible now (no more
 * on/off debug checkbox), only stopped for prefers-reduced-motion — see
 * the <script> in the template. Backing buffers are deliberately kept at 1:1
 * (not devicePixelRatio) — on a laptop with 1.5-2x display scaling, scaling
 * the buffer up made even a fully static canvas noticeably expensive to
 * composite, just from being a bigger texture.
 *
 * `display: none` by default: a sized canvas is its own composited GPU
 * layer just by having a backing buffer, on or not — JS flips this to
 * `block` only while enabled, and drops width/height back to 0 when off.
 * Clouds sit after stars in DOM order (both position: absolute, same
 * z-index), so they paint on top.
 */
.starfield-canvas,
.clouds-canvas {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    display: none;
}

/*
 * Rotating rainbow ring, drawn with createConicGradient() + a handful of
 * flat-alpha concentric strokes (posterized bands, not a soft radial fade —
 * a deliberate retro/pixel-art look) instead of a CSS mask on a rotating
 * transform. A previous CSS version (a masked conic-gradient nearly twice
 * the viewport's width/height, rotating via `transform`) was the single
 * worst effect on the page for GPU cost; this canvas version won the A/B
 * outright and is kept as the only implementation. Always visible, like
 * bg-canvas/hills-canvas — no on/off debug checkbox, just the "arcbands"
 * sub-toggle in the JS.
 * `display: none` by default, same reasoning as .starfield-canvas above —
 * a sized canvas is its own composited GPU layer whether or not it's
 * drawing, so this is load-bearing for a true "off" state, not just
 * cosmetic.
 */
.arc-canvas {
    position: absolute;
    inset: 0;
    z-index: 0;
    width: 100%;
    height: 100%;
    display: none;
}

/* Hero */

.hero {
    position: relative;
    z-index: 2;
    max-width: 900px;
}

/*
 * A genuine pixel-art font (not just a monospace one like Share Tech Mono)
 * kept to this one small badge on purpose — 'Press Start 2P' at headline
 * size next to 'Baloo 2' would fight the page's existing identity
 * rather than nod at retro/8-bit alongside it.
 */
.pixel-badge {
    display: inline-block;
    margin: 0 0 18px;
    padding: 6px 10px;
    font-family: 'Press Start 2P', 'Share Tech Mono', monospace;
    font-size: 0.6rem;
    line-height: 1.4;
    letter-spacing: 1px;
    color: #39ff6a;
    background: rgba(0, 0, 0, 0.35);
    border: 2px solid #39ff6a;
    text-shadow: 0 0 6px rgba(57, 255, 106, 0.7);
    /* Now a real <button> (it toggles CRT/8-bit/bands on click, see the
       <script>), so reset the browser's own button chrome — everything
       else above already covers font/color/background/border. */
    appearance: none;
    cursor: pointer;
    transition: transform 0.1s ease, background-color 0.15s ease, color 0.15s ease;
}

/* Classic retro-terminal "inverted selection" look on hover, plus a small
   press-down on click — nothing changed visually before, so it read as a
   plain label rather than a button. */
.pixel-badge:hover {
    background: #39ff6a;
    color: #0a0021;
    text-shadow: none;
}

.pixel-badge:active {
    transform: translateY(2px) scale(0.96);
}

/*
 * Chunky "3D block" title. Went through an animated neon text-shadow, then
 * a canvas-drawn glow (had to hand-match the h1's own font/letter-spacing,
 * which is exactly what went wrong there); this is a plain stack of solid,
 * unblurred offsets, so it needs neither, and there's nothing here that
 * ever needs a per-frame repaint in the first place. 'Press Start 2P' (the
 * genuine pixel font already loaded for the badge) instead of 'Baloo 2' —
 * a rounded, friendly font read as "not square enough" for a retro-8-bit
 * wordmark; a real pixel font's blocky right angles are the whole point.
 * Its glyphs are much wider per character than Baloo 2's, hence the
 * smaller clamp() range, to keep "HORNY PONY" fitting on one line.
 */
.title-3d {
    margin: 0 0 14px;
    font-family: 'Press Start 2P', 'Share Tech Mono', monospace;
    font-weight: 400;
    font-size: clamp(1.2rem, 6vw, 3.4rem);
    line-height: 1.5;
    color: #fff;
    text-shadow:
        1px 1px 0 #ff2fd0,
        2px 2px 0 #ff2fd0,
        3px 3px 0 #ff2fd0,
        4px 4px 0 #e024b8,
        5px 5px 0 #e024b8,
        6px 6px 0 #c31da0,
        7px 7px 0 #c31da0,
        8px 8px 0 rgba(0, 0, 0, 0.35);
}

.tagline {
    margin: 0 0 54px;
    font-size: clamp(1.1rem, 3vw, 1.6rem);
    font-weight: 700;
    color: #fff;
    text-shadow: 0 2px 6px rgba(0, 0, 0, 0.35);
}

.subtagline {
    margin: 14px 0 0;
    font-family: 'Share Tech Mono', monospace;
    font-size: clamp(0.62rem, 1.4vw, 0.75rem);
    font-weight: 500;
    color: rgba(255, 255, 255, 0.85);
}

.magic-btn {
    position: relative;
    overflow: hidden; /* clips the canvas below to the pill shape */
    isolation: isolate; /* keeps the canvas's z-index: -1 inside this button's own stacking context, behind the label text */
    font-family: 'Baloo 2', cursive;
    font-size: clamp(1rem, 2.4vw, 1.3rem);
    font-weight: 800;
    color: #2a0845;
    border: none;
    border-radius: 999px;
    padding: 16px 34px;
    cursor: pointer;
    background: #ff9a2f; /* static fallback: the canvas fully covers this once it draws */
    box-shadow: 0 6px 0 rgba(0, 0, 0, 0.25), 0 0 30px rgba(255, 47, 208, 0.6);
    transition: transform 0.15s ease;
}

/*
 * Costly as a plain `background-position` animation on the button itself,
 * then still costly moved to a `transform` slide on an oversized
 * pseudo-element (compositor-eligible in theory, same lesson as the party
 * background/arc-en-ciel elsewhere in this theme) — the remaining cost is
 * most likely this small layer's rounded-corner clip (`overflow: hidden` +
 * `border-radius: 999px`) needing to be reapplied to the animating layer
 * every frame, on top of the button's own static `box-shadow` sitting in
 * the same stacking context. A canvas sidesteps both: it's drawn to a
 * plain bitmap at a throttled ~12fps (see the <script> in the template),
 * and clipping a mostly-static bitmap to a rounded corner costs nothing
 * like clipping a continuously re-transformed layer does.
 */
.magic-btn-canvas {
    position: absolute;
    inset: 0;
    z-index: -1;
    width: 100%;
    height: 100%;
    display: block;
}

.magic-btn:hover {
    transform: translateY(-3px) scale(1.05);
}

.magic-btn:active {
    transform: translateY(2px) scale(0.98);
}

/*
 * Retro HUD-style frame around the generated message, echoing the boxed
 * "INVITES" banner under a retro arcade wordmark: a coloured border and a
 * soft glow. Always shows a message, even before the first click (see the
 * <script>), so it keeps its min-width/min-height rather than popping into
 * existence.
 * `display: block; width: fit-content` (not `inline-block`) so it's
 * guaranteed its own line below the button — `<button>` is inline-level by
 * default, and inline-block would let the message sit *beside* it instead
 * of below whenever there's room.
 */
.magic-msg {
    position: relative; /* anchors the two corner-accent pseudo-elements below */
    display: block;
    width: fit-content;
    min-height: 1.4em;
    min-width: 220px;
    margin: 0 auto 20px;
    padding: 8px 22px;
    font-weight: 700;
    color: #fff;
    text-shadow: 0 0 10px rgba(255, 255, 255, 0.6);
    border: 3px solid #2fd0ff;
    background: rgba(10, 8, 30, 0.45);
    box-shadow: 0 0 16px rgba(47, 208, 255, 0.5);
}

/*
 * Small pixel corner accents, à la retro game HUD frames: two 8x8 tabs
 * straddling each corner (half in, half out of the border), so they read
 * as little studs poking past the frame rather than being hidden behind
 * it. `textContent` is what fills this element (see the <script>), which
 * would wipe out any real child elements — pseudo-elements are immune to
 * that, so the accents live there instead of as actual corner <span>s.
 */
.magic-msg::before,
.magic-msg::after {
    content: "";
    position: absolute;
    inset: -4px;
    pointer-events: none;
    background-repeat: no-repeat;
    background-size: 8px 8px;
}

.magic-msg::before {
    background-image: linear-gradient(#2fd0ff, #2fd0ff), linear-gradient(#2fd0ff, #2fd0ff);
    background-position: top left, bottom right;
}

.magic-msg::after {
    background-image: linear-gradient(#2fd0ff, #2fd0ff), linear-gradient(#2fd0ff, #2fd0ff);
    background-position: top right, bottom left;
}

/*
 * Pastel hills along the bottom of the screen: three static, stacked
 * silhouettes (back/mid/front), replacing the rainbow road + running
 * unicorn strip. Drawn to canvas — not gated behind its own on/off
 * checkbox (always visible, like .bg-canvas), but its backing buffer is
 * still wired into the shared "lowres" ("Effet 8-bit") toggle so it
 * pixelates along with the sky/arc/background. Height matches the tallest
 * (back) layer; the other two are drawn shorter within the same canvas,
 * anchored to the bottom — see resizeHillsCanvas()/renderHills() in the
 * template's <script>.
 */
.hills-canvas {
    position: absolute;
    inset: auto 0 0 0;
    z-index: 1;
    pointer-events: none;
    width: 100%;
    height: 22vw;
    display: block;
}

/*
 * Wandering pixel unicorn: same box as .hills-canvas (so its own
 * fraction-of-width math lines up with the hills underneath) and the same
 * z-index, but later in DOM order, so it paints in front of the hill
 * silhouettes rather than behind them — still safely below .hero's own
 * z-index: 2. Wired into the "Effet 8-bit" toggle below like the other
 * canvases, so its own backing buffer shrinks too — it's already drawn as
 * flat "pixel" blocks, but a coarser buffer chunks those blocks up further
 * for an even blockier look, consistent with the rest of the page.
 */
.hill-unicorn-canvas {
    position: absolute;
    inset: auto 0 0 0;
    z-index: 1;
    pointer-events: none;
    width: 100%;
    height: 22vw;
    display: block;
}

/*
 * Firework rocket + burst: covers the whole #party box (not just the
 * .sky/.hills strip) so a rocket launched near the button has room to fly
 * up into the "sky" before exploding. Same z-index/layer group as the sky
 * and hills, painted last among them (DOM order) so it can appear above
 * both while still sitting behind .hero's own text.
 */
.fireworks-canvas {
    position: absolute;
    inset: 0;
    z-index: 1;
    pointer-events: none;
    width: 100%;
    height: 100%;
    display: block;
}

/* Confetti burst */

#confetti-layer {
    position: fixed;
    inset: 0;
    pointer-events: none;
    z-index: 50;
    overflow: hidden;
    contain: layout style paint;
}

.confetti-bit {
    position: absolute;
    top: 55%;
    font-size: 1.6rem;
    animation: confettiBurst var(--dur, 1s) ease-out forwards;
}

@keyframes confettiBurst {
    0% {
        transform: translate(0, 0) rotate(0deg);
        opacity: 1;
    }
    100% {
        transform: translate(var(--tx), var(--ty)) rotate(var(--rot));
        opacity: 0;
    }
}

/* Mouse sparkle trail */

.mouse-spark {
    position: fixed;
    z-index: 60;
    pointer-events: none;
    font-size: 1rem;
    transform: translate(-50%, -50%);
    animation: sparkleFade 0.7s ease-out forwards;
}

@keyframes sparkleFade {
    0%   { opacity: 1; transform: translate(-50%, -50%) scale(1) rotate(0deg); }
    100% { opacity: 0; transform: translate(-50%, -140%) scale(0.3) rotate(60deg); }
}

/* Footer */

/*
 * Old-game credits-line look (à la "© 2019 STUDIO NAME" under a retro
 * arcade splash screen) — monospace terminal font (already loaded for the
 * boot screen), wide letter-spacing, small caps-friendly sizing.
 */
.tiny-footer {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 14px;
    z-index: 2;
    margin: 0;
    font-family: 'Share Tech Mono', monospace;
    font-size: 0.6rem;
    font-weight: 700;
    letter-spacing: 1px;
    text-transform: uppercase;
    color: rgba(255, 255, 255, 0.75);
}

/*
 * CRT scanlines: a static repeating-linear-gradient overlay, not an
 * animation — no compositor/GPU cost beyond one extra painted layer (no
 * `background-position` shift, no transform, nothing recomputed per frame).
 * z-index: 1 sits above the background/sky/arc/hills layers (-1/0/1) but
 * below .hero and .tiny-footer (both z-index: 2) — was z-index: 10
 * (above literally everything, including the text), which drew the
 * scanlines over the title/tagline/button instead of just over the
 * backdrop behind them.
 *
 * On by default in production (same `fxEnabled()` convention as the JS
 * variables below: on unless in debug mode, where every effect starts off
 * until its own checkbox is ticked) — and forced off by the "MODE HD
 * 8-BIT" badge (see the <script>), independent of debug mode entirely.
 */
#party::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 1;
    pointer-events: none;
    background: repeating-linear-gradient(
        to bottom,
        rgba(0, 0, 0, 0.18) 0px,
        rgba(0, 0, 0, 0.18) 1px,
        transparent 1px,
        transparent 3px
    );
    opacity: 1;
}

body.fx-debug-mode #party::after {
    opacity: 0;
}

body.fx-debug-mode.fx-scanlines #party::after {
    opacity: 1;
}

body.fx-hd-off #party::after {
    opacity: 0;
}

/* ---------------------------------------------------------------- */
/* Small screens                                                     */
/* ---------------------------------------------------------------- */

@media (max-width: 480px) {
    #boot-ascii {
        font-size: 8px;
    }
}

/* ---------------------------------------------------------------- */
/* Reduced motion                                                    */
/* ---------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
    #party {
        opacity: 1;
    }
}

/* ---------------------------------------------------------------- */
/* Debug mode (?debug=1): every remaining toggleable effect is canvas + */
/* JS, driven by fxReactive in the template's <script> — nothing left  */
/* here needs an `animation-play-state` rule.                          */
/* ---------------------------------------------------------------- */

/*
 * "Effet 8-bit": the JS (resizeBgCanvas/resizeArcCanvas/resizeSkyCanvas,
 * driven by the shared lowResOn flag) shrinks each canvas's own backing
 * buffer so there are fewer real pixels to paint; this rule is what then
 * stops the browser from smoothing that small buffer back out when it's
 * stretched up to the element's display size. Was briefly split into two
 * separate checkboxes (resolution vs. render mode) so each half could be
 * tested in isolation, but neither one looks like anything on its own —
 * just a blurry downscale, or no visible change at all — so back to a
 * single toggle for the combined look.
 *
 * On by default in production (matches lowResOn's own `fxEnabled()`
 * default in the JS), off by default in debug mode until the checkbox is
 * ticked, and forced off by the "MODE HD 8-BIT" badge regardless of debug
 * mode (see the <script>).
 */
.bg-canvas,
.arc-canvas,
.starfield-canvas,
.clouds-canvas,
.hills-canvas,
.hill-unicorn-canvas,
.fireworks-canvas {
    image-rendering: -moz-crisp-edges;
    image-rendering: pixelated;
}

body.fx-debug-mode .bg-canvas,
body.fx-debug-mode .arc-canvas,
body.fx-debug-mode .starfield-canvas,
body.fx-debug-mode .clouds-canvas,
body.fx-debug-mode .hills-canvas,
body.fx-debug-mode .hill-unicorn-canvas,
body.fx-debug-mode .fireworks-canvas {
    image-rendering: auto;
}

body.fx-debug-mode.fx-lowres .bg-canvas,
body.fx-debug-mode.fx-lowres .arc-canvas,
body.fx-debug-mode.fx-lowres .starfield-canvas,
body.fx-debug-mode.fx-lowres .clouds-canvas,
body.fx-debug-mode.fx-lowres .hills-canvas,
body.fx-debug-mode.fx-lowres .hill-unicorn-canvas,
body.fx-debug-mode.fx-lowres .fireworks-canvas {
    image-rendering: -moz-crisp-edges;
    image-rendering: pixelated;
}

body.fx-hd-off .bg-canvas,
body.fx-hd-off .arc-canvas,
body.fx-hd-off .starfield-canvas,
body.fx-hd-off .clouds-canvas,
body.fx-hd-off .hills-canvas,
body.fx-hd-off .hill-unicorn-canvas,
body.fx-hd-off .fireworks-canvas {
    image-rendering: auto;
}
