* { margin: 0; padding: 0; box-sizing: border-box; }

/* ════════════════════════════════════════════════════════════════
   GLOBAL CSS DEFAULTS
   Override per-layer via inline style="" on a .layer element.
   JS behaviour defaults live in window.LAYER_CONFIG in layers.html.
   ════════════════════════════════════════════════════════════════ */
:root {
    --vignette-intensity: 0.85;  /* 0–1: overall darkness of the radial vignette */
    --vignette-spread:    75%;   /* How far the darkness extends inward from the edge */
    --kb-drift-speed:     6s;    /* Duration of one Ken Burns pan/zoom loop cycle */
    --base-transition:    1.5s;  /* Entry animation speed for zoom-in and fade */
}

html { 
    scroll-snap-type: y mandatory; 
}
.layer { 
    scroll-snap-align: start; 
}

body { background: #000; color: #fff; font-family: 'Inter', sans-serif; overflow-x: hidden; }

.layer {
    position: relative;
    height: 100vh;
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
}

/* ── VIGNETTE ENGINE ─────────────────────────────────────────────
   Rendered as a pseudo-element so it never interferes with content
   or bg-image z-ordering. Intensity and spread are tunable per-layer
   via CSS variable overrides on the .layer element itself. */
.layer::after {
    content: '';
    position: absolute;
    inset: 0;
    background: radial-gradient(
        circle,
        transparent 20%,
        rgba(0,0,0, calc(var(--vignette-intensity) * 0.5)) var(--vignette-spread),
        rgba(0,0,0, var(--vignette-intensity)) 100%
    );
    pointer-events: none;
    z-index: 1;
}

/* ── BACKGROUND IMAGE ────────────────────────────────────────────
   Oversized to give parallax and zoom room to move without
   revealing the layer background behind it.
   NOTE: filter is intentionally absent from transition-property.
   Blur is scroll-driven and recalculated every rAF frame — a CSS
   transition would cause it to lag far behind the scroll position
   by its full duration, making the effect appear broken. */
.bg-image {
    position: absolute;
    top: -20%; left: -20%; width: 140%; height: 140%;
    background-size: cover;
    z-index: 0;
    will-change: transform, filter;
    /* --zoom-speed can be set as an inline CSS variable on any individual .bg-image
       to override the global --base-transition for that layer's zoom entry only.
       Falls back to --base-transition when not set, so unset layers are unaffected. */
    transition: transform var(--zoom-speed, var(--base-transition)) cubic-bezier(0.16, 1, 0.3, 1);
}

/* ── ANIMATED ELEMENTS ───────────────────────────────────────────  */
.anim-el {
    opacity: 0;
    will-change: transform, opacity;
    transition: transform var(--base-transition) cubic-bezier(0.16, 1, 0.3, 1),
                opacity   var(--base-transition) ease;
}

/* ── LOADING STATE ───────────────────────────────────────────────
   Suppresses all transitions and animations while JS sets initial
   transforms, preventing a visible flash where elements animate
   from their offset positions the moment the page paints. */
[data-state="loading"] .anim-el,
[data-state="loading"] .bg-image {
    transition: none !important;
    animation:  none !important;
}

/* ── ACTIVE STATES ───────────────────────────────────────────────  */
.layer.active .anim-el {
    opacity: 1 !important;
    /* --parallax-y is written by the JS render loop so the scroll offset
       composes with this reset transform rather than fighting it. */
    transform: translate(0, 0) scale(1) translateY(var(--parallax-y, 0px)) !important;
}

.layer.active .bg-image[data-bg-effect="zoom"] { transform: scale(1); }

/* ── INITIAL BG STATES ───────────────────────────────────────────  */
.bg-image[data-bg-effect="zoom"] { transform: scale(1.3); }

.bg-image[data-bg-effect="KB"] {
    transform: scale(1.1);
    animation: kb-drift var(--kb-drift-speed) infinite alternate ease-in-out;
}

@keyframes kb-drift {
    0%   { transform: scale(1.1)  translate(0,    0);    }
    100% { transform: scale(1.2) translate(-4%, -1%); }
}

/* ── SCROLL NAV ──────────────────────────────────────────────────  */
.scroll-nav {
    position: fixed;
    /* Fix #4: env() keeps the nav clear of notch/Dynamic Island on iOS */
    right: max(2rem, calc(env(safe-area-inset-right, 0px) + 1rem));
    top: 2.5rem;
    z-index: 100;
    display: flex;
    flex-direction: column;
    gap: 1.5rem;
}

.dot {
    /* Visual size: 8×8px */
    width: 8px; height: 8px;
    /* Border uses solid white — opacity on the element itself controls
       overall transparency so the effect is clearly visible. */
    border: 2px solid #fff;
    border-radius: 50%;
    cursor: pointer;
    opacity: 0.6;
    transition: background 0.4s, border-color 0.4s, transform 0.4s, opacity 0.4s;
    /* Fix #2: Invisible padding expands the tap target to ~44×44px
       (Apple HIG / WCAG minimum) without affecting the visual or layout. */
    padding: 16px;
    margin: -16px;
    box-sizing: content-box;
}

.dot.active { background: #fff; border-color: #fff; transform: scale(1.5); opacity: 0.6; }

/* ── TYPOGRAPHY ──────────────────────────────────────────────────  */
h1 { font-size: clamp(3rem, 10vw, 6rem); margin: 5px; text-transform: uppercase; font-weight: 900; line-height: 1; text-shadow: 2px 1px #3d3d3d;}
p  { font-size: 1.2rem; margin: 5px; opacity: 0.6; text-shadow: 2px 1px #3d3d3d;}
a {
  color: #ebedf9; /* Changes the link text color */
  font-family: 'Inter', sans-serif; /* Sets the font family */
  font-weight: bold; /* Makes the text bold */
  text-shadow: 2px 1px #3d3d3d;
}

/* ── RESPONSIVE ──────────────────────────────────────────────────  */
@media (max-width: 768px) {
    .scroll-nav {
        right: max(1rem, calc(env(safe-area-inset-right, 0px) + 0.5rem));
        gap: 1rem;
    }
    p { font-size: 1rem; text-shadow: 1px 1px #3d3d3d;}
}