/*
 * base.css — font loading, body defaults, and the dark/light surface system.
 *
 * The "dark surface" applies to Home, the Project Gallery section, and the
 * Contact page background outside of the white panel. The "light surface"
 * applies to the Project Description section and the Contact page text panel.
 *
 * Phase 5+ HTML files should add font preload hints in <head> for the
 * Regular weight (used by the logo and cover labels) ahead of the other
 * weights:
 *
 *   <link rel="preload" href="/assets/fonts/Geist-Regular.woff2"
 *         as="font" type="font/woff2" crossorigin>
 *
 * Font src paths below are absolute from site root (/assets/...). The
 * loading mechanism — Netlify publish-root, build-time copy into public/,
 * or a redirect rule — is settled in Phase 5 when HTML pages first reference
 * these stylesheets.
 */

@font-face {
  font-family: 'Geist';
  src: url('/assets/fonts/Geist-Regular.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Geist';
  src: url('/assets/fonts/Geist-Light.woff2') format('woff2');
  font-weight: 300;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Geist';
  src: url('/assets/fonts/Geist-ExtraLight.woff2') format('woff2');
  font-weight: 200;
  font-style: normal;
  font-display: swap;
}

body {
  font-family: var(--font-primary);
  font-weight: var(--fw-regular);
  font-size: var(--fs-body);
  line-height: var(--lh-base);
  background-color: var(--color-bg-dark);
  color: var(--color-fg-dark);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  transition:
    background-color var(--dur-theme) var(--ease-ui),
    color var(--dur-theme) var(--ease-ui);
}

/* Switches the body to the white description surface. Phase 7 toggles this
 * class on the Project Description section; it lives here so the token
 * system stays coherent end-to-end. */
.surface-description {
  background-color: var(--color-bg-light);
  color: var(--color-fg-light);
}

/* Typography baseline. Page-specific phases override sizes via tokens. */
h1, h2, h3, h4, h5, h6 {
  font-weight: var(--fw-regular);
  line-height: var(--lh-snug);
}

p {
  line-height: var(--lh-base);
}

::selection {
  background-color: var(--project-highlight);
  color: var(--color-bg-dark);
}

/* Visible focus rings on keyboard interaction; preserved across surfaces. */
:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

/* ---------- Line reveal animation ----------
 * Shared by the Project page description, the Contact page copy, and
 * any other text that animates in line-by-line on entry. The wrapper
 * is .reveal-line-clip (clip-path: inset(0) so the inline-block keeps
 * its text-baseline anchoring); the slide-up element is .reveal-line.
 * Adding the .reveal-in class on a common ancestor triggers all
 * descendant .reveal-line elements to transition into place. Each
 * .reveal-line gets a --reveal-delay CSS variable set by JS so the
 * visual lines fire in sequence rather than all at once.
 *
 * clip-path (not overflow: hidden) is intentional: an inline-block's
 * baseline shifts to its bottom margin edge whenever overflow is
 * anything other than visible, which with one inline-block per word
 * inflates the visible line gap. clip-path keeps overflow: visible so
 * the baseline stays at the text baseline.
 */
.reveal-line-clip {
  display: inline-block;
  clip-path: inset(0);
  -webkit-clip-path: inset(0);
  vertical-align: baseline;
  line-height: inherit;
}

.reveal-line {
  display: inline-block;
  transform: translateY(100%);
  transition: transform var(--dur-line-reveal) var(--ease-line-reveal);
  line-height: inherit;
}

.reveal-in .reveal-line {
  transform: none;
  transition-delay: var(--reveal-delay, 0ms);
}

/* Snap-to-visible: applied to <body> at click-time just before
 * window.location.assign so the OLD snapshot the cross-document
 * VT captures shows the page with text fully revealed. Without
 * this, a click that lands before the line reveal completes would
 * capture a snapshot with the text still translated below its
 * clip — and the closing sweep would show a sub-page with no
 * visible text. */
.reveal-snap .reveal-line {
  transition: none !important;
  transform: none !important;
}

/* ===========================================================
 * Cross-document view transitions (BUILD_SPEC §2, Phase 10)
 * ===========================================================
 * Vertical sweep between Home and a sub-page. Direction is picked
 * off the destination page's <html class="..."> (set statically in
 * each shell). Project <-> Contact navigation doesn't exist in the
 * UI; anything else (typed URL, reload, cross-origin link) doesn't
 * trigger a VT and falls through to the browser default (instant).
 *
 * Browsers without cross-document view-transition support
 * (Firefox, older WebKit) ignore @view-transition entirely;
 * navigation is then an ordinary instant page load.
 */
@view-transition {
  navigation: auto;
}

/* The sweep is bg-only: both pseudos stay at the viewport position
 * the snapshot was taken at, and a clip-path animation reveals or
 * hides one of them. Content within the snapshot doesn't move —
 * it's just exposed or covered as the clip edge passes over it.
 *
 * Opening (Home -> sub-page): NEW (sub-page) clips from "hidden"
 * to "fully visible," edge sweeping top -> down. OLD (Home) stays
 * put, visible beneath NEW where NEW is still clipped.
 *
 * Closing (sub-page -> Home): OLD (sub-page) clips from "fully
 * visible" to "hidden," edge sweeping bottom -> up — top elements
 * (logo, Get in touch) stay visible until the end. NEW (Home) is
 * revealed from the bottom as OLD shrinks. OLD must paint above
 * NEW for the reveal direction to read correctly.
 *
 * Direction is picked off the <html> class (set statically in each
 * HTML shell), not :has(body.X). Direct class matching on the
 * pseudo's originating element is well-trodden territory, whereas
 * :has() combined with cross-document ::view-transition-* pseudos
 * sometimes silently fails — and we want zero intermittent fallback
 * to the UA default fade.
 *
 * All clip-path values use the four-arg inset(top right bottom left)
 * form so keyframe endpoints share an identical syntactic shape;
 * mixing inset(0) and inset(0 0 100% 0) is technically valid but
 * has caused some engines to drop the interpolation and skip the
 * animation entirely. */

html.project-page::view-transition-old(root),
html.contact-page::view-transition-old(root) {
  animation: vt-stay var(--dur-sweep) linear both;
}
html.project-page::view-transition-new(root),
html.contact-page::view-transition-new(root) {
  animation: vt-clip-reveal var(--dur-sweep) var(--ease-sweep) both;
}

html.home-page::view-transition-old(root) {
  animation: vt-clip-hide var(--dur-sweep) var(--ease-sweep) both;
  z-index: 2;
}
html.home-page::view-transition-new(root) {
  animation: vt-stay var(--dur-sweep) linear both;
  z-index: 1;
}

/* Holds a pseudo at opacity 1 / no clip for the whole transition
 * duration. Used instead of `animation: none`, which only blanks
 * animation-name and still lets the UA's default fade-out / fade-in
 * keyframes apply via the inherited animation properties on
 * ::view-transition-image-pair. */
@keyframes vt-stay {
  from, to { opacity: 1; clip-path: inset(0 0 0 0); }
}

@keyframes vt-clip-reveal {
  from { clip-path: inset(0 0 100% 0); }
  to   { clip-path: inset(0 0 0 0); }
}
@keyframes vt-clip-hide {
  from { clip-path: inset(0 0 0 0); }
  to   { clip-path: inset(0 0 100% 0); }
}

@media (prefers-reduced-motion: reduce) {
  html.home-page::view-transition-old(root),
  html.home-page::view-transition-new(root),
  html.project-page::view-transition-old(root),
  html.project-page::view-transition-new(root),
  html.contact-page::view-transition-old(root),
  html.contact-page::view-transition-new(root) {
    animation: none !important;
  }
}

/* ===========================================================
 * Custom cursor (experimental — see js/cursor.js)
 * ===========================================================
 * Small dot follows the mouse with a slight lerp delay (in JS); the
 * dot grows on hover over interactive elements. The native cursor is
 * hidden site-wide so this is the only pointer shown.
 *
 * Guarded by `(hover: hover) and (pointer: fine) and (prefers-
 * reduced-motion: no-preference)`: on touch / coarse-pointer devices
 * and for reduced-motion users, the native system cursor stays
 * visible and .custom-cursor stays display: none. The JS module
 * mirrors the same guard, so neither side runs in fallback contexts.
 *
 * Centering: JS sets `transform: translate3d(x, y, 0) translate(
 * -50%, -50%)`. The first translate moves the box's top-left corner
 * to the mouse position; the second pulls the box back by half its
 * own size so the dot's center sits exactly on the mouse. The size
 * change on hover is transitioned by CSS (width/height), and because
 * `-50%` is element-relative the centering stays correct throughout.
 */
.custom-cursor {
  position: fixed;
  top: 0;
  left: 0;
  width: 10px;
  height: 10px;
  background-color: var(--cursor-color, var(--color-accent));
  border-radius: 50%;
  pointer-events: none;
  z-index: 9999;
  opacity: 0;
  transition:
    width 250ms cubic-bezier(.33, 1, .55, 1),
    height 250ms cubic-bezier(.33, 1, .55, 1),
    opacity 200ms ease,
    background-color var(--dur-highlight, 700ms) ease;
  will-change: transform;
  display: none;
}

/* Home is dark-background (project covers fill the viewport), so the
 * cursor needs to be white to stay visible. Project description and
 * Contact use white backgrounds, so the blue accent reads cleanly
 * there — `--cursor-color` falls through to its default. */
.home-page { --cursor-color: #fff; }

.custom-cursor.is-active { opacity: 1; }

.custom-cursor.is-hover {
  width: 36px;
  height: 36px;
}

@media (hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference) {
  .custom-cursor { display: block; }
  *, html, body { cursor: none !important; }
}

/* ===========================================================
 * Shared hover affordances — info-row links + Get-in-touch CTA
 * ===========================================================
 *
 * Both the Project page (LINKS, Gallery) and the Contact page
 * (Instagram and other socials, Get-in-touch CTA) use the same
 * hover pattern: a small arrow slides up from a clipping mask
 * and the text shifts to make room.
 *
 * Two flavors:
 *
 * 1. `.info-link / .info-action` — arrow on the LEFT, text shifts
 *    right. Used for external links (NE diagonal arrow ↗) and
 *    internal nav like the Gallery button (SE diagonal ↘). The
 *    SVG is baked into each element's HTML; only the animation is
 *    shared here.
 *
 * 2. `.static-get-in-touch` — arrow on the RIGHT, text shifts
 *    left. Used only for the email-copy CTA at the top-right.
 *    Mirror direction because the element is right-anchored
 *    (position: fixed; right: page-pad-x;), so growing content on
 *    the right side naturally pushes its left edge further left.
 *
 * All three are gated behind (hover: hover) + (pointer: fine) +
 * (prefers-reduced-motion: no-preference). Coarse-pointer and
 * reduced-motion users see no arrow and no shift.
 */
.info-link,
.info-action {
  display: inline-flex;
  align-items: center;
}

.info-arrow-clip,
.cta-arrow-clip {
  /* Hidden by default; enabled by the fine-pointer media query
   * below. */
  display: none;
}

@media (hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference) {
  /* ----- Left-side arrow (LINKS, socials, Gallery button) ----- */
  .info-arrow-clip {
    display: inline-flex;
    align-items: center;
    width: 0;
    height: 0.85em;
    overflow: hidden;
    transition: width 280ms cubic-bezier(.33, 1, .55, 1);
  }

  .info-arrow {
    display: block;
    width: 0.7em;
    height: 0.7em;
    flex: 0 0 auto;
    transform: translateY(100%);
    transition: transform 280ms cubic-bezier(.33, 1, .55, 1);
  }

  .info-link:hover .info-arrow-clip,
  .info-action:hover .info-arrow-clip,
  .info-section-btn:hover .info-arrow-clip {
    width: 0.85em;
  }

  .info-link:hover .info-arrow,
  .info-action:hover .info-arrow,
  .info-section-btn:hover .info-arrow {
    transform: translateY(0);
  }

  /* ----- Right-side arrow (Get-in-touch CTA) ----- */
  .cta-arrow-clip {
    display: inline-flex;
    align-items: center;
    width: 0;
    height: 0.85em;
    overflow: hidden;
    transition: width 280ms cubic-bezier(.33, 1, .55, 1);
  }

  .cta-arrow {
    display: block;
    width: 0.7em;
    height: 0.7em;
    flex: 0 0 auto;
    transform: translateY(100%);
    transition: transform 280ms cubic-bezier(.33, 1, .55, 1);
  }

  .static-get-in-touch:hover .cta-arrow-clip {
    width: 0.85em;
  }

  .static-get-in-touch:hover .cta-arrow {
    transform: translateY(0);
  }
}
