/* ================================================================
   site-fx.css — shared interaction layer for every non homepage
   Custom cursor, 3D scroll reveals, scroll progress bar.
   No GSAP. No hardcoded colours. Theme driven end to end.

   Colour policy: every colour resolves from the site theme tokens.
   The generated pages (pages.css) expose --ink / --line / --line-2.
   The portfolio pages (nova.css) expose --text-main / --border /
   --border-hover instead, so each token below falls back through the
   sibling token and finally to currentColor. No hex is ever written
   here, and both themes recompute automatically when data-theme flips.
   ================================================================ */

:root {
    --fx-ink:    var(--ink, var(--text-main, currentColor));
    --fx-bg:     var(--bg, var(--surface, transparent));
    --fx-line:   var(--line, var(--border, currentColor));
    --fx-line-2: var(--line-2, var(--border-hover, currentColor));
    --fx-ease:   cubic-bezier(0.16, 1, 0.3, 1);

    /* Soft halo in the page background colour. Keeps the cursor readable when
       it crosses a photo, which the ink colour alone cannot guarantee. Still
       token only, so it flips with the theme like everything else. */
    --fx-halo:   0 0 6px 1px color-mix(in srgb, var(--fx-bg) 65%, transparent);
}

/* ── 1. CUSTOM CURSOR ───────────────────────────────────────────
   Both elements are pointer-events:none so they can never intercept
   a click. The theme toggle at bottom right keeps every one of its
   clicks even though the cursor paints above it. */

.fx-cursor-dot,
.fx-cursor-ring {
    position: fixed;
    top: 0;
    left: 0;
    border-radius: 50%;
    pointer-events: none;
    opacity: 0;
    contain: layout style size;
    will-change: transform, opacity;
}

.fx-cursor-dot {
    width: 8px;
    height: 8px;
    margin: -4px 0 0 -4px;
    background: var(--fx-ink);
    box-shadow: var(--fx-halo);
    z-index: 999999;
    transition: opacity 0.25s ease, background 0.3s ease;
}

.fx-cursor-ring {
    width: 40px;
    height: 40px;
    margin: -20px 0 0 -20px;
    border: 1.5px solid var(--fx-ink);
    box-shadow: var(--fx-halo);
    z-index: 999998;
    transition: opacity 0.25s ease,
                width 0.3s var(--fx-ease),
                height 0.3s var(--fx-ease),
                margin 0.3s var(--fx-ease),
                border-color 0.3s ease,
                background-color 0.3s ease;
}

.fx-cursor-dot.fx-visible,
.fx-cursor-ring.fx-visible { opacity: 1; }

/* Hover state over anything interactive: ring opens up, dot shrinks. */
.fx-cursor-ring.fx-hot {
    width: 60px;
    height: 60px;
    margin: -30px 0 0 -30px;
    border-color: var(--fx-ink);
    background-color: color-mix(in srgb, var(--fx-ink) 8%, transparent);
}

.fx-cursor-dot.fx-hot { opacity: 0.35; }

/* Click feedback. */
.fx-cursor-ring.fx-down {
    width: 30px;
    height: 30px;
    margin: -15px 0 0 -15px;
}

/* Native cursor is hidden ONLY while .fx-cursor-on is on <html>, and the
   script only sets that class once the custom cursor is genuinely running
   on a fine pointer. It is removed the instant the cursor is torn down, so
   a user is never left with no visible pointer. */
html.fx-cursor-on,
html.fx-cursor-on body,
html.fx-cursor-on * { cursor: none !important; }

/* Text entry always keeps its native caret affordance. There are no form
   fields on these pages today, but the contact page is where one would land,
   and losing the I-beam in a text field is a real usability regression. */
html.fx-cursor-on textarea,
html.fx-cursor-on [contenteditable="true"],
html.fx-cursor-on input[type="text"],
html.fx-cursor-on input[type="email"],
html.fx-cursor-on input[type="tel"],
html.fx-cursor-on input[type="url"],
html.fx-cursor-on input[type="search"],
html.fx-cursor-on input[type="password"],
html.fx-cursor-on input[type="number"],
html.fx-cursor-on input:not([type]) { cursor: auto !important; }

/* ...and the custom cursor steps aside so there is never a doubled pointer. */
html.fx-cursor-on.fx-text .fx-cursor-dot,
html.fx-cursor-on.fx-text .fx-cursor-ring { opacity: 0 !important; }

/* Retire a legacy GSAP cursor while ours is running, so a page can never
   show two cursors at once. Scoped to .fx-cursor-on on purpose: if our
   cursor stands down (touch, reduced motion) the legacy one comes straight
   back and the page is left exactly as it was. */
html.fx-cursor-on .cursor,
html.fx-cursor-on .cursor-ring { display: none !important; }

/* ── 2. SCROLL REVEALS ──────────────────────────────────────────
   .fx-on is added to <html> by the script only when reveals are
   actually wired up, so nothing can be left stranded at opacity 0
   if the script never runs. */

html.fx-on .fx-reveal {
    opacity: 0;
    transform: perspective(900px) translateY(26px) rotateX(7deg);
    transform-origin: 50% 100%;
    transition: opacity 0.62s var(--fx-ease), transform 0.62s var(--fx-ease);
    will-change: opacity, transform;
}

html.fx-on .fx-reveal.fx-in {
    opacity: 1;
    transform: none;
    will-change: auto;
}

/* Clean takeover of the existing .rise system. pages.css already sets
   .rise{opacity:0} and .rise.in{opacity:1}; its own observer still adds
   .in. We only upgrade the arrival transform and let the script add a
   stagger delay, so .rise elements are never double animated and never
   depend on this file to become visible. */
html.fx-on .rise {
    transform: perspective(900px) translateY(24px) rotateX(6deg);
    transform-origin: 50% 100%;
}

html.fx-on .rise.in { transform: none; }

/* ── 3. SCROLL PROGRESS BAR ─────────────────────────────────────
   Sits at the very top of the viewport. The theme toggle lives at the
   bottom right, so there is no spatial overlap at all, and the bar is
   pointer-events:none regardless. */

.fx-progress {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 2px;
    z-index: 9990;
    pointer-events: none;
    background: transparent;
}

.fx-progress-bar {
    display: block;
    width: 100%;
    height: 100%;
    background: var(--fx-ink);
    opacity: 0.85;
    transform: scaleX(0);
    transform-origin: 0 50%;
    will-change: transform;
}

/* ── 4. REDUCED MOTION ──────────────────────────────────────────
   The cursor is never built at all under reduce (handled in JS).
   These rules are the belt and braces for the reveal layer. */

@media (prefers-reduced-motion: reduce) {
    .fx-cursor-dot,
    .fx-cursor-ring { display: none !important; }

    html.fx-cursor-on,
    html.fx-cursor-on body,
    html.fx-cursor-on * { cursor: auto !important; }

    html.fx-on .fx-reveal,
    html.fx-on .rise {
        opacity: 1 !important;
        transform: none !important;
        transition: none !important;
    }

    .fx-progress-bar { transition: none !important; }
}

/* Coarse pointers never get the custom cursor. Redundant with the JS
   gate, kept so a stale class can never blank the pointer on touch. */
@media (pointer: coarse), (hover: none) {
    .fx-cursor-dot,
    .fx-cursor-ring { display: none !important; }

    html.fx-cursor-on,
    html.fx-cursor-on body,
    html.fx-cursor-on * { cursor: auto !important; }
}
