/**
 * Atmospheric + interaction stylesheet.
 *
 * Layered on top of the bespoke `/public/css/style.css`. Everything here is
 * monochrome; the only variable colour is `--accent`, which defaults to white.
 *
 * Loaded after style.css so these rules win on equal specificity.
 */

/* ══════
   1. WebGL layer + static fallback
   ══════ */

.webgl-background {
  position: fixed;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  /* Promote to its own layer so the canvas never triggers a page-wide repaint. */
  transform: translate3d(0, 0, 0);
  /* Contain layout/paint work inside the canvas box. */
  contain: strict;
}

.webgl-background canvas {
  display: block;
  width: 100% !important;
  height: 100% !important;
  /* The scene is atmosphere, not content: keep it behind the copy and faint. */
  opacity: 0.9;
}

/* Fade the scene in once it has actually produced a frame, so there is never a
   black flash between the fallback and the live canvas. */
.webgl-background {
  animation: webgl-rise 1.6s ease-out both;
}

@keyframes webgl-rise {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

/*
 * Static fallback — used when WebGL is unavailable or motion is reduced.
 *
 * Three soft radial pools built purely from gradients. Because they are
 * background-images on a fixed element, the browser rasterises them once and
 * the drift is a compositor-only transform: effectively free.
 */
.webgl-fallback {
  position: fixed;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  overflow: hidden;
  contain: strict;
  background: #07070a;
}

/*
 * Rendered underneath the live canvas while the 3D chunk is still downloading.
 * Keeping it at z-index 0 means the canvas fades in *over* an already-correct
 * backdrop, so there is never a black frame between the two.
 */
.webgl-fallback[data-behind="true"] {
  z-index: 0;
}

.webgl-fallback__pool {
  position: absolute;
  border-radius: 50%;
  will-change: transform;
  transform: translate3d(0, 0, 0);
}

.webgl-fallback__pool--1 {
  width: 90vmax;
  height: 90vmax;
  top: -30vmax;
  left: -20vmax;
  background: radial-gradient(circle, rgba(255, 255, 255, 0.055) 0%, transparent 62%);
  animation: fallback-drift-a 46s ease-in-out infinite alternate;
}

.webgl-fallback__pool--2 {
  width: 70vmax;
  height: 70vmax;
  bottom: -28vmax;
  right: -18vmax;
  background: radial-gradient(circle, rgba(210, 214, 220, 0.045) 0%, transparent 66%);
  animation: fallback-drift-b 58s ease-in-out infinite alternate;
}

.webgl-fallback__pool--3 {
  width: 48vmax;
  height: 48vmax;
  top: 34%;
  left: 46%;
  background: radial-gradient(circle, rgba(255, 255, 255, 0.03) 0%, transparent 70%);
  animation: fallback-drift-c 72s ease-in-out infinite alternate;
}

/* Very fine film grain so the flat gradients do not band on cheap panels. */
.webgl-fallback__grain {
  position: absolute;
  inset: 0;
  opacity: 0.16;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");
}

@keyframes fallback-drift-a {
  from {
    transform: translate3d(0, 0, 0) scale(1);
  }
  to {
    transform: translate3d(6vw, 4vh, 0) scale(1.08);
  }
}
@keyframes fallback-drift-b {
  from {
    transform: translate3d(0, 0, 0) scale(1.05);
  }
  to {
    transform: translate3d(-5vw, -5vh, 0) scale(1);
  }
}
@keyframes fallback-drift-c {
  from {
    transform: translate3d(-2vw, 2vh, 0) scale(0.95);
  }
  to {
    transform: translate3d(3vw, -3vh, 0) scale(1.12);
  }
}

/* ══════
   2. Depth + layering system
   ══════ */

/*
 * Content sits above the WebGL and particle layers. `--scroll-progress` is
 * published by the GSAP scroll binder and read here so CSS depth reacts to
 * scroll without a second scroll listener.
 */
.editorial-page {
  position: relative;
  z-index: 10;
}

/* Sections gain a faint hairline separation as they take focus. */
.editorial-section {
  position: relative;
}

.editorial-section::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 1px;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.14), transparent);
  /* `--section-focus` is set by the existing scroll observer. */
  opacity: calc(var(--section-focus, 0) * 0.9);
  transition: opacity 400ms cubic-bezier(0.22, 1, 0.36, 1);
  pointer-events: none;
}

/* ══════
   3. Typography refinements
   ══════ */

/* Optical tightening for the large display type. */
.hero-name h1,
.editorial-section h2 {
  text-wrap: balance;
  letter-spacing: -0.03em;
}

.editorial-section p {
  text-wrap: pretty;
}

/* Section labels: mono, tracked, quiet. Reinforces the "technical document"
   read without adding visual weight. */
.section-label {
  text-wrap: nowrap;
}

/* ══════
   4. Scroll reveal states (GSAP owns the animation; these are the
      no-JS / pre-hydration resting states)
   ══════ */

/*
 * If GSAP never runs (script failure, JS disabled), the IntersectionObserver
 * path in the site controller adds `in-view` and the content is visible. These
 * rules guarantee content is *never* permanently hidden — a failed animation
 * must not hide the portfolio.
 */
.reveal {
  opacity: 1;
}

@media (prefers-reduced-motion: no-preference) {
  .reveal:not(.in-view):not([data-gsap-managed]) {
    /* Only hidden while waiting for the observer, and only when motion is OK. */
    opacity: 0;
  }
}

/* GSAP sets its own inline styles; this just prevents a double transition. */
[data-gsap-managed] {
  will-change: transform, opacity;
}

/* ══════
   5. Interactive element polish
   ══════ */

/* Magnetic elements need a transform origin at their centre. */
.scroll-cue,
.theme-fab,
.contact-link {
  will-change: transform;
  transform: translate3d(0, 0, 0);
}

/* Project media: a hairline frame + corner clip reads as a technical panel
   rather than a rounded card. */
.project-media {
  position: relative;
  overflow: hidden;
  border: 1px solid rgba(255, 255, 255, 0.08);
  background: linear-gradient(160deg, rgba(255, 255, 255, 0.03), transparent 60%);
  clip-path: polygon(
    0 0,
    calc(100% - 16px) 0,
    100% 16px,
    100% 100%,
    16px 100%,
    0 calc(100% - 16px)
  );
}

.project-media::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(115deg, transparent 40%, rgba(255, 255, 255, 0.05) 50%, transparent 60%);
  opacity: 0;
  transition: opacity 620ms cubic-bezier(0.22, 1, 0.36, 1);
  pointer-events: none;
}

.project:hover .project-media::after,
.project:focus-within .project-media::after {
  opacity: 1;
}

/* Skill chips: precise, restrained borders. */
.skill-chip {
  transition:
    border-color 320ms cubic-bezier(0.22, 1, 0.36, 1),
    background-color 320ms cubic-bezier(0.22, 1, 0.36, 1),
    transform 320ms cubic-bezier(0.22, 1, 0.36, 1);
}

.skill-chip:hover {
  transform: translate3d(0, -1px, 0);
  border-color: rgba(255, 255, 255, 0.2);
}

/* Presence cards: subtle rim light on hover. */
.presence-card {
  transition:
    border-color 420ms cubic-bezier(0.22, 1, 0.36, 1),
    box-shadow 420ms cubic-bezier(0.22, 1, 0.36, 1);
}

.presence-card:hover {
  border-color: rgba(255, 255, 255, 0.16);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06);
}

/* Contact links: the underline draws from the left on hover/focus. */
.contact-link {
  position: relative;
}

.contact-link::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: -2px;
  height: 1px;
  width: 100%;
  background: currentColor;
  opacity: 0.5;
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 460ms cubic-bezier(0.22, 1, 0.36, 1);
}

.contact-link:hover::after,
.contact-link:focus-visible::after {
  transform: scaleX(1);
}

/* ══════
   6. Grain / interference overlay
   ══════ */

#grain-overlay {
  position: fixed;
  inset: 0;
  z-index: 45;
  pointer-events: none;
  opacity: 0.035;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='4' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23g)'/%3E%3C/svg%3E");
  animation: grain-shift 8s steps(6, end) infinite;
}

@keyframes grain-shift {
  0% { transform: translate3d(0, 0, 0); }
  20% { transform: translate3d(-2%, 1%, 0); }
  40% { transform: translate3d(1%, -2%, 0); }
  60% { transform: translate3d(-1%, 2%, 0); }
  80% { transform: translate3d(2%, 1%, 0); }
  100% { transform: translate3d(0, 0, 0); }
}

/* ══════
   7. Container queries — intentional responsive behaviour
   ══════ */

/* The skills matrix reflows on its own width, not the viewport's. */
#skills-container {
  container-type: inline-size;
  container-name: skills;
}

@container skills (min-width: 640px) {
  #skills-container {
    column-gap: 2rem;
  }
}

@container skills (min-width: 900px) {
  #skills-container {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* Project rows: stack on narrow containers, alternate on wide ones. */
#projects-container {
  container-type: inline-size;
  container-name: projects;
}

@container projects (min-width: 860px) {
  .project {
    align-items: center;
  }
}

/* ══════
   8. Mobile / touch adaptations
   ══════ */

/*
 * Touch devices have no hover state, so hover-only affordances must be
 * permanently visible or replaced by an active state.
 */
@media (hover: none) {
  .project-media::after {
    opacity: 0.5;
  }

  /* Magnetic transforms are meaningless without a cursor. */
  .scroll-cue,
  .theme-fab,
  .contact-link {
    will-change: auto;
    transform: none !important;
  }

  /* Reduce the cost of the grain layer on phones. */
  #grain-overlay {
    opacity: 0.025;
  }
}

/* Small screens: fewer expensive compositor layers. */
@media (max-width: 768px) {
  .webgl-background canvas {
    opacity: 0.75;
  }

  .webgl-fallback__pool--3 {
    display: none;
  }

  /* Disable the animated grain; it costs more than it contributes on mobile. */
  #grain-overlay {
    animation: none;
  }
}

/* ══════
   9. Reduced motion — the hard off switch
   ══════ */

@media (prefers-reduced-motion: reduce) {
  .webgl-fallback__pool,
  #grain-overlay {
    animation: none !important;
  }

  .webgl-background {
    animation: none !important;
  }

  /* Reveal content immediately — never let motion preferences hide content. */
  .reveal {
    opacity: 1 !important;
    transform: none !important;
    clip-path: none !important;
    filter: none !important;
  }

  /* Neutralise all our hover translations. */
  .skill-chip:hover,
  .project-media,
  .contact-link,
  .scroll-cue,
  .theme-fab {
    transform: none !important;
    transition: none !important;
  }

  .contact-link::after,
  .project-media::after {
    transition: none !important;
  }
}

/* ══════
   10. High-contrast / forced-colors safety
   ══════ */

@media (forced-colors: active) {
  .webgl-background,
  .webgl-fallback,
  #grain-overlay,
  .blob {
    display: none !important;
  }

  .reveal {
    opacity: 1 !important;
  }
}

/* Print: strip all atmosphere, keep the content legible. */
@media print {
  .webgl-background,
  .webgl-fallback,
  #grain-overlay,
  #cursor,
  #cursor-follower,
  #mouse-light,
  #particles-canvas,
  #loading-screen,
  .music-bar,
  .theme-fab {
    display: none !important;
  }

  .reveal {
    opacity: 1 !important;
    transform: none !important;
  }
}
