/* ==========================================================================
   The step — the site's hover language (Round 1, Oct 2026).

   Linked by every page after its last stylesheet (after home.css on the home
   page), so every rule here overrides the component it names. NOT linked by
   build/home/v0.html: the frozen pre-fix home page keeps the hover behaviour
   it shipped with — which is also why the sliding nav pill's code is still in
   site.js and site.css, and is hidden here rather than deleted there.

   THE RULE. Hover fills what you can click, in steps. Surfaces and commands
   — the project cards, the nav, buttons, the logo — fill from the leading
   edge in two equal bands, the top band first and the other --step-stagger
   behind it, on --step-ease. Words draw a line; arrows move one notch the way
   they point. Nothing that is not clickable moves, and no hover ever lowers
   contrast (on a filled plate, muted text steps up to ink). Keyboard focus
   shows the hover state plus the ring.

   MOUSE ONLY. Everything sits inside (hover: hover) and (pointer: fine).
   Touch browsers apply :hover on tap and leave it stuck, which would strand a
   card or a nav tab half-filled after a back navigation; on touch screens the
   site keeps exactly the behaviour it had before this file existed.

   Timing, easing and the fills are tokens in tokens.css (--step-*,
   --hover-fill, --selected-fill), so cards, nav, buttons and the logo cannot
   drift apart. Every number was settled on a lab board, removed before release;
   it is in the history at d71b4a9 (site/_lab/_motion.html) if it is ever needed.

   Sections, in the order they shipped: cards and words, the nav, buttons and
   the logo. Each carries its own reduced-motion handling, so each can be
   rolled back on its own.
   ========================================================================== */


/* === 1. Cards and words =================================================== */

@media (hover: hover) and (pointer: fine) {

  /* --- Home card -------------------------------------------------------------
     The text column fills with the hover mauve in two bands, the top band
     first. The mauve is the covers' own margin colour (--color-line) mixed
     into the card white, so the column reads as a lighter step of its cover
     and the card as one plate. The card itself stays white — the old warm
     tint took it toward the warm band it sits on, which lost the separation —
     and the frame steps up one notch to the full hairline. */
  .home-card:hover,
  .home-card:focus-visible {
    background-color: var(--color-plate);
    border-color: var(--color-line);
  }

  .home-card__body {
    position: relative;
    isolation: isolate;
  }

  .home-card__body::before,
  .home-card__body::after,
  .project-card__body::before,
  .project-card__body::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    background: var(--hover-fill);
    clip-path: inset(0 100% 0 0);
    transition: clip-path var(--step-out) var(--ease);
  }

  .home-card__body::after,
  .project-card__body::after {
    bottom: 50%;
  }

  .home-card:is(:hover, :focus-visible) .home-card__body::before,
  .home-card:is(:hover, :focus-visible) .home-card__body::after,
  .project-card:is(:hover, :focus-visible) .project-card__body::before,
  .project-card:is(:hover, :focus-visible) .project-card__body::after {
    clip-path: inset(0);
    transition-duration: var(--step-card-in);
    transition-timing-function: var(--step-ease);
  }

  .home-card:is(:hover, :focus-visible) .home-card__body::before,
  .project-card:is(:hover, :focus-visible) .project-card__body::before {
    transition-delay: var(--step-card-stagger);
    transition-duration: calc(var(--step-card-in) * var(--step-card-trail));
  }

  /* Muted text does not hold 4.5:1 on the mauve (4.15 measured), so every
     muted line on a hovered plate steps up to the body ink, and the quiet tag
     chips turn white — holes in the tint, which keeps their muted text at
     5.2. Both cards' one-liners are already ink. */
  .home-card__meta,
  .project-card__meta,
  .project-card__desc,
  .home-card .tags--quiet .tag {
    transition: color var(--btn-ink-dur) var(--ease),
      background-color var(--btn-ink-dur) var(--ease);
  }

  .home-card:is(:hover, :focus-visible) .home-card__meta,
  .project-card:is(:hover, :focus-visible) .project-card__meta {
    color: var(--color-text);
    transition-delay: calc(var(--step-card-stagger) / 2);
  }

  .home-card:is(:hover, :focus-visible) .tags--quiet .tag {
    background: var(--color-plate);
    transition-delay: calc(var(--step-card-stagger) / 2);
  }

  /* --- Projects-page card ------------------------------------------------------
     The same fill, on the caption only: from the cover's bottom edge (the
     body's own top margin, --sp-6) to one step below the text, flush with the
     cover's sides — nothing passes the gallery's grid line. The caption slides
     one step inward so the text clears the plate's edge, and a
     hairline frame wraps cover and caption as one object, so the card still
     reads as a card when only its cover is above the fold. Transform and
     opacity only: the caption's box never moves. The cover no longer zooms
     and the name stays ink — the plate is the signal, and the call to action
     answers it as the home cards' does. */
  .project-card {
    position: relative;
    --caption-shift: var(--sp-4);
  }

  .project-card__body {
    position: relative;
    isolation: isolate;
  }

  .project-card__body::before,
  .project-card__body::after {
    inset: calc(-1 * var(--sp-6)) 0;
    border-radius: 0 0 var(--radius-lg) var(--radius-lg);
  }

  .project-card__body > * {
    transition: transform var(--step-out) var(--ease);
  }

  .project-card:is(:hover, :focus-visible) .project-card__body > * {
    transform: translateX(var(--caption-shift));
    transition-duration: var(--step-card-in);
    transition-timing-function: var(--step-ease);
  }

  .project-card::after {
    content: "";
    position: absolute;
    inset: 0 0 calc(-1 * var(--sp-6)) 0;
    z-index: 2;
    border: 1px solid var(--color-line);
    border-radius: var(--radius-lg);
    pointer-events: none;
    opacity: 0;
    transition: opacity var(--step-out) var(--ease);
  }

  .project-card:is(:hover, :focus-visible)::after {
    opacity: 1;
    transition-duration: var(--step-card-in);
  }

  .project-card:hover .project-card__media img,
  .project-card:focus-visible .project-card__media img {
    transform: none;
  }

  .project-card:hover .project-card__name,
  .project-card:focus-visible .project-card__name {
    color: inherit;
  }

  /* --- Plain text link ----------------------------------------------------------
     Deeper blue and the underline from the left — what .link always did, minus the
     accent. */
  .link:hover,
  .link:focus-visible {
    color: var(--link-hover);
  }

  .link::after {
    transition-duration: var(--step-in);
    transition-timing-function: var(--step-ease);
  }

  /* --- Underlined links: the footer's contact links, the cards' "View case
     study" / "Get a sneak peek" (the same component), and the case studies' "Next project" ----------
     The resting underline stays the full ink — it is the affordance, and the
     reason these read as links at all. Hover draws the line on past the words
     to sit under the arrow, which slides in beside them: the underline grows,
     as it always did, without a width ever animating. The arrow is laid beside
     the link rather than inside its box, so the text never reflows. */
  .contact-link,
  .cs-next__link {
    position: relative;
  }

  .contact-link::after,
  .cs-next__link::after {
    content: "";
    position: absolute;
    left: 0;
    bottom: -1px;
    height: 1px;
    background: currentColor;
    transform: scaleX(0);
    transform-origin: left;
    transition: transform var(--step-out) var(--ease);
  }

  .contact-link::after {
    right: -1.4em;
  }

  .cs-next__link::after {
    right: -1.04em;
  }

  .contact-link:hover,
  .contact-link:focus-visible,
  .home-card:hover .contact-link,
  .home-card:focus-visible .contact-link,
  .project-card:hover .contact-link,
  .project-card:focus-visible .contact-link,
  .contact-link.is-copied,
  .cs-next__link:hover,
  .cs-next__link:focus-visible {
    color: var(--link-hover);
  }

  .contact-link:hover::after,
  .contact-link:focus-visible::after,
  .home-card:hover .contact-link::after,
  .home-card:focus-visible .contact-link::after,
  .project-card:hover .contact-link::after,
  .project-card:focus-visible .contact-link::after,
  .cs-next__link:hover::after,
  .cs-next__link:focus-visible::after {
    transform: scaleX(1);
    transition-duration: var(--step-in);
    transition-timing-function: var(--step-ease);
  }

  .contact-link__icon,
  .cs-next__icon {
    position: absolute;
    left: 100%;
    top: 50%;
    opacity: 0;
    transition: opacity var(--step-out) var(--ease),
      transform var(--step-out) var(--ease);
  }

  .contact-link__icon {
    width: 0.95em;
    margin-left: 0.45em;
    transform: translate(-0.3em, -50%);
  }

  .cs-next__icon {
    width: 0.7em;
    margin-left: 0.34em;
    transform: translate(-0.3em, -50%);
  }

  .contact-link:hover .contact-link__icon,
  .contact-link:focus-visible .contact-link__icon,
  .contact-link.is-copied .contact-link__icon,
  .home-card:hover .contact-link__icon,
  .home-card:focus-visible .contact-link__icon,
  .project-card:hover .contact-link__icon,
  .project-card:focus-visible .contact-link__icon,
  .cs-next__link:hover .cs-next__icon,
  .cs-next__link:focus-visible .cs-next__icon {
    opacity: 1;
    transform: translate(0, -50%);
    transition-duration: var(--step-in);
    transition-timing-function: var(--step-ease);
  }

  /* --- Arrow links: the case studies' back link, and "Top" ----------------------
     Ink, and the arrow moves one notch the way it points. "Top" used to declare
     a 2px lift of the whole link that never animated (only colour was in its
     transition); the arrow carries it now, and it animates. */
  .back-link:hover,
  .back-link:focus-visible {
    color: var(--link-hover);
  }

  .to-top:hover,
  .to-top:focus-visible {
    color: var(--hover-ink);
  }

  .back-link .arrow,
  .to-top svg {
    transition: transform var(--step-in) var(--step-ease);
  }

  .to-top:hover,
  .to-top:focus-visible {
    transform: none;
  }

  .to-top:hover svg,
  .to-top:focus-visible svg {
    transform: translateY(-0.2em);
  }

  /* --- Scroll cue (hero) -------------------------------------------------------
     It dimmed to 70% on hover — the one hover on the site that took something
     away. The arrow steps down instead, the way it points; the badge stays. */
  .scroll-cue:hover,
  .scroll-cue:focus-visible {
    opacity: 1;
  }

  .scroll-cue:hover svg,
  .scroll-cue:focus-visible svg {
    animation: none;
    transform: translateY(0.2em);
    transition: transform var(--step-in) var(--step-ease);
  }
}

/* base.css shortens every duration under reduced motion but not the delays,
   so a second band would still wait its stagger. Straight to the final state,
   and the caption does not shift at all — a text jump is motion too. */
@media (hover: hover) and (pointer: fine) and (prefers-reduced-motion: reduce) {
  .home-card__body::before,
  .home-card__body::after,
  .project-card__body::before,
  .project-card__body::after,
  .project-card::after,
  .home-card__meta,
  .project-card__meta,
  .project-card__desc,
  .home-card .tags--quiet .tag,
  .contact-link::after,
  .cs-next__link::after,
  .contact-link__icon,
  .cs-next__icon,
  .back-link .arrow,
  .to-top svg,
  .scroll-cue svg {
    transition: none !important;
  }

  .project-card:is(:hover, :focus-visible) .project-card__body > * {
    transform: none;
  }
}


/* === 2. Navigation ========================================================== */

/* The tab. The current page IS a filled tab, and hovering another item fills
   the same tab in two bands — one step lighter (--hover-fill against
   --selected-fill), so hover previews the selected state instead of
   duplicating it. The nav is a set of commands, so it takes the commands'
   fill rather than the words' line.

   The sliding pill and the current-page bar are hidden, not deleted: site.js
   still builds them, because v0 still uses them (see the header).

   48rem up only, where the links carry --nav-pad-x either side and the tab has
   a box to fill; below it the nav is the phone menu panel, untouched. The tab
   draws on the links' own pseudo-elements — ::after is .link's underline,
   which this nav never shows — scoped to .site-nav > a so a change to .link
   cannot leak in. */
@media (hover: hover) and (pointer: fine) and (min-width: 48rem) {
  .site-nav__pill,
  .site-nav__bar {
    display: none;
  }

  .js .site-nav > a {
    isolation: isolate;
  }

  .js .site-nav > a::before,
  .js .site-nav > a::after {
    content: "";
    display: block;
    position: absolute;
    inset: 0;
    z-index: -1;
    height: auto;
    border-radius: var(--radius-sm);
    background: var(--hover-fill);
    transform: none;
    clip-path: inset(0 100% 0 0);
    transition: clip-path var(--step-out) var(--ease);
  }

  .js .site-nav > a::after {
    bottom: 50%;
  }

  .js .site-nav > a[aria-current]::before,
  .js .site-nav > a[aria-current]::after {
    background: var(--selected-fill);
    clip-path: inset(0);
  }

  .js .site-nav > a:is(:hover, :focus-visible)::before,
  .js .site-nav > a:is(:hover, :focus-visible)::after {
    clip-path: inset(0);
    transition-duration: var(--step-in);
    transition-timing-function: var(--step-ease);
  }

  .js .site-nav > a:is(:hover, :focus-visible)::before {
    transition-delay: var(--step-stagger);
    transition-duration: calc(var(--step-in) * var(--step-trail));
  }

  /* Over the wine band (site.js sets .on-dark-ground there) the tabs are light
     tints of the wine, and a hovered label stays the light ink: the band's pink
     hover text holds 4.74 on bare wine and would not hold on a lifted tab. */
  .site-header.on-dark-ground .site-nav > a::before,
  .site-header.on-dark-ground .site-nav > a::after {
    background: var(--hover-fill-on-dark);
  }

  .site-header.on-dark-ground .site-nav > a[aria-current]::before,
  .site-header.on-dark-ground .site-nav > a[aria-current]::after {
    background: var(--selected-fill-on-dark);
  }

  .site-header.on-dark-ground .site-nav > a:hover,
  .site-header.on-dark-ground .site-nav > a:focus-visible {
    color: var(--ink-on-dark);
  }
}

@media (hover: hover) and (pointer: fine) and (min-width: 48rem) and (prefers-reduced-motion: reduce) {
  .js .site-nav > a::before,
  .js .site-nav > a::after {
    transition: none !important;
  }
}


/* === 3. Buttons and the logo ================================================= */

/* The label copy assets/js/motion.js adds is never shown to a touch screen,
   where the rest of this section does not apply. */
.btn__on {
  display: none;
}

@media (hover: hover) and (pointer: fine) {

  /* --- Button: the stepped fill ----------------------------------------------
     The outline fills from the left in two bands, the top band first — the
     hero's steps in miniature, replacing the corner circle. ::after is the top
     band (the top half only, painted last); ::before is the lower band, the
     full height, starting --step-stagger later and travelling --step-trail
     times slower. The lag lives on small things: on a 40px button an offset
     only reads as a step if it stays on screen. The first band is untouched,
     so the feedback is never late — only its echo. --btn-fill / --btn-on-fill
     still come from the ground, so the wine band's buttons need nothing new. */
  .btn::before,
  .btn::after {
    content: "";
    position: absolute;
    inset: -1px;
    z-index: -1;
    border-radius: calc(var(--radius-sm) + 1px);
    background: var(--btn-fill);
    clip-path: inset(0 100% 0 0);
    transition: clip-path var(--step-out) var(--ease);
  }

  .btn::after {
    bottom: 50%;
  }

  .btn:hover::before,
  .btn:hover::after,
  .btn:focus-visible::before,
  .btn:focus-visible::after {
    clip-path: inset(0);
    transition-duration: var(--step-in);
    transition-timing-function: var(--step-ease);
  }

  .btn:hover::before,
  .btn:focus-visible::before {
    transition-delay: var(--step-stagger);
    transition-duration: calc(var(--step-in) * var(--step-trail));
  }

  /* The label, in two colours at once. With two bands on two clocks, one label
     colour is always wrong on one half. assets/js/motion.js adds a copy of the
     label in the on-fill colour, split into the same two bands and clipped on
     the fill's own timing, and marks the button .is-split; the real label then
     never flips. Without the script, site.css's single flip still runs. */
  .btn.is-split:hover,
  .btn.is-split:focus-visible {
    color: var(--btn-ink);
  }

  .btn__on {
    display: block;
    position: absolute;
    inset: 0;
    z-index: 1;
    padding: inherit;
    gap: inherit;
    pointer-events: none;
    /* Dragging across a button should copy its label once, not three times. */
    user-select: none;
  }

  /* Each copy spans the box the fill paints — 1px past the button on every
     side, over the border — so its clip runs in lockstep with the fill's. The
     transparent border gives that pixel back, so the copy's text lands exactly
     on the real label's. */
  .btn__on-band {
    position: absolute;
    inset: -1px;
    display: flex;
    align-items: center;
    gap: inherit;
    padding: inherit;
    border: 1px solid transparent;
    color: var(--btn-on-fill);
    transition: clip-path var(--step-out) var(--ease);
  }

  .btn__on-band--top {
    clip-path: inset(0 100% 50% 0);
  }

  .btn__on-band--bottom {
    clip-path: inset(50% 100% 0 0);
  }

  .btn:hover .btn__on-band--top,
  .btn:focus-visible .btn__on-band--top {
    clip-path: inset(0 0 50% 0);
    transition-duration: var(--step-in);
    transition-timing-function: var(--step-ease);
  }

  .btn:hover .btn__on-band--bottom,
  .btn:focus-visible .btn__on-band--bottom {
    clip-path: inset(50% 0 0 0);
    transition-delay: var(--step-stagger);
    transition-duration: calc(var(--step-in) * var(--step-trail));
    transition-timing-function: var(--step-ease);
  }

  /* --- Logo: the same fill, to the pink plate ----------------------------------
     The resting plate moves to the link's own background, which frees both
     pseudo-elements to be the two bands. Same order as the button: ::after is
     the top band and leads. The glyph is a real element on top of all of it,
     so it never gets re-cut by the fill. */
  .logomark {
    background: currentColor;
  }

  .logomark::before,
  .logomark::after {
    background: var(--color-highlight);
    clip-path: inset(0 100% 0 0);
    transition: clip-path var(--step-out) var(--ease);
  }

  /* Over the violet band the coral wipe would sit at 2.7:1; it takes the
     band's own accent there, the same pale pink its links turn on hover. */
  .site-header.on-dark-ground .logomark::before,
  .site-header.on-dark-ground .logomark::after {
    background: var(--on-band-accent);
  }

  .logomark::after {
    bottom: 50%;
  }

  .logomark:hover::before,
  .logomark:hover::after,
  .logomark:focus-visible::before,
  .logomark:focus-visible::after {
    clip-path: inset(0);
    transition-duration: var(--step-in);
    transition-timing-function: var(--step-ease);
  }

  .logomark:hover::before,
  .logomark:focus-visible::before {
    transition-delay: var(--step-stagger);
    transition-duration: calc(var(--step-in) * var(--step-trail));
  }
}

@media (hover: hover) and (pointer: fine) and (prefers-reduced-motion: reduce) {
  .btn::before,
  .btn::after,
  .btn__on-band,
  .logomark::before,
  .logomark::after {
    transition: none !important;
  }
}


/* === 4. The About fill ======================================================

   The one scroll effect on the site, and it never hides anything. Each word
   of the About paragraph goes from the band's ink at --fill-from (2.72:1 —
   faint, but legible; see the note on the token) to full ink as the paragraph moves up the window, one
   after the other. Scroll-driven, not timed: it moves exactly as fast as the
   page, cannot lag a fast scroll, and stops where you stop.

   The words are spans motion.js adds. Without the script, without
   scroll-driven animations (Firefox by default) or with reduced motion, the
   paragraph is plain full-ink text. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    #about .lead {
      view-timeline-name: --about-lead;
    }

    #about .lead .fw {
      color: color-mix(in srgb, var(--ink-on-dark) var(--fill-from), var(--ground-ink));
      animation: about-fill linear both;
      animation-timeline: --about-lead;
      animation-range-start: cover calc(var(--fill-start) + var(--p) * var(--fill-span));
      animation-range-end: cover calc(var(--fill-start) + var(--p) * var(--fill-span) + var(--fill-word));
    }
  }
}

@keyframes about-fill {
  to { color: var(--ink-on-dark); }
}
