/* ============================================================================
   enhance.css — ADDITIVE LAYER. Loaded after styles.css.
   Nothing here restyles the site. It adds the surfaces the new motion needs
   (cursor, progress bar, split-line clipping) and fixes two measured defects.
   Delete this file and the two <script> tags in index.html and the site is
   exactly as it was.

   Palette-neutral on purpose: every color below resolves through the existing
   tokens (--accent, --ink, --bg), so it inherits whatever canvas the page is
   on. To move the site to the cobalt direction, link theme-cobalt.css after
   this file. One line, reversible.
   ========================================================================== */

/* ============================================================================
   FIXES
   ========================================================================== */

/* .cta p hardcoded rgba(243,242,238,.7), a cream, while .cta itself flips
   background:var(--ink) / color:var(--bg). On .idx-dark --ink is #FFFFFF, so
   the card is white and the paragraph is cream on white: measured 1.06:1.

   The card is now a dark surface (see CONTACT CARD below), so the mix runs the
   other way and the cream this was written to catch is the correct color
   again. Kept as a token mix rather than a hex so it still tracks the canvas on
   the cream project pages. */
.cta p{color:color-mix(in srgb,var(--ink) 76%,var(--surface))}

/* ============================================================================
   CONTACT CARD
   Redesigned Aug 14 2026. The card was the only element on the page carrying no
   information, and it arrives straight after ten projects full of it. It was
   also the only white surface on a dark page, which is what made it read as a
   footer banner bolted on rather than the last card in the set.

   It now uses var(--surface), the same surface as the project cards and the
   education rows, inside a gradient frame that lights up on hover.

   THE FRAME. Ported from a Uiverse card by Tiagoadag. The trick is that the
   gradient is not on the card, it is BEHIND it: the wrapper is the gradient and
   the card sits on top at full size, so the gradient is invisible until the
   card scales to .98 and lets a rim of it show on all four sides. Recolored
   from the original green-to-blue to the site accent and a deep plum of the
   same hue family, with the glow at the original's .30 alpha.

   Because .cta owns transform on hover, .reveal moved to the wrapper: main.js
   writes translateY into .reveal and the two would fight over the property.
   ========================================================================== */

.cta-frame{border-radius:22px;transition:box-shadow .3s var(--ease);
  background-image:linear-gradient(163deg,var(--accent) 0%,#6E1A5C 100%)}
.cta-frame:hover{box-shadow:0 0 30px 1px rgba(255,61,127,.30)}

/* Surface and text both flip: styles.css sets background:var(--ink) with
   color:var(--bg), which was white card, dark text. */
.cta{background:var(--surface);color:var(--ink);
  display:flex;flex-direction:column;align-items:center;
  padding-top:3.75rem;padding-bottom:3.5rem;
  transition:transform .2s var(--ease),border-radius .2s var(--ease)}
.cta-frame:hover .cta{transform:scale(.98)}
.cta h2{color:var(--ink)}
.cta h2 em{font-family:var(--serif);font-style:italic;font-weight:400;
  color:var(--accent-soft)}

/* .cta p is 0-1-1 and both new paragraphs are <p>, so plain .cta-status /
   .cta-lead class rules at 0-1-0 lose to it — including to the contrast fix
   above, which is mine. Element-qualified here to clear it. Same trap as the
   buttons, one specificity step lower. */
.cta p.cta-status{display:flex;align-items:center;gap:.6rem;margin:0 0 1.4rem;
  font-family:var(--mono);font-size:.68rem;letter-spacing:.14em;
  text-transform:uppercase;max-width:none;
  /* Lighter than the lead copy: it is a label, not a sentence. */
  color:var(--accent-soft)}
.cta-dot{width:6px;height:6px;border-radius:50%;background:var(--accent);
  flex:none;animation:ctaPulse 2.4s ease-in-out infinite}
@keyframes ctaPulse{0%,100%{opacity:.45}50%{opacity:1}}

/* 44ch, not the 34rem .cta p sets: at 16.5px that rule renders 67 characters on
   line one. Bottom margin to 0 so the only gap to the button row is that row's
   own 2rem, rather than 32 + 32. */
.cta p.cta-lead{max-width:44ch;margin:1.1rem auto 0}

.cta-acts{display:flex;flex-wrap:wrap;justify-content:center;gap:.75rem;
  margin-top:2rem}
.cta-acts .btn{display:inline-flex;align-items:center;gap:.55rem}

/* The primary inverts against the dark card now, so it is the light fill.
   styles.css has this the other way round for the white card it replaced. */
.idx-dark .cta .btn{background:var(--ink);border-color:var(--ink);
  color:var(--surface)}
.idx-dark .cta .btn:hover{background:var(--accent-soft);
  border-color:var(--accent-soft);color:var(--surface)}

/* Secondaries read as outlines. PREFIXED WITH .idx-dark, and that is
   load-bearing: index.html's inline <style> carries
   .idx-dark .btn-ghost{color:#FFC7DC} for the hero, also 0-2-0, and an inline
   block comes after this linked sheet, so at equal specificity it wins. 0-3-0
   settles it without touching the hero rule. */
.idx-dark .cta .cta-alt{background:transparent;border-color:var(--border2);
  color:var(--ink-soft)}
.idx-dark .cta .cta-alt:hover{background:rgba(255,205,222,.06);
  border-color:var(--accent-soft);color:var(--ink)}
/* Kept unprefixed too, for the cream project pages where .idx-dark is absent. */
.cta .cta-alt{background:transparent;border-color:var(--border2);
  color:var(--ink-soft)}
.cta .cta-alt:hover{border-color:var(--accent-soft);color:var(--ink)}
.cta-alt svg{flex:none}

@media(max-width:560px){
  /* Three buttons wrap to three lines on a phone. Full width each is a cleaner
     stack than a ragged two-plus-one. */
  .cta-acts{flex-direction:column;align-self:stretch}
  .cta-acts .btn{justify-content:center}
}
@media(hover:none),(pointer:coarse){
  /* No hover on touch, so the rim would never appear and the frame would just
     be a gradient hairline behind a card. Show it as a static rim instead. */
  .cta{transform:scale(.985)}
}
@media(prefers-reduced-motion:reduce){
  .cta-dot{animation:none}
  .cta,.cta-frame{transition:none}
  .cta-frame:hover .cta{transform:none}
}

/* #contact carries a full section's bottom padding and footer adds another
   5rem on top of it: 160px of stacked nothing between the CTA card and the
   rule. Dropping the footer margin alone only moves the problem, because the
   section's own 80px is then the whole gap, so both come down. 52px is the
   sec-head-to-body step used everywhere else on the page. */
.idx-dark footer{margin-top:0}
.idx-dark #contact{padding-bottom:3.25rem}

/* styles.css sets html{scroll-behavior:smooth} and only overrides it inside the
   prefers-reduced-motion block, which never applies while Lenis is running.
   Lenis calls window.scrollTo every frame and the browser re-smooths each call,
   so an anchor click teleports instead of playing the 1.1s travel enhance.js
   asks for. Lenis puts .lenis on <html> itself, so this reverts with the layer.
   This is the one line of lenis.css that matters; the rest is opt-in helpers. */
html.lenis{scroll-behavior:auto}

/* main.js appends the motion-pause button into .foot-copy, which is a text
   block, so an inline-flex control lands on the same line as the copyright and
   its margin-top does nothing. Giving it a line of its own is what the 1.1rem
   was always for. Pre-existing; visible at every width. */
.foot-copy .motion-toggle{display:flex;width:fit-content}

/* ============================================================================
   SCROLL PROGRESS
   Injected into .nav-outer by enhance.js, so it never exists without the script
   that drives it.
   ========================================================================== */
.scroll-prog{position:absolute;left:0;right:0;bottom:-1px;height:2px;
  background:var(--accent);transform:scaleX(0);transform-origin:0 50%;
  border-radius:0 2px 2px 0;pointer-events:none;z-index:3}

/* ============================================================================
   CUSTOM CURSOR
   Two nodes, both driven from one rAF loop. mix-blend-mode:difference means the
   ring never needs a color of its own: it inverts whatever it sits on, so it
   stays legible over the video, the glass panel and the cards alike.

   .cur-on lands on <html> only when the pointer is fine AND motion is allowed,
   so touch and reduced-motion users keep their own cursor and these two nodes
   are never created.
   ========================================================================== */
.cur-ring,.cur-dot{position:fixed;top:0;left:0;z-index:9999;pointer-events:none;
  opacity:0;mix-blend-mode:difference;will-change:transform;
  transition:opacity .25s var(--ease)}
.cur-ring{width:38px;height:38px;margin:-19px 0 0 -19px;border-radius:50%;
  border:1px solid rgba(255,255,255,.62);display:grid;place-items:center}
.cur-dot{width:5px;height:5px;margin:-2.5px 0 0 -2.5px;border-radius:50%;
  background:#fff}
.cur-label{font-family:var(--mono);font-size:8.5px;letter-spacing:.14em;
  text-transform:uppercase;color:#000;opacity:0;white-space:nowrap;
  transition:opacity .18s var(--ease)}
.cur-ring.is-lg .cur-label{opacity:1}
html.cur-on,html.cur-on *{cursor:none}
/* Text entry is the one place a hidden caret is a real cost. */
html.cur-on input,html.cur-on textarea{cursor:text}

/* ============================================================================
   SPLIT HEADINGS
   SplitType writes .line/.word into the heading. The clip lives on .line so the
   words can travel from below their own baseline rather than fading in place.

   Set only once the script has run: html.split-on is added by enhance.js right
   before it splits. With the CDN blocked the headings are plain text and never
   sit at yPercent 110 with nothing to animate them back.
   ========================================================================== */
html.split-on .split .line{overflow:hidden;padding-bottom:.06em}
html.split-on .split .word{will-change:transform}

/* The eyebrow wipes in from the left instead of fading, which reads as a rule
   being drawn rather than as one more thing appearing. */
html.split-on .eyebrow{clip-path:inset(0 100% 0 0)}

/* ============================================================================
   MAGNETIC ELEMENTS
   ========================================================================== */
[data-mag]{will-change:transform}

/* ============================================================================
   PROJECT CARD TILT
   Perspective on the card, transform on the inner anchor, so the existing
   .proj:hover lift in styles.css is untouched and the two never fight over the
   same transform.
   ========================================================================== */
.proj{perspective:1100px}
.proj-main{transform-style:preserve-3d;
  transition:transform .5s var(--ease)}
.proj.is-tilting .proj-main{transition:transform .08s linear}
.proj-thumb img{will-change:transform}

/* ============================================================================
   HERO
   will-change only. Every value is written by GSAP.
   ========================================================================== */
.vhero-bg video,.vhero-still,.hcol,.hero-panel{will-change:transform}

/* ============================================================================
   REDUCED MOTION
   The layer's job under reduce is to leave no trace: no cursor nodes, no clip
   on a heading that will never animate, no progress bar.
   ========================================================================== */
@media (prefers-reduced-motion:reduce){
  .cur-ring,.cur-dot,.scroll-prog{display:none}
  html.cur-on,html.cur-on *{cursor:auto}
  html.split-on .split .line{overflow:visible}
  html.split-on .eyebrow{clip-path:none}
  .proj-main{transition:none}
}

/* Touch: no hover, no tilt, no magnet. */
@media (hover:none),(pointer:coarse){
  .cur-ring,.cur-dot{display:none}
  .proj-main{transform:none !important}
}
