/* Breakpoints used throughout this file:
   phone   < 480px  (default, mobile-first)
   tablet  >= 481px
   laptop  >= 769px
   desktop >= 1200px
*/

/* Seamless cross-page navigation (Chrome/Edge 126+; no-op elsewhere —
   pure progressive enhancement). Every page shares this stylesheet, so
   the default crossfade applies to every same-origin navigation site-wide,
   including clicking a picture in a zero-chrome scene. */
@view-transition {
    navigation: auto;
}

:root {
    --void: #08080b;
    --void-soft: #121216;
    --bone: #d9d4c4;
    --ash: #837c6d;
    --blood: #3d0a5c;
    --blood-bright: #9a2ee6;
    --success: #3d5c3d;
    --error: #3d0a5c;
    --radius: 2px;
    --container-width: 1100px;
    --mono: "Consolas", "Courier New", ui-monospace, monospace;
    --gothic: "UnifrakturMaguntia", "Old English Text MT", serif;
}

/* Self-hosted (SIL Open Font License, free for commercial use — see
   app/static/fonts/) so this doesn't need a Google Fonts CDN allowance in
   the CSP (font-src is 'self' only, see app/__init__.py). Used for
   headings only — genuine blackletter reads poorly at body-text sizes, so
   paragraphs/buttons/UI stay on --mono. */
@font-face {
    font-family: "UnifrakturMaguntia";
    src: url("../fonts/UnifrakturMaguntia.woff2") format("woff2");
    font-weight: 400;
    font-style: normal;
    font-display: swap;
}

* {
    box-sizing: border-box;
}

html {
    -webkit-text-size-adjust: 100%;
}

body {
    margin: 0;
    font-family: var(--mono);
    background: var(--void);
    color: var(--bone);
    line-height: 1.6;
}

@media (pointer: fine) {
    body {
        cursor: none;
    }

    /* Native video controls (scrub bar, volume slider) need real,
       precise cursor feedback while dragging — the synthetic follow-cursor
       can't track drags inside the browser's own control widget. */
    video[controls] {
        cursor: auto;
    }
}

/* Full-screen, no-scroll scene pages (bunker landing, portfolio, contact):
   the photo fills the entire viewport, nothing scrolls. Plain pages
   (privacy, cookies) don't get this class and scroll normally. */
body.no-scroll {
    height: 100dvh;
    overflow: hidden;
}

body.no-scroll #main-content {
    max-width: none;
    min-height: 0;
    height: 100dvh;
    margin: 0;
    padding: 0;
}

img, svg {
    max-width: 100%;
    height: auto;
}

h1, h2, h3 {
    font-family: var(--gothic);
    color: var(--bone);
    font-weight: 400;
    letter-spacing: 0.02em;
}

a {
    color: var(--bone);
}

.skip-link {
    position: absolute;
    left: -9999px;
    top: 0;
    background: var(--void-soft);
    color: var(--bone);
    padding: 0.5rem 1rem;
    z-index: 9500;
}
.skip-link:focus {
    left: 0.5rem;
    top: 0.5rem;
}

.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
}

/* Screen texture: subtle scanlines + grain over the whole viewport,
   including UI chrome — an intentional "looking through a CRT" effect. */
body::before {
    content: "";
    position: fixed;
    inset: 0;
    pointer-events: none;
    z-index: 9000;
    opacity: 0.3;
    background: repeating-linear-gradient(
        0deg,
        rgba(255, 255, 255, 0.02) 0px,
        rgba(255, 255, 255, 0.02) 1px,
        transparent 1px,
        transparent 3px
    );
}

body::after {
    content: "";
    position: fixed;
    inset: 0;
    pointer-events: none;
    z-index: 9001;
    opacity: 0.04;
    mix-blend-mode: overlay;
    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='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* Custom cursor: solid block by default, expands with a hollow center
   over anything clickable. Desktop/mouse only — untouched on touch. */
#custom-cursor {
    position: fixed;
    top: 0;
    left: 0;
    width: 14px;
    height: 14px;
    background: var(--bone);
    pointer-events: none;
    z-index: 9999;
    transform: translate(-50%, -50%);
    transition: width 0.18s ease, height 0.18s ease, background-color 0.18s ease;
}

#custom-cursor::after {
    content: "";
    position: absolute;
    inset: 0;
    margin: auto;
    width: 0;
    height: 0;
    background: var(--void);
    transition: width 0.18s ease, height 0.18s ease;
}

#custom-cursor.is-hover {
    width: 46px;
    height: 46px;
    background: var(--blood-bright);
}

#custom-cursor.is-hover::after {
    width: 24px;
    height: 24px;
}

#custom-cursor.is-active {
    width: 10px;
    height: 10px;
}

#custom-cursor.is-active::after {
    width: 0;
    height: 0;
}

#custom-cursor.is-native {
    opacity: 0;
}

/* Main content */
main {
    max-width: var(--container-width);
    margin: 0 auto;
    padding: 1.5rem 1.25rem 3rem;
    min-height: 60vh;
}

@media (min-width: 481px) {
    main {
        padding: 2rem 1.5rem 4rem;
    }
}

.page-header {
    margin-bottom: 2rem;
}

.prose {
    max-width: 65ch;
}

.btn {
    display: inline-block;
    background: transparent;
    color: var(--bone);
    padding: 0.7rem 1.4rem;
    border-radius: var(--radius);
    border: 1px solid var(--bone);
    text-decoration: none;
    cursor: none;
    font-size: 0.85rem;
    font-family: var(--mono);
    text-transform: uppercase;
    letter-spacing: 0.15em;
    transition: border-color 0.2s ease, color 0.2s ease, box-shadow 0.2s ease;
}

.btn:hover {
    border-color: var(--blood-bright);
    color: var(--blood-bright);
}

.btn-small {
    padding: 0.4rem 0.9rem;
    font-size: 0.75rem;
}

.btn-secondary {
    background: transparent;
    color: var(--ash);
    border: 1px solid var(--ash);
}

/* Flash messages */
.flash-container {
    margin-bottom: 1.5rem;
}

.flash {
    padding: 0.8rem 1rem;
    border-radius: var(--radius);
    margin-bottom: 0.5rem;
    border: 1px solid currentColor;
    font-size: 0.9rem;
}

.flash-success {
    background: rgba(61, 92, 61, 0.15);
    color: #7fae7f;
}

.flash-error {
    background: rgba(61, 10, 92, 0.15);
    color: var(--blood-bright);
}

/* Cookie banner */
.cookie-banner {
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    background: var(--void-soft);
    color: var(--bone);
    padding: 1rem 1.25rem;
    box-shadow: 0 -2px 12px rgba(0, 0, 0, 0.5);
    z-index: 9500;
    border-top: 1px solid #232228;
}

.cookie-banner p {
    margin: 0 0 0.75rem;
    font-size: 0.85rem;
}

.cookie-banner-actions {
    display: flex;
    gap: 0.75rem;
    flex-wrap: wrap;
}

.cookie-banner[hidden] {
    display: none;
}

@media (min-width: 481px) {
    .cookie-banner {
        bottom: 1rem;
        left: 1rem;
        right: auto;
        max-width: 420px;
        border-radius: var(--radius);
    }
}

/* Flicker text — used sparingly for atmosphere */
@keyframes flicker {
    0%, 89%, 91%, 100% { opacity: 1; }
    90% { opacity: 0.4; }
}

.flicker {
    animation: flicker 6s infinite;
}

@media (prefers-reduced-motion: reduce) {
    .flicker {
        animation: none;
    }
}

/* "click to enter" gate: browsers won't play audio with sound until the
   page has had a user gesture, so the first scene of a session shows this
   full-screen — see app/static/js/ambient-sound.js. Deliberately blocking
   (covers the whole scene, catches the click itself) so that first click
   only dismisses the gate instead of also landing on whatever hotspot sits
   underneath it and navigating away immediately. */
.enter-gate {
    position: fixed;
    inset: 0;
    z-index: 500;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--void);
    cursor: none;
    opacity: 1;
    transition: opacity 0.6s ease;
}

.enter-gate.enter-gate-leaving {
    opacity: 0;
    pointer-events: none;
}

.enter-gate-text {
    margin: 0;
    color: var(--ash);
    font-family: var(--mono);
    font-size: 0.75rem;
    text-transform: uppercase;
    letter-spacing: 0.28em;
}

.enter-gate-credit {
    position: absolute;
    bottom: 2rem;
    left: 50%;
    transform: translateX(-50%);
    margin: 0;
    max-width: 26rem;
    padding: 0 1.5rem;
    color: var(--ash);
    opacity: 0.5;
    font-family: var(--mono);
    font-size: 0.6rem;
    text-align: center;
    letter-spacing: 0.05em;
    text-transform: none;
}

/* All full-screen scenes share the same geometry: fill #main-content
   edge-to-edge, photo as cover background, clip anything that doesn't fit
   rather than scroll. */
.bunker-scene,
.portfolio-scene,
.contact-scene,
.passage-scene,
.cult-scene {
    position: relative;
    width: 100%;
    height: 100%;
    overflow: hidden;
    background-size: cover;
    background-position: center;
}

/* Bunker landing scene — real photo (app/static/images/scenes/bunker-door.jpg).
   No text/heading here by design — just the photo and two hotspots. */
.bunker-scene {
    background-image: url("../images/scenes/bunker-door.jpg");
}

/* Tunnel, between the bunker door and the fork: step 1 is the fork itself
   (tunnel.jpg, two hotspots below), step 2 is the right-path continuation
   (tunnel-2.jpg, plain click-anywhere .scene-link) into the portfolio. */
.passage-scene[data-step="1"] {
    background-image: url("../images/scenes/tunnel.jpg");
}

.passage-scene[data-step="2"] {
    background-image: url("../images/scenes/tunnel-2.jpg");
}

/* Cult scene — left-path destination from the tunnel fork
   (app/static/images/scenes/cult.jpg). NPC reappears here (same markup as
   index.html); background click (.scene-link) returns to the bunker. */
.cult-scene {
    background-image: url("../images/scenes/cult.jpg");
}

.cult-scene::after {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    background: radial-gradient(ellipse at center, transparent 40%, rgba(0, 0, 0, 0.8) 100%);
}

/* Hotspot over the actual dark doorway in bunker-door.jpg. Traced against
   that specific photo — "cover" crops differently per viewport aspect
   ratio, so this lines up closely but not pixel-perfectly at every screen
   size. Retrace these percentages if the photo is replaced.

   Fully invisible by design, no hover glow — the custom cursor's
   shape-change on hover is the only feedback that this is clickable. */
.bunker-opening {
    position: absolute;
    left: 37%;
    top: 40%;
    width: 26%;
    height: 32%;
    text-decoration: none;
    color: transparent;
}

/* Second hotspot: an invisible marker over dark sky/canopy gap
   (upper-left of bunker-door.jpg), leads to /contact. Same as
   .bunker-opening — no visible glow, cursor-only feedback. */
.bunker-panel {
    position: absolute;
    left: 5%;
    top: 3%;
    width: 22%;
    height: 18%;
    text-decoration: none;
    color: transparent;
}

/* Tunnel hotspots (tunnel.jpg / tunnel-2.jpg): positioned by
   app/static/js/scene-hotspots.js from data-hotspot fractions traced
   against the real archway/tunnel-mouth/door in each photo — same pattern
   and cover-crop-aware positioning as .bunker-opening above. Invisible by
   design, cursor-only feedback. */
.passage-hotspot {
    position: absolute;
    text-decoration: none;
    color: transparent;
}

/* Return-to-bunker hotspot: covers the scene background so clicking
   anywhere that isn't a foreground element (card, info box) navigates
   home. Sits behind that foreground content in stacking order. */
.scene-link {
    position: absolute;
    inset: 0;
    z-index: 0;
}

/* NPC encounter: a figure sized off viewport height so he reads as
   standing in the scene rather than pasted on top of it. Right-anchored by
   default (bunker exterior — never overlaps .bunker-opening or
   .bunker-panel, both left-of-center); the cult scene mirrors him to the
   left instead, below. Starts invisible/inert —
   app/static/js/npc-dialogue.js fades him in; .npc-visible/.npc-leaving
   are toggled from there. */
.npc-wrap {
    position: absolute;
    right: 3%;
    bottom: 0;
    height: min(78vh, 800px);
    z-index: 5;
    opacity: 0;
    transform: translateY(16px);
    transition: opacity 0.9s ease, transform 0.9s ease;
    pointer-events: none;
}

/* npcSecondDialoguePart1.png has his body touching the very left edge of
   the source photo (no margin) — an abrupt crop with nothing CSS can
   restore. Sized up and pulled left past the scene's own left edge (the
   scene has overflow:hidden) so that rough edge sits off-screen instead of
   showing mid-frame; the rest of him stays comfortably in view. */
.cult-scene .npc-wrap {
    right: auto;
    left: -6%;
    height: min(94vh, 950px);
}

.npc-wrap.npc-visible {
    opacity: 1;
    transform: translateY(0);
    pointer-events: auto;
}

.npc-wrap.npc-leaving {
    opacity: 0;
    transform: translateY(10px);
}

.npc-figure {
    display: block;
    height: 100%;
    width: auto;
    max-width: none;
    /* npc.png has ~6.93% transparent padding below his feet (measured from
       the source file) — shifting the rendered image down by that same
       percentage of its own height puts his actual feet at the scene
       floor instead of floating above it, at any render size. */
    transform: translateY(6.93%);
    filter: drop-shadow(0 20px 30px rgba(0, 0, 0, 0.6));
    pointer-events: none;
}

.npc-speech {
    position: absolute;
    right: -12px;
    bottom: 100%;
    margin-bottom: 14px;
    width: min(300px, 62vw);
    background: rgba(8, 8, 11, 0.82);
    backdrop-filter: blur(4px);
    -webkit-backdrop-filter: blur(4px);
    border: 1px solid rgba(154, 46, 230, 0.5);
    border-radius: var(--radius);
    padding: 0.9rem 1rem;
    font-size: 0.85rem;
    line-height: 1.5;
    box-shadow: 0 12px 32px rgba(0, 0, 0, 0.55);
}

.npc-speech::after {
    content: "";
    position: absolute;
    top: 100%;
    right: 28px;
    width: 10px;
    height: 10px;
    background: inherit;
    border-right: 1px solid rgba(154, 46, 230, 0.5);
    border-bottom: 1px solid rgba(154, 46, 230, 0.5);
    transform: rotate(45deg) translateY(-5px);
}

.npc-speech-close {
    position: absolute;
    top: 0.4rem;
    right: 0.5rem;
    background: none;
    border: none;
    color: var(--ash);
    font-size: 1.1rem;
    line-height: 1;
    cursor: none;
    padding: 0.2rem;
}

.npc-speech-close:hover,
.npc-speech-close:focus-visible {
    color: var(--blood-bright);
    outline: none;
}

.npc-speech-text {
    margin: 0 1.1rem 0.7rem 0;
    min-height: 2.4em;
    color: var(--bone);
}

.npc-options {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
}

.npc-option {
    background: none;
    border: none;
    text-align: left;
    color: var(--ash);
    font-family: var(--mono);
    font-size: 0.78rem;
    letter-spacing: 0.03em;
    padding: 0.15rem 0;
    cursor: none;
    transition: color 0.2s ease;
}

.npc-option::before {
    content: "\203A\0020";
}

.npc-option:hover,
.npc-option:focus-visible {
    color: var(--blood-bright);
    outline: none;
}

@media (prefers-reduced-motion: reduce) {
    .npc-wrap {
        transition: none;
    }
}

/* Cult-scene reveal cutscene (app/static/js/npc-dialogue.js, playCultReveal):
   the whole cult encounter — he's standing there (left side, above), a
   beat later it cuts to black, then to a close-up of the original photo
   (paired with a voice-over), then black again, then back to the bunker
   exterior. #scene-blackout is a fixed full-viewport layer shared by both
   black beats. */
.scene-blackout {
    position: fixed;
    inset: 0;
    background: #000;
    opacity: 0;
    z-index: 50;
    pointer-events: none;
    transition: opacity 0.6s ease;
}

.scene-blackout.scene-blackout-visible {
    opacity: 1;
    pointer-events: auto;
}

.npc-wrap.npc-outro-closeup {
    position: fixed;
    inset: 0;
    right: auto;
    bottom: auto;
    height: 100vh;
    width: 100vw;
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
    transform: none;
    transition: none;
    z-index: 15;
}

.npc-wrap.npc-outro-closeup .npc-figure {
    /* npc.png's mask sits at ~25% down the full canvas (measured from the
       source file, same kind of measurement as the 6.93%-feet comment
       above). The flex-centered image's own middle (50%) lands at viewport
       center, so shifting down by (50% - 25%) = 25% of the image's own
       height brings the mask to viewport center — true regardless of how
       tall (how zoomed) the image is rendered. */
    height: 200vh;
    width: auto;
    max-width: none;
    transform: translateY(25%);
    filter: none;
}

@media (prefers-reduced-motion: reduce) {
    .scene-blackout {
        transition: none;
    }
}

@media (max-width: 480px) {
    .npc-wrap {
        height: min(60vh, 520px);
        right: 2%;
    }
    .cult-scene .npc-wrap {
        right: auto;
        left: -6%;
        height: min(70vh, 620px);
    }
    .npc-speech {
        width: min(240px, 68vw);
    }
}

/* Portfolio: a real bunker-interior photo backdrop
   (app/static/images/scenes/bunker-interior.jpg) with floating gallery
   cards as individual glass panels over it. Artwork images come from
   app/static/images/gallery/ — see app/gallery.py for the naming
   convention that drives title + order. Locked to one viewport (no
   scroll) — beyond a handful of pieces this needs paging instead. */
.portfolio-scene {
    background-image: url("../images/scenes/bunker-interior.jpg");
}

.portfolio-scene::after {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    background:
        radial-gradient(circle at 50% 0%, rgba(217, 212, 196, 0.1) 0%, transparent 35%),
        radial-gradient(ellipse at center, transparent 40%, rgba(0, 0, 0, 0.82) 100%);
}

/* Contact button pinned top-left of the portfolio scene — the one
   visible nav element on an otherwise zero-interface page. Sits above
   .scene-link (z-index 0) so it catches its own clicks instead of the
   full-background "return to the bunker" link underneath it. */
.scene-contact-link {
    position: absolute;
    top: 1.5rem;
    left: 1.5rem;
    z-index: 5;
}

/* The gallery grid/cards themselves are a React app (frontend/, built
   into app/static/dist/) — this just positions its mount point above the
   scene-link and fills the available space. See portfolio.html. */
#portfolio-root {
    position: relative;
    z-index: 1;
    height: 100%;
    width: 100%;
}

/* Contact: gothic glass panel over a real forest photo
   (app/static/images/scenes/forest.jpg). */
.contact-scene {
    background-image: url("../images/scenes/forest.jpg");
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1.5rem;
}

.contact-scene::after {
    content: "";
    position: absolute;
    inset: 0;
    background: radial-gradient(ellipse at center, transparent 30%, rgba(0, 0, 0, 0.88) 100%);
    pointer-events: none;
}

.info-box {
    position: relative;
    z-index: 1;
    display: flex;
    flex-direction: column;
    width: min(100%, 1200px);
    max-height: min(92vh, 860px);
    background: rgba(8, 8, 11, 0.5);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
    border: 1px solid rgba(61, 10, 92, 0.5);
    box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
}

.social-links {
    display: flex;
    gap: 1.25rem;
    list-style: none;
    margin: 0;
    padding: 1rem 2rem;
    border-bottom: 1px solid var(--blood);
}

.social-links a {
    display: flex;
    color: var(--ash);
    transition: color 0.2s ease;
}

.social-links a:hover,
.social-links a:focus-visible {
    color: var(--blood-bright);
}

.social-links svg {
    width: 1.25rem;
    height: 1.25rem;
    fill: currentColor;
}

.info-columns {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
}

@media (min-width: 700px) {
    .info-columns {
        flex-direction: row;
    }
}

.info-left,
.info-right {
    padding: 2rem;
    overflow-y: auto;
    min-height: 0;
}

.info-left {
    flex: 1 1 50%;
    border-bottom: 1px solid var(--blood);
}

.info-right {
    flex: 1 1 50%;
}

@media (min-width: 700px) {
    .info-left {
        flex: 0 0 58%;
        border-bottom: none;
        border-right: 1px solid var(--blood);
    }
    .info-right {
        flex: 0 0 42%;
    }
}

.info-left h2,
.info-right h2 {
    font-size: 1rem;
    margin-top: 0;
}

.about-segment,
.inspo-segment {
    display: flex;
    flex-direction: column;
}

.inspo-segment {
    margin-top: 1.5rem;
    padding-top: 1.5rem;
    border-top: 1px solid var(--blood);
}

.inspo-artist {
    margin-top: 1.25rem;
    padding-top: 1.25rem;
    border-top: 1px solid rgba(61, 10, 92, 0.4);
}

.inspo-artist:first-child {
    margin-top: 0.75rem;
    padding-top: 0;
    border-top: none;
}

.inspo-artist h3 {
    font-size: 0.85rem;
    margin: 0 0 0.5rem;
    color: var(--blood-bright);
}

.inspo-video {
    position: relative;
    width: 100%;
    aspect-ratio: 16 / 9;
    margin-top: 0.75rem;
    border: 1px solid var(--blood);
    overflow: hidden;
}

.inspo-video iframe {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    border: none;
}

.photo-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 0.5rem;
    margin-top: 1rem;
}

.photo-grid-item {
    aspect-ratio: 1;
    overflow: hidden;
    border: 1px solid var(--blood);
}

.photo-grid-item img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    transition: transform 0.3s ease;
}

.photo-grid-item img:hover {
    transform: scale(1.06);
}

.experience-divider {
    margin-top: 1.5rem;
    padding-top: 1rem;
    border-top: 1px solid var(--blood);
}

.experience-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
}

.experience-list li {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
}

.experience-list strong {
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    line-height: 1.3;
}

.experience-list span {
    color: var(--ash);
    font-size: 0.65rem;
}

.experience-note {
    font-style: italic;
}

.experience-skills {
    color: var(--ash);
    font-size: 0.65rem;
    letter-spacing: 0.02em;
    text-transform: none;
}

.contact-bar {
    display: flex;
    flex-wrap: wrap;
    gap: 1.5rem;
    padding: 1rem 2rem;
    border-top: 1px solid var(--blood);
}

.contact-bar-item {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    color: var(--ash);
    text-decoration: none;
    font-size: 0.85rem;
    transition: color 0.2s ease;
}

.contact-bar-item:hover,
.contact-bar-item:focus-visible {
    color: var(--blood-bright);
}

.contact-bar-item svg {
    width: 1.1rem;
    height: 1.1rem;
    flex-shrink: 0;
    fill: currentColor;
}
