/* Effects layer for the reconstructed aael24.com.
   Everything here sits on top of the site runtime, which owns the rest of the DOM. */

/* ---------- preloader ---------- */
#fx-preloader {
  position: fixed;
  inset: 0;
  z-index: 100000;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #fafafa;            /* the page's own field */
  will-change: opacity;
}

/* Fades rather than slides away: the mark it leaves behind is the same mark, at
   the same size in the same place, so nothing should appear to move. */
#fx-preloader.fx-leaving {
  transition: opacity 0.6s ease;
  opacity: 0;
  pointer-events: none;
}

/* The mark draws itself here, so it needs no entrance of its own — the first
   stroke appearing is the entrance. The filled mark waits underneath at zero
   and takes over once the drawing is complete. */
#fx-logo {
  position: relative;
  width: 116px;
  height: 116px;
}

#fx-logo svg,
#fx-logo img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}

#fx-logo img {
  opacity: 0;
  transition: opacity 0.26s ease;
}

#fx-logo.fx-logo-set svg { opacity: 0; transition: opacity 0.26s ease; }
#fx-logo.fx-logo-set img { opacity: 1; }

/* Hold the page still while the preloader is up. */
html.fx-locked,
html.fx-locked body { overflow: hidden; }

/* ---------- word reveal ---------- */
/* No overflow:hidden here on purpose. The runtime gives these captions a
   line-height far smaller than their font-size (an 18px box at 40px type), so a
   clipping mask decapitates the glyphs — AAEL renders as "A A |- I". The words
   still rise into place, they just are not clipped on the way. */
.fx-mask {
  display: inline-block;
  vertical-align: top;
  line-height: inherit;
}

.fx-word {
  display: inline-block;
  transform: translateY(0.5em);
  opacity: 0;
  will-change: transform, opacity;
  transition: transform 1.1s cubic-bezier(0.22, 1, 0.36, 1),
              opacity 1.1s cubic-bezier(0.22, 1, 0.36, 1);
}

.fx-word.fx-in {
  transform: translateY(0);
  opacity: 1;
}

/* ---------- glass bands ---------- */
/* Matches gionatannese.com: pure vertical refraction, no blur, no tint and no
   specular edge — the sag profile already fades to nothing at the inner edge,
   so no mask is needed either. backdrop-filter is set from JS, which sizes the
   displacement map to the viewport. */
.fx-glass {
  position: fixed;
  left: 0;
  width: 100%;
  height: var(--fx-band-h, 8vh);
  /* under the site's fixed captions (z 9999), above the imagery */
  z-index: 9998;
  pointer-events: none;
}

.fx-glass--top { top: 0; }
.fx-glass--bottom { bottom: 0; }

/* Safari path: no SVG displacement available, so soften the band towards the
   page instead of leaving a hard-edged blurred rectangle. */
.fx-glass--fallback.fx-glass--top {
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 30%, transparent 100%);
  mask-image: linear-gradient(to bottom, #000 0%, #000 30%, transparent 100%);
}

.fx-glass--fallback.fx-glass--bottom {
  -webkit-mask-image: linear-gradient(to top, #000 0%, #000 30%, transparent 100%);
  mask-image: linear-gradient(to top, #000 0%, #000 30%, transparent 100%);
}

#fx-glass-defs {
  position: absolute;
  width: 0;
  height: 0;
  pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
  .fx-word { transform: none; opacity: 1; }
  #fx-preloader.fx-leaving { transition-duration: 0.2s; }
}

/* ---------- WebGL layer (effects-gallery.js) ---------- */
/* Sits above the page imagery but below the fixed captions, mirroring how the
   reference site keeps its navbar in the DOM on top of the canvas. */
#fx-canvas {
  position: fixed;
  inset: 0;
  /* Size comes from visualViewport in JS — on iOS neither vh, dvh nor lvh match
     what is actually on screen while the address bar animates, and every one of
     them left bands of background at the edges. */
  width: 100%;
  height: 100vh;
  z-index: 9998;
  pointer-events: none;
  display: block;
}

/* ---------- gallery: cursor label, caption inversion ---------- */

#fx-cursor {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 100000;
  pointer-events: none;
  /* offset from the pointer itself, like the View label on the reference site */
  margin: -26px 0 0 14px;
  font: 400 11px/1 ui-monospace, "SF Mono", Menlo, monospace;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  white-space: nowrap;
  color: #111;
  opacity: 0;
  transition: opacity 0.25s ease, color 0.35s ease;
  will-change: transform;
}

#fx-cursor.is-on { opacity: 1; }

/* Inversion via blend mode, the way the reference site does it: the captions
   are painted white and differenced against whatever is behind them, so they go
   black over a light frame and white over a dark one with no state to track.
   `isolation: auto` on the ancestors is what lets them blend against the canvas
   rather than against an empty stacking context. */
:root.fx-gallery #container p,
:root.fx-gallery #container a,
:root.fx-gallery #container sup,
#fx-cursor {
  color: #fff !important;
  /* difference against the frame behind, same rule as the captions — otherwise
     the label stays white and disappears over a light render */
  mix-blend-mode: difference;
  /* the runtime paints type through -webkit-text-fill-color, which wins over
     `color` — without this the captions ignore the rule entirely */
  -webkit-text-fill-color: #fff !important;
}

/* The blend goes on the fixed wrapper that owns the stacking context, not on the
   text node itself (see markBlendRoots in effects-gallery.js). */
:root.fx-gallery [data-fx-invert] {
  mix-blend-mode: difference;
}

/* The logo is a dark SVG; inverting it first makes difference flip it with the
   frame behind, same as the type. It also needs lifting above the opaque canvas. */
/* The canvas draws the mark itself. The runtime's copy used to be merely hidden
   behind it, which was fine until the canvas started panning away — then its
   twin sat in the middle of the page waiting to be met. */
:root.fx-gallery [data-fx-logo-wrap] {
  display: none !important;
}

/* The runtime parks the copyright line in flow; pin it to the bottom edge. */
#container [data-fx-copyright] {
  position: fixed !important;
  left: 0 !important;
  right: 0 !important;
  top: auto !important;
  /* Above the home indicator, or Safari's floating bar, wherever the page
     runs under the bottom edge (viewport-fit=cover). Zero where it does not. */
  bottom: calc(24px + env(safe-area-inset-bottom, 0px)) !important;
  margin: 0 !important;
  text-align: center;
  z-index: 9999;
}

/* The runtime's own nav link (its wrapper tagged in effects-gallery.js) is
   never shown: the way to the other page is the page's own #fx-nav, at the
   bottom, on every page and screen — see below. Clicks on ours are still
   handed to it, so the runtime's router stays in step. */
#container [data-fx-nav] {
  display: none !important;
}

/* ---------- Teodor (from the reference site) ---------- */
@font-face {
  font-family: "Teodor";
  src: url("/fonts/Teodor-Regular.woff") format("woff");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

/* About's display copy is set in the Light cut; the cursor label stays Regular. */
@font-face {
  font-family: "Teodor";
  src: url("/fonts/Teodor-Light.woff") format("woff");
  font-weight: 300;
  font-style: normal;
  font-display: swap;
}

/* The nav link and the cursor label both use it. */
/* Teodor is for the About page's display copy only; the nav link belongs with
   the other captions. */
#fx-cursor {
  font-family: "Teodor", Georgia, serif !important;
}

:root.fx-gallery #container a,
:root.fx-gallery #container a * {
  font-family: "Neue Haas Display", ui-sans-serif, system-ui, sans-serif !important;
}

#fx-cursor {
  font-size: 15px;
  letter-spacing: 0.01em;
  text-transform: none;
}

/* ---------- Neue Haas Grotesk Display (Roman) ---------- */
/* Supplied as TTF; browsers accept it directly, so no conversion step. */
@font-face {
  font-family: "Neue Haas Display";
  src: url("/fonts/NeueHaasDisplayRoman.ttf") format("truetype");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

/* The captions the runtime renders (AAEL, C24, © 2026) are specced in Neue Haas
   Grotesk Display Pro 55 Roman; the dump ships no @font-face for it. */
#container p,
#container span,
#container sup {
  font-family: "Neue Haas Display", "Neue Haas Grotesk Display Pro", ui-sans-serif, system-ui, sans-serif;
}

/* The pointer itself, while over an actionable frame. */
#fx-dot {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 100000;
  width: 10px;
  height: 10px;
  margin: -5px 0 0 -5px;
  border-radius: 50%;
  background: #fff;
  mix-blend-mode: difference;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.2s ease;
  will-change: transform;
}

#fx-dot.is-on { opacity: 1; }

/* Hide the system cursor only while the dot stands in for it. */
:root.fx-hide-pointer,
:root.fx-hide-pointer body { cursor: none; }

/* On the gallery the glass comes from the WebGL shader, so the backdrop-filter
   bands are a second, conflicting copy of the effect — and effects.js rebuilds
   them on every resize (which on mobile fires constantly as the address bar
   shows and hides). Removing the nodes loses that race; hiding them does not.
   About keeps its bands. */
:root.fx-gallery .fx-glass {
  display: none !important;
}

/* The gallery owns the gesture: without this Safari still rubber-bands, pulls
   to refresh and runs its edge back-swipe over our own scrolling. */
:root.fx-gallery,
:root.fx-gallery body {
  overscroll-behavior: none;
  touch-action: none;
  overflow: hidden;
  height: 100%;
}

/* On a phone the page runs under Safari's bars — measured on the device, not
   guessed: Safari blurs the *document* beyond the viewport into its bottom bar
   and, once something has been scrolled past, into the status bar; it never
   paints a fixed layer there, and a theme-color makes the bars opaque. So on
   touch the document is taller than the viewport by more than the bars, held
   scrolled by a few dozen pixels (effects-gallery.js), and the canvas is part
   of the document rather than fixed to the viewport. Only touch-action holds
   the page, which the phone test showed is enough. */
@media (hover: none) and (pointer: coarse) {
  :root.fx-gallery,
  :root.fx-gallery body {
    overflow: visible;
    height: auto;
  }
  :root.fx-gallery body {
    min-height: calc(100vh + 220px);
    min-height: calc(100lvh + 220px);
  }
  #fx-canvas {
    position: absolute;
    top: 0;
    left: 0;
  }
}

#fx-canvas {
  touch-action: none;
}

/* Pointer chrome is mouse-only; a touch tap should not leave it on screen. */
@media (hover: none), (pointer: coarse) {
  #fx-cursor,
  #fx-dot {
    display: none !important;
  }
  :root.fx-hide-pointer,
  :root.fx-hide-pointer body { cursor: auto; }
}

/* With viewport-fit=cover the page reaches under the notch and home indicator;
   painting these the strip's colour keeps them from reading as bands before the
   canvas draws. */
html, body {
  /* !important: the runtime paints body white after us, and Safari takes the
     page's colour from here for its own bars — one colour, top to bottom.
     --fx-page is set from the gallery's own frame loop (effects-gallery.js),
     so the colour under the bars is the colour of the render on screen. */
  background-color: var(--fx-page, #fafafa) !important;
}

/* The copyright line is gone from both pages: the phone frame reads as three
   things — the labels above, the mark or the copy, the way to the other page
   below — and the desktop had nothing to say there either. */
#container [data-fx-copyright] {
  display: none !important;
}

/* Mobile — Figma frames 1515:4610 (Home) and 1515:4687 (About), one layout
   for both pages: AAEL and C24 in the corners at the top (36px, tracked
   10.8px, 40px in from the sides), the nav link centred at the bottom (36px,
   tracked 1.8px, 40px up), the mark or the copy alone in the middle. The
   runtime lays these out by its desktop metrics and gives the two pages
   different sizes; every rule here is pinned so the pages match. */
@media (max-width: 760px) {
  #container p,
  #container a,
  #container a * {
    font-size: 36px !important;
  }

  /* the side labels keep their monogram tracking */
  #container [data-fx-side],
  #container [data-fx-side] * {
    letter-spacing: 10.8px !important;
  }

  /* Cap height of 36px type sits at 41 in the frame. The runtime gives these
     an 18px line box at 36px type, so the glyphs overhang it by about 4px
     above: the box goes at 45 for the caps to land at 41. */
  #container [data-fx-side] {
    position: fixed !important;
    top: calc(45px + env(safe-area-inset-top, 0px)) !important;
    bottom: auto !important;
    transform: none !important;
    margin: 0 !important;
    width: auto !important;
    text-align: left !important;
  }

  #container [data-fx-side="left"] {
    left: 40px !important;
    right: auto !important;
  }

  #container [data-fx-side="right"] {
    right: 40px !important;
    left: auto !important;
    text-align: right !important;
  }

  /* On the phone the corner labels are the page's own (#fx-labels, built in
     effects.js); the runtime's are hidden so nothing re-renders up there. */
  #container [data-fx-side] {
    display: none !important;
  }
}

/* The way to the other page — ABOUT, HOME, BACK — is the page's own element
   (#fx-nav, effects.js) on every page and screen, centred at the bottom. Ours
   rather than the runtime's for the same reason as the phone's labels, and
   because the runtime's link cannot be anchored to the bottom edge on iOS
   without paint and geometry parting company. Hung from the visible bottom
   (--fx-vh, set in effects.js), never from `bottom`. The box is the word and
   20px of tap target around it, and no wider: a full-width band would take
   the clicks meant for the picture under it. */
#fx-nav {
  display: block;
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  z-index: 9999;
  padding: 20px 28px;
  /* Desktop: the runtime's caption type, 40px tracked 2px; 82px of box ending
     20px above the edge, so the word sits 40px up — as it sat 40px down from
     the top before. */
  top: calc(var(--fx-vh, 100vh) - 102px - env(safe-area-inset-bottom, 0px));
  font: 40px/42px "Neue Haas Display", ui-sans-serif, system-ui, sans-serif;
  letter-spacing: 2px;
  text-align: center;
  text-transform: uppercase;
  text-decoration: none;
  white-space: nowrap;
  color: #161616;
  -webkit-tap-highlight-color: transparent;
}

/* over a render it differences, like every other caption there */
:root.fx-gallery #fx-nav,
:root.fx-project #fx-nav {
  color: #fff;
  mix-blend-mode: difference;
}

/* The page's own corner labels (effects.js). Same type as the runtime's
   captions — 36px, tracked 10.8px, an 18px line box so the caps land at 41 —
   and the same inversion over a dark frame: white through difference reads
   black on the page and white on the render. Phone only. */
#fx-labels { display: none; }
@media (max-width: 760px) {
  #fx-labels { display: block; }
  .fx-label {
    position: fixed;
    top: calc(45px + env(safe-area-inset-top, 0px));
    z-index: 9999;
    font: 36px/18px "Neue Haas Display", ui-sans-serif, system-ui, sans-serif;
    letter-spacing: 10.8px;
    color: #161616;
    white-space: nowrap;
    pointer-events: none;
  }
  .fx-label[data-side="left"] { left: 40px; }
  /* the tracking after the last glyph is not part of the design's 40px */
  .fx-label[data-side="right"] { right: calc(40px - 10.8px); }
  :root.fx-gallery .fx-label {
    color: #fff;
    mix-blend-mode: difference;
  }

  /* The phone's type (Figma 1515:4610): 36px tracked 1.8px, 83px of box
     ending 20px above the edge, so the word sits 40px up as in the frame. */
  #fx-nav {
    top: calc(var(--fx-vh, 100svh) - 103px - env(safe-area-inset-bottom, 0px));
    font: 36px/43px "Neue Haas Display", ui-sans-serif, system-ui, sans-serif;
    letter-spacing: 1.8px;
  }
}

/* Page switch: the two layers dip through the page's own colour rather than
   cutting between a canvas and a field of type (see router.js). */
#fx-veil {
  position: fixed;
  inset: 0;
  z-index: 100001;
  background: #fafafa;
  opacity: 0;
  pointer-events: none;
  transition: opacity 260ms ease;
}

#fx-veil.is-on { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  #fx-veil { transition-duration: 0.1s !important; }
}
