/* ============================================================================
   Patricia Martinez — portfolio
   Shared foundation. Every page loads this file and nothing else.

   Values come from the Figma frames in "Section 1" of Revamp-personnel.
   The 1440px design width maps to fluid CSS: the 156px side margin is
   156/1440 = 10.8333vw, clamped so it never collapses on small screens and
   never exceeds the design value on large ones.
   ========================================================================= */

:root {
  /* ---- surfaces ---- */
  --surface:      #F3EFEB;   /* header, hero, footer — warm, matched
                                to the sand band's temperature (R-G 4, G-B 4) */
  --surface-sand: #EBE7E2;   /* case study intro band */
  --surface-lilac:#E4E4EA;   /* "next work" band */
  --surface-teal: #E2EBEB;   /* work index band */

  /* ---- ink ---- */
  --ink:        #000000;
  --ink-muted:  #5F5A55;
  --ink-faint:  #888888;
  --rule:       #40376A;

  /* ---- brand gradient ----
     This is the Figma paint style "Gradiant passing wcag test" — the one
     that clears contrast against the cream surface. It is the ONLY gradient
     used for interaction and for marking the current page. */
  --grad-a: #5160BA;
  --grad-b: #57406D;
  --grad-c: #6B3333;
  --brand-grad: linear-gradient(90deg, var(--grad-a) 0%, var(--grad-b) 53.37%, var(--grad-c) 100%);

  /* ---- layout ---- */
  --gutter: clamp(24px, 10.8333vw, 156px);   /* never below 24 */
  --content-max: 1128px;          /* 1440 - 156*2 */
  --header-h: 80px;

  /* ---- motion ---- */
  --ease: cubic-bezier(.45, 0, .55, 1);
  --dur:  .4s;
}

/* ---------------------------------------------------------------- reset */
*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--surface);
  color: var(--ink);
  font-family: "Space Grotesk", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  /* wide figures scroll inside their own container; the page never does */
  overflow-x: hidden;

  /* Sticky footer. Without this, on a tall monitor the hero hits its 900px
     cap and the footer ends up floating in the middle of the screen with
     empty cream beneath it. This pins it to the bottom on short pages while
     leaving long pages (the case studies) to flow normally. */
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
}
main { flex: 1 0 auto; display: flex; flex-direction: column; }
main > .hero { flex: 1 0 auto; }
.site-footer { flex-shrink: 0; }

img, svg { max-width: 100%; }
a { color: inherit; text-decoration: none; }

/* Visible focus for keyboard users only. Never remove this without a
   replacement — it is the difference between a usable site and one that
   locks out anyone not using a mouse. */
:focus-visible {
  outline: 2px solid var(--grad-b);
  outline-offset: 3px;
  border-radius: 2px;
}

.visually-hidden {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  clip-path: inset(50%); overflow: hidden; white-space: nowrap;
}

.skip-link {
  position: absolute; left: 8px; top: -60px;
  background: var(--ink); color: var(--surface);
  padding: 10px 16px; border-radius: 6px; font-size: 14px; z-index: 100;
  transition: top .2s ease;
}
.skip-link:focus { top: 8px; }

/* ------------------------------------------------------- gradient text */
.grad-text {
  background-image: var(--brand-grad);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* ============================================================ THE GRADIENT
   INTERACTION SYSTEM

   No underlines anywhere. The gradient itself IS the state — the text
   fills with it. This matches the "Contact - links when hovered or tapped"
   frame, where the active nav item is gradient text and nothing else changes.

   Three states, each on the input it belongs to:
     :focus-visible — keyboard
     :active        — touch AND mouse-down; Medium, same as hover
     :hover         — pointer devices ONLY, defined at the foot of this file
                      behind (hover: hover). It is not declared here because
                      a phone would fire it on tap and then leave it stuck.

   The mechanism: the gradient is painted as a background clipped to the
   glyphs, then covered by an opaque `color`. Fading that colour to
   transparent reveals the gradient underneath — so it can be transitioned,
   which a `background-image` swap cannot.
   ========================================================================= */
.link-grad {
  display: inline-block;
  text-align: center;
  background-image: var(--brand-grad);
  -webkit-background-clip: text;
  background-clip: text;
  color: var(--ink);
  font-weight: 400;
  transition: color var(--dur) ease, font-weight var(--dur) ease;
}
.link-grad:focus-visible { color: transparent; font-weight: 500; }
/* Interaction — hover and tap alike — is Medium. Bold is reserved for the
   page you are actually on, so the two never read as the same thing. */
.link-grad:active { color: transparent; font-weight: 500; }

/* Heavier text is wider, which would shove the neighbouring nav items
   sideways. This invisible copy stays at 700 — still the heaviest state,
   since the current page sits there permanently — so every link is always
   as wide as its widest state and nothing moves. */
.link-grad::after {
  content: attr(data-label);
  display: block;
  height: 0;
  overflow: hidden;
  visibility: hidden;
  font-weight: 700;
  pointer-events: none;
}

/* The wordmark marks the current page. On the page it points at it is a
   plain <span> (see index.html) — not a link, not hoverable, just the
   gradient stating where you are. Elsewhere it is an <a.wordmark-link>
   in plain ink that fills with the gradient on interaction. */
.wordmark {
  font-weight: 500;
  font-size: clamp(17px, 1.39vw, 20px);
  background-image: var(--brand-grad);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.wordmark-link {
  font-weight: 500;
  font-size: clamp(17px, 1.39vw, 20px);
  background-image: var(--brand-grad);
  -webkit-background-clip: text;
  background-clip: text;
  color: var(--ink);
  transition: color var(--dur) ease;
}
.wordmark-link:focus-visible,
.wordmark-link:active { color: transparent; }

/* --------------------------------------------------------------- header */
.site-header {
  min-height: var(--header-h);
  padding: 24px var(--gutter);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  background: var(--surface);
  position: relative;
  /* Above the menu scrim (4). The mobile menu is a CHILD of this header, and
     a child cannot outrank its parent's stacking context however high its own
     z-index — so the header must sit above the scrim for the panel to. The
     footer stays at 3 so the scrim veils it, as designed. */
  z-index: 5;
}
.site-nav {
  display: flex;
  align-items: center;
  gap: clamp(18px, 2.2vw, 32px);
  font-size: clamp(14px, 1.11vw, 16px);
}
/* The page you are on holds the full selected state — gradient AND bold —
   permanently, not just while pointed at. Same treatment a tap produces, so
   "where I am" and "where I'm going" read as one language. */
.site-nav a[aria-current="page"],
.mobile-menu a[aria-current="page"] {
  background-image: var(--brand-grad);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  font-weight: 700;
}

/* --------------------------------------------------------------- footer */
.site-footer {
  min-height: var(--header-h);
  padding: 24px var(--gutter);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  flex-wrap: wrap;
  font-size: clamp(14px, 1.11vw, 16px);
  background: var(--surface);
  position: relative;
  z-index: 3;
}
.footer-note { font-weight: 500; }
/* The wrapper exists only for the mobile stack. At desktop it dissolves so its
   two children become direct flex items of the footer again. */
.footer-text { display: contents; }
.footer-copyright { order: -1; }   /* copyright sits left, as designed */
.footer-utils { display: flex; align-items: center; gap: 16px; }

/* LinkedIn — the glyph itself fills with the gradient. CSS `fill` accepts a
   url() reference to a gradient defined in the same document, which is why
   the icon is inlined rather than loaded through <img>. */
.icon-link {
  display: inline-flex;
  width: 32px; height: 32px;
}
.icon-link svg { width: 100%; height: 100%; display: block; }
/* 2px at every size — your Figma now shows strokeW=2 on the desktop ring too.
   The circle's r was pulled to 15 so a 2px stroke ends exactly on the
   viewBox edge instead of being clipped by it. */
.icon-link svg circle { stroke-width: 2; }
.icon-link .li-glyph {
  fill: var(--ink);
  transition: fill var(--dur) ease;
}
.icon-link:focus-visible .li-glyph,
.icon-link:active .li-glyph { fill: url(#liGrad); }

/* CV button — same language as the nav and the LinkedIn glyph: the content
   fills with the gradient. Matches the "cv button" in the Figma hover spec,
   where the stroke, the label and the arrow all become gradient. */
.cv-button {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  height: 32px;
  padding: 0 11px;
  border: 2px solid var(--grad-a);   /* 2px in Figma - 1px read as too faint */
  border-radius: 8px;
  font-size: clamp(14px, 1.11vw, 16px);
  transition: border-color var(--dur) ease;
}

/* The interior fills with the gradient — the button's own shape, the same way
   the LinkedIn glyph's shape fills. Painted on a pseudo-element and faded by
   opacity rather than swapped as a background-image, because an image swap
   cannot transition. */
.cv-button::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 6px;                /* 8px border box minus the 2px border */
  background: var(--brand-grad);
  opacity: 0;
  transition: opacity var(--dur) ease;
  pointer-events: none;
}

/* Label and arrow sit above the fill, so they stay legible once it arrives. */
.cv-button .label,
.cv-button svg { position: relative; z-index: 1; }

.cv-button .label {
  color: var(--ink);
  transition: color var(--dur) ease;
}
.cv-button svg {
  width: 11px; height: 11px;
  transform: rotate(90deg);          /* the shared arrow asset, pointing down */
  transition: transform var(--dur) var(--ease);
}
.cv-button .cv-arrow {
  fill: var(--ink);
  transition: fill var(--dur) ease;
}

/* Cream on the filled gradient — the same reversal the wordmark uses. */
.cv-button:focus-visible,
.cv-button:active { border-color: transparent; }
.cv-button:focus-visible::before,
.cv-button:active::before { opacity: 1; }
.cv-button:focus-visible .label { color: var(--surface); }
.cv-button:active .label { color: var(--surface); font-weight: 500; }
.cv-button:focus-visible .cv-arrow,
.cv-button:active .cv-arrow { fill: var(--surface); }
.cv-button:focus-visible svg,
.cv-button:active svg { transform: rotate(90deg) translateX(3px); }

/* ============================================================ HOME / HERO
   The intro is a cover: it should be exactly one screen, whatever screen
   that is. A fixed 700px hero can only be correct on one display — it
   overflows a 13" laptop and leaves dead space on a 16".

   So the hero takes whatever height is left once the header and footer have
   had theirs, floored at 480 (so it never crushes) and capped at 900 (so it
   never sprawls on a tall monitor). The design's own 700px sits comfortably
   inside that range.

   `dvh`, not `vh`: on mobile Safari `100vh` means the viewport with the URL
   bar HIDDEN, so a vh-sized cover is always taller than the visible area and
   the bottom is cut off. `dvh` tracks the space actually on screen. */
.hero {
  position: relative;
  min-height: clamp(480px, calc(100dvh - (var(--header-h) * 2)), 900px);
  display: flex;
  align-items: center;
  padding: 60px var(--gutter);
  background: var(--surface);
  overflow: hidden;
}
/* Older browsers with no dvh support fall back to the design's fixed height
   rather than to nothing. Declared first so dvh overrides it where supported. */
@supports not (height: 100dvh) {
  .hero { min-height: 700px; }
}

/* The animated field sits behind the copy and never intercepts a click. */
.hero-wave {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 0;
  pointer-events: none;
}

.hero-inner {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: var(--content-max);
}

.hero h1 {
  margin: 0;
  max-width: 815px;                     /* the measure set in the design */
  font-weight: 500;
  font-size: clamp(26px, 2.78vw, 40px);
  line-height: 1.2;
  letter-spacing: -0.01em;
  text-wrap: balance;
}

/* The hero call to action: fills with the gradient, no underline.
   In Figma this is one text node - "View my work →" - so the arrow is
   separated by a plain word space, not a layout gap. 0.28em matches the
   width of a space in Space Grotesk and scales with the font size. */
.hero-cta {
  display: inline-flex;
  align-items: baseline;
  gap: 0.28em;
  margin-top: clamp(28px, 3.9vw, 56px);
  font-size: clamp(20px, 2.22vw, 32px);
}
.hero-cta .label {
  text-align: left;
  background-image: var(--brand-grad);
  -webkit-background-clip: text;
  background-clip: text;
  color: var(--ink);
  font-weight: 400;
  transition: color var(--dur) ease, font-weight var(--dur) ease;
}
/* Same width reservation as the nav: without it, the label getting heavier
   would push the arrow further than its own slide is meant to. */
.hero-cta .label::after {
  content: attr(data-label);
  display: block;
  height: 0;
  overflow: hidden;
  visibility: hidden;
  font-weight: 700;
  pointer-events: none;
}
.hero-cta .arrow {
  display: inline-block;
  /* Painted explicitly rather than inherited: the translateX makes this span
     its own containing block, so a parent's background-clip:text cannot reach
     it and it would vanish the moment the text goes transparent. --grad-c is
     where the ramp lands at its right edge, which is exactly where the arrow
     sits, so it still reads as one continuous gradient. */
  color: var(--ink);
  font-weight: 400;
  /* A back-out curve overshoots slightly before settling - that is the little
     bounce. Colour and weight run on the same clock as the label so the whole
     phrase changes as one thing, not in two steps. */
  transition: transform var(--dur) cubic-bezier(.34, 1.56, .64, 1),
              color var(--dur) ease,
              font-weight var(--dur) ease;
}
.hero-cta:focus-visible .label { color: transparent; font-weight: 500; }
.hero-cta:focus-visible .arrow {
  transform: translateX(6px);
  color: var(--grad-c);
  font-weight: 500;
}
/* tapped: label and arrow both go bold, together */
.hero-cta:active .label { color: transparent; font-weight: 500; }
.hero-cta:active .arrow {
  transform: translateX(6px);
  color: var(--grad-c);
  font-weight: 500;
}

/* ------------------------------------------------------------- responsive */
@media (max-width: 560px) {
  /* On phones the footer stacks to three rows, so "viewport minus 160" no
     longer describes the space left. Give the hero a smaller share and let
     the page scroll a little — a rigid one-screen cover matters far less
     here, and forcing it would squeeze the headline. */
  .hero {
    min-height: clamp(380px, calc(100dvh - 260px), 560px);
    padding-top: 40px;
    padding-bottom: 40px;
  }
  .site-footer {
    justify-content: flex-start;
    flex-direction: column;
    align-items: flex-start;
    gap: 18px;
  }
  .footer-note { order: 3; }
}

/* Three short nav items fit at 375px, so no hamburger — a menu that hides
   three words behind a tap is worse than the words. */
@media (max-width: 380px) {
  .site-header { gap: 12px; }
  .site-nav { gap: 14px; font-size: 13px; }
}

/* --------------------------------------------------------- reduced motion */
@media (prefers-reduced-motion: reduce) {
  /* the wave holds a still frame rather than breathing */
  .hero-wave animate { display: none; }
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
}

/* ============================================================================
   MOBILE
   From the Figma frames "Mobile intro" (1133:31417) and "Mobile menu tapped"
   (1133:31662), both 402px wide.

   Two different insets in that design, kept deliberately:
     chrome (header / footer) sits at 24px
     hero content sits at 40px
   ========================================================================= */

/* The hamburger is desktop-hidden; the inline nav is mobile-hidden. Both
   exist in the DOM at all times so no layout is built by JavaScript. */
.nav-toggle { display: none; }
/* Both mobile-only. They must be hidden at BASE, not merely styled inside the
   media query — an element with no rules above the breakpoint still renders. */
.mobile-menu { display: none; }
.menu-scrim { display: none; }

@media (max-width: 768px) {

  :root { --gutter: 24px; }

  /* ---------------- header ---------------- */
  .site-nav { display: none; }          /* replaced by the panel below */
  .wordmark, .wordmark-link { font-size: 16px; }

  .nav-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    position: relative;
    width: 32px; height: 32px;
    padding: 0;
    border: 2px solid var(--grad-a);     /* +1 for mobile */
    border-radius: 8px;
    background: transparent;
    cursor: pointer;
    /* the gradient fill is painted underneath and revealed when open */
    background-image: var(--brand-grad);
    background-size: 100% 100%;
    background-repeat: no-repeat;
    transition: box-shadow var(--dur) ease;
  }
  /* Closed: an opaque surface layer hides the gradient. Opening fades it out,
     so the button fills rather than swapping colour abruptly. */
  .nav-toggle::before {
    content: "";
    position: absolute; inset: 0;
    border-radius: 6px;   /* 8 minus the 2px border */
    background: var(--surface);
    transition: opacity var(--dur) ease;
  }
  .nav-toggle[aria-expanded="true"]::before { opacity: 0; }

  .nav-toggle .bars {
    position: relative;
    width: 12px; height: 12px;
  }
  .nav-toggle .bars span {
    position: absolute;
    left: 0;
    width: 12px; height: 1.5px;
    /* Fully rounded caps. The Figma nodes report cornerRadius:0 because they
       are VECTORs — the rounding lives in the path geometry, not the property.
       999px clamps to half the height (0.75px), giving semicircular ends. */
    border-radius: 999px;
    background: #0C0C0C;
    transition: transform var(--dur) var(--ease),
                opacity var(--dur) ease,
                background-color var(--dur) ease,
                width var(--dur) var(--ease);
  }
  /* Spacing is 5px, not the 5.25 in Figma, and every bar starts on a .25
     subpixel offset. A 1.5px line straddles pixel boundaries differently
     depending on where its fractional position falls — at 0 / 5.25 / 10.5 the
     three bars land on phases .0 / .25 / .5 and the browser antialiases each
     one differently, which is why the top bar reads thinner. Identical phase
     means identical rasterisation. The 0.25px spacing loss is invisible; the
     weight difference was not. */
  .nav-toggle .bars span:nth-child(1) { top: 0.25px; }
  .nav-toggle .bars span:nth-child(2) { top: 5.25px; }
  .nav-toggle .bars span:nth-child(3) { top: 10.25px; }

  /* Open: outer bars cross into an X, middle bar fades. Widths and the 1.885px
     weight come straight from the "Mobile menu tapped" frame. */
  /* The X is an 11.22px square in Figma, so each stroke is its diagonal:
     11.22 x sqrt(2) = 15.87. Cream, not white — it sits on the gradient fill. */
  .nav-toggle[aria-expanded="true"] .bars span {
    background: var(--surface);
    width: 15.87px;
    height: 1.5px;
    left: -1.93px;
    border-radius: 999px;
  }
  .nav-toggle[aria-expanded="true"] .bars span:nth-child(1) {
    top: 5.25px; transform: rotate(-45deg);
  }
  .nav-toggle[aria-expanded="true"] .bars span:nth-child(2) { opacity: 0; }
  .nav-toggle[aria-expanded="true"] .bars span:nth-child(3) {
    top: 5.25px; transform: rotate(45deg);
  }
  /* Tapped: same gradient fill the rest of the site uses. */
  .nav-toggle:active::before { opacity: 0; transition-duration: .08s; }
  .nav-toggle:active .bars span { background: var(--surface); transition-duration: .08s; }

  /* ---------------- dropdown panel ---------------- */
  .mobile-menu {
    position: absolute;
    top: 64px;
    right: var(--gutter);              /* right edge aligns with the button */
    width: 144px;
    background: var(--surface);
    border: 2px solid var(--grad-a);
    border-radius: 24px;
    /* 22px horizontal so the labels land 24px from the panel's outer edge
       once the 2px border is counted. */
    padding: 22px;
    display: flex;
    flex-direction: column;
    gap: 6.5px;
    z-index: 20;

    opacity: 0;
    visibility: hidden;
    transform: translateY(-6px);
    transition: opacity var(--dur) ease,
                transform var(--dur) var(--ease),
                visibility 0s linear var(--dur);
  }
  .mobile-menu[data-open="true"] {
    opacity: 1;
    visibility: visible;
    transform: translateY(0);
    transition-delay: 0s;
  }
  .mobile-menu a {
    font-size: 18px;
    line-height: 32px;
    color: var(--ink);
    background-image: var(--brand-grad);
    -webkit-background-clip: text;
    background-clip: text;
    transition: color var(--dur) ease, font-weight var(--dur) ease;
  }
  .mobile-menu a:focus-visible { color: transparent; font-weight: 500; }

  /* Rules sit 16px from the panel's outer edge while the labels sit at 24,
     so they need to break out of the padding box by 10px either side. */
  .mobile-menu .rule {
    height: 1px;
    margin: 0 -8px;   /* content box is 96; +16 = the 112 in Figma */
    background: var(--grad-a);
    opacity: .35;
    flex: 0 0 auto;
  }
  .mobile-menu a:active { color: transparent; font-weight: 500; }

  /* ---------------- menu scrim ----------------
     From "Rectangle 1526": 402x794 starting at y=80, white at 20%, blur 10.
     It begins BELOW the header, so the wordmark and close button stay crisp
     while everything behind the panel recedes — which is the right call,
     since the control you need to dismiss it must never be veiled.

     No blur of any kind: the tint alone is the effect. */
  .menu-scrim {
    display: block;
    position: fixed;
    top: var(--header-h);
    left: 0; right: 0; bottom: 0;
    /* The "blurr effect" layer: the brand gradient at 5%. Alpha is baked into
       the stops rather than set with `opacity`, because `opacity` would create
       a stacking context and change how the layer composites. */
    background: linear-gradient(90deg,
                  rgba(81, 96, 186, 0.05) 0%,
                  rgba(87, 64, 109, 0.05) 53.4%,
                  rgba(107, 51, 51, 0.05) 100%);
    /* No backdrop blur. Figma's Layer blur on a flat gradient produces no
       visible softening, so the design is a tint and nothing more — the
       panel's own border and the crisp header do the separating. Adding
       blur here only ever made the page look out of focus. */
    opacity: 0;
    visibility: hidden;
    transition: opacity var(--dur) ease, visibility 0s linear var(--dur);
    z-index: 4;
  }
  .menu-scrim[data-open="true"] {
    opacity: 1;
    visibility: visible;
    transition-delay: 0s;
  }

  /* ---------------- hero ---------------- */
  .hero { padding-left: 40px; padding-right: 40px; }
  .hero h1 { font-size: 24px; max-width: 322px; }
  .hero-cta { font-size: 20px; margin-top: 34px; }

  /* ---------------- footer ----------------
     The mobile footer stacks the two text lines on the left with the icons
     to their right, rather than the desktop's three-across arrangement. */
  .site-footer {
    min-height: 84px;
    padding: 24px var(--gutter);
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    flex-wrap: nowrap;
    gap: 12px;
  }
  .footer-text {
    display: flex;          /* re-forms the wrapper for the stacked mobile footer */
    flex-direction: column;
    gap: 2px;
    order: 0;
  }
  .footer-copyright { order: 1; }
  .footer-note { font-size: 14px; order: 0; }
  .footer-copyright { font-size: 11px; }
  .footer-utils { gap: 12px; flex-shrink: 0; }
  .cv-button { border-width: 2px; }
  .cv-button::before { border-radius: 5px; }

}

/* The header must be a positioning context for the dropdown at every width. */
.site-header { position: relative; }

/* ============================================================================
   HOVER — POINTER DEVICES ONLY

   Bound behind (hover: hover) so touch never receives it. On a phone a
   :hover rule fires on tap and then STICKS until you tap elsewhere, which
   would leave links frozen mid-gradient. Touch gets :active instead, which
   is already defined above and releases with the finger.
   ========================================================================= */
@media (hover: hover) and (pointer: fine) {
  .link-grad:hover { color: transparent; font-weight: 500; }
  .wordmark-link:hover { color: transparent; }
  .icon-link:hover .li-glyph { fill: url(#liGrad); }

  .cv-button:hover { border-color: transparent; }
  .cv-button:hover::before { opacity: 1; }
  .cv-button:hover .label { color: var(--surface); }
  .cv-button:hover .cv-arrow { fill: var(--surface); }
  .cv-button:hover svg { transform: rotate(90deg) translateX(3px); }

  .hero-cta:hover .label { color: transparent; font-weight: 500; }
  .hero-cta:hover .arrow {
    transform: translateX(6px);
    color: var(--grad-c);
    font-weight: 500;
  }

  .mobile-menu a:hover { color: transparent; font-weight: 500; }

  /* The burger fills with the gradient like everything else. It only exists
     below 768px, so this fires on a narrow pointer window — but the language
     stays consistent wherever it is seen. */
  .nav-toggle:hover::before { opacity: 0; }
  .nav-toggle:hover .bars span { background: var(--surface); }
}

/* ============================================================================
   TAP FEEDBACK SPEED

   A tap lasts roughly 100ms before navigation begins. The 0.4s transition
   used for hover is a slow reveal — appropriate when a cursor is resting on
   something, useless when a finger is leaving it. These states therefore
   snap in, so the gradient is fully visible within the touch, then release
   at the normal speed.
   ========================================================================= */
.link-grad:active,
.hero-cta:active .label,
.hero-cta:active .arrow,
.cv-button:active,
.cv-button:active .label,
.cv-button:active .cv-arrow,
.icon-link:active .li-glyph,
.mobile-menu a:active {
  transition-duration: .08s;
}
.cv-button:active::before { transition-duration: .08s; }
