/* ==========================================================================
   Components. Custom-element selectors (gv-*) style behaviour wrappers;
   plain class selectors style everything else.
   ========================================================================== */

@layer components {
  /* ------------------------------------------------------------------ *
   * Text & shared bits
   * ------------------------------------------------------------------ */

  .eyebrow {
    display: inline-block;
    font-family: var(--font-mono);
    font-size: var(--step--1);
    text-transform: uppercase;
    letter-spacing: 0.28em;
    color: var(--sand);
  }

  .eyebrow--center {
    display: block;
    text-align: center;
  }

  .lede {
    max-width: 42rem;
    color: var(--text-muted);
    font-size: var(--step-1);
  }

  .gradient-text {
    background-image: linear-gradient(
      110deg,
      var(--paper) 0%,
      var(--sand) 35%,
      var(--sky) 65%,
      var(--paper) 100%
    );
    background-size: 200% auto;
    background-clip: text;
    color: transparent;
    animation: gradient-pan 8s ease infinite;
  }

  .rule {
    display: block;
    width: 6rem;
    height: 1px;
    border: 0;
    background: linear-gradient(90deg, var(--sand), var(--violet), transparent);
  }

  .card {
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    background: var(--fill);
    backdrop-filter: blur(16px);
  }

  .chip {
    display: inline-block;
    padding: 0.25rem 0.5rem;
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    background: color-mix(in srgb, var(--paper) 5%, transparent);
    font-family: var(--font-mono);
    font-size: 0.65rem;
    color: var(--text-muted);
    white-space: nowrap;
  }

  .chip-row {
    display: flex;
    flex-wrap: wrap;
    gap: 0.375rem;
  }

  /* Decorative colour wash used behind the hero, footer and page headers.
     The blob elements are generated by <gv-aurora>; their look lives here. */
  gv-aurora {
    position: absolute;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    overflow: hidden;

    & span {
      position: absolute;
      border-radius: 50%;
      filter: blur(120px);
      animation: drift 18s ease-in-out infinite;
    }

    & .blob-1 {
      inset-block-start: 2rem;
      inset-inline-start: -6rem;
      width: 34rem;
      aspect-ratio: 1;
      background: color-mix(in srgb, var(--sand) 22%, transparent);
    }
    & .blob-2 {
      inset-block-start: 8rem;
      inset-inline-end: -5rem;
      width: 30rem;
      aspect-ratio: 1;
      background: color-mix(in srgb, var(--sky) 22%, transparent);
      animation-delay: -6s;
    }
    & .blob-3 {
      inset-block-end: -4rem;
      inset-inline-start: 33%;
      width: 28rem;
      aspect-ratio: 1;
      background: color-mix(in srgb, var(--violet) 22%, transparent);
      animation-delay: -12s;
    }
  }

  /* The footer wash is a single blob, nudged in from the edge. */
  .contact gv-aurora .blob-3 {
    inset-inline-start: 50%;
  }

  .grid-lines {
    position: absolute;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    background-image:
      linear-gradient(
        90deg,
        color-mix(in srgb, var(--paper) 4.5%, transparent) 1px,
        transparent 1px
      ),
      linear-gradient(
        color-mix(in srgb, var(--paper) 4.5%, transparent) 1px,
        transparent 1px
      );
    background-size: 64px 64px;
  }

  /* ------------------------------------------------------------------ *
   * Buttons
   * ------------------------------------------------------------------ */

  .btn {
    --btn-bg: transparent;
    --btn-fg: var(--text-strong);
    --btn-border: var(--line-strong);

    position: relative;
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.8rem 1.5rem;
    border: 1px solid var(--btn-border);
    border-radius: var(--radius-pill);
    background: var(--btn-bg);
    color: var(--btn-fg);
    font-size: var(--step--1);
    font-weight: 600;
    overflow: hidden;
    transition:
      translate var(--speed) var(--ease-out-expo),
      border-color var(--speed),
      color var(--speed),
      background-color var(--speed);

    &:hover {
      translate: 0 -2px;
    }

    & .arrow {
      transition: translate var(--speed) var(--ease-out-expo);
    }
    &:hover .arrow {
      translate: 4px 0;
    }
  }

  .btn--primary {
    --btn-bg: var(--paper);
    --btn-fg: var(--ink-900);
    --btn-border: var(--paper);

    /* Light sweep on hover. */
    &::after {
      content: '';
      position: absolute;
      inset-block: 0;
      inline-size: 40%;
      background: color-mix(in srgb, var(--sand) 45%, transparent);
      rotate: 12deg;
      translate: -120% 0;
    }
    &:hover::after {
      animation: shimmer 0.9s var(--ease-out-expo);
    }
  }

  .btn--ghost:hover {
    border-color: var(--sand);
    color: var(--sand);
  }

  .btn--outline {
    --btn-border: color-mix(in srgb, var(--sand) 50%, transparent);
    --btn-fg: var(--sand);
    padding: 0.45rem 1rem;

    &:hover {
      background: var(--sand);
      color: var(--ink-900);
    }
  }

  /* A project that is finished but not yet deployed. Reads as a button so it
     sits correctly beside the real ones, but is deliberately inert: no link,
     no hover lift, no shimmer sweep. */
  .btn--soon {
    --btn-bg: transparent;
    --btn-fg: color-mix(in srgb, var(--sand) 60%, transparent);
    --btn-border: color-mix(in srgb, var(--sand) 22%, transparent);
    cursor: default;

    &:hover {
      translate: none;
    }
  }

  .link-quiet {
    color: var(--text-faint);
    text-underline-offset: 4px;
    transition: color var(--speed);

    &:hover {
      color: var(--text-strong);
      text-decoration: underline;
    }
  }

  /* ------------------------------------------------------------------ *
   * Scroll progress + navigation
   * ------------------------------------------------------------------ */

  gv-scroll-progress {
    position: fixed;
    inset-block-start: 0;
    inset-inline: 0;
    z-index: 60;
    block-size: 3px;

    & i {
      display: block;
      block-size: 100%;
      background: linear-gradient(90deg, var(--sand), var(--violet), var(--sky));
      transform-origin: left center;
      scale: var(--progress, 0) 1;
      transition: scale 120ms linear;
    }
  }

  gv-site-nav {
    position: fixed;
    inset-block-start: 0;
    inset-inline: 0;
    z-index: 50;
    display: block;
    border-block-end: 1px solid transparent;
    transition:
      background-color var(--speed-slow) var(--ease-out-expo),
      border-color var(--speed-slow),
      backdrop-filter var(--speed-slow);

    &[stuck] {
      background: color-mix(in srgb, var(--ink-900) 80%, transparent);
      border-block-end-color: var(--line);
      backdrop-filter: blur(16px);
    }
  }

  .nav-bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    block-size: 4rem;
  }

  .nav-brand {
    display: flex;
    align-items: center;
    gap: 0.625rem;
    font-family: var(--font-display);
    font-size: var(--step--1);
    font-weight: 600;

    & .mark {
      display: grid;
      place-items: center;
      inline-size: 2rem;
      aspect-ratio: 1;
      border-radius: var(--radius-sm);
      background: linear-gradient(135deg, var(--sand), var(--violet));
      color: var(--ink-900);
      font-family: var(--font-mono);
      font-size: 0.7rem;
      transition: rotate var(--speed), scale var(--speed);
    }
    &:hover .mark {
      rotate: 6deg;
      scale: 1.1;
    }

    & .name {
      display: none;
    }
    @media (width >= 40em) {
      & .name {
        display: inline;
      }
    }
  }

  .nav-links {
    display: none;
    align-items: center;
    gap: 0.25rem;

    @media (width >= 48em) {
      display: flex;
    }

    & a {
      position: relative;
      padding: 0.375rem 0.875rem;
      border-radius: var(--radius-pill);
      font-size: var(--step--1);
      color: var(--text-muted);
      transition: color var(--speed);

      &::after {
        content: '';
        position: absolute;
        inset-inline: 0.875rem;
        inset-block-end: 0;
        block-size: 1px;
        background: var(--sand);
        scale: 0 1;
        transition: scale var(--speed) var(--ease-out-expo);
      }

      &:hover {
        color: var(--text-strong);
      }

      &[aria-current='true'] {
        color: var(--text-strong);
      }
      &[aria-current='true']::after {
        scale: 1 1;
      }
    }
  }

  /* "About me" gathers the three sections about him plus the résumé. It is a
     <details>, so with no scripting at all it still opens on click and is
     operable from the keyboard; the hover behaviour is the enhancement. */
  .nav-menu {
    position: relative;

    & > summary {
      display: flex;
      align-items: center;
      gap: 0.4rem;
      padding: 0.375rem 0.875rem;
      border-radius: var(--radius-pill);
      font-size: var(--step--1);
      color: var(--text-muted);
      cursor: pointer;
      list-style: none;
      transition: color var(--speed);
    }

    /* Safari draws its own disclosure triangle without this. */
    & > summary::-webkit-details-marker {
      display: none;
    }

    & > summary:hover,
    &[open] > summary {
      color: var(--text-strong);
    }

    & .caret {
      inline-size: 0.3rem;
      aspect-ratio: 1;
      border-inline-end: 1px solid currentColor;
      border-block-end: 1px solid currentColor;
      rotate: 45deg;
      translate: 0 -0.1em;
      transition: rotate var(--speed) var(--ease-out-expo);
    }

    &[open] .caret {
      rotate: 225deg;
      translate: 0 0.1em;
    }

    /* Underline the group while the reader is inside one of its sections,
       the same way a plain nav link marks itself current. */
    &[current] > summary {
      color: var(--text-strong);
    }
    &[current] > summary::after {
      content: '';
      position: absolute;
      inset-inline: 0.875rem;
      inset-block-start: 1.9rem;
      block-size: 1px;
      background: var(--sand);
    }
  }

  .nav-menu-panel {
    position: absolute;
    inset-block-start: calc(100% + 0.5rem);
    inset-inline-start: 50%;
    z-index: 20;
    translate: -50% 0;
    min-inline-size: max-content;
    padding: 0.375rem;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    /* Opaque, not tinted: this hangs over the marquee, and a moving row of
       chips reading through a menu is worse than no translucency at all. */
    background: var(--ink-700);
    box-shadow: 0 24px 60px -20px rgb(0 0 0 / 85%);

    & a {
      display: block;
      padding: 0.5rem 0.875rem;
      border-radius: var(--radius-sm);
      font-size: var(--step--1);
      color: var(--text-muted);
      white-space: nowrap;
      transition:
        color var(--speed),
        background-color var(--speed);

      &::after {
        content: none;
      }

      &:hover,
      &[aria-current='true'] {
        background: var(--fill-hover);
        color: var(--text-strong);
      }
    }
  }

  .nav-actions {
    display: flex;
    align-items: center;
    gap: 0.5rem;

    & .btn--outline {
      display: none;
      @media (width >= 40em) {
        display: inline-flex;
      }
    }
  }

  .nav-toggle {
    display: grid;
    place-items: center;
    inline-size: 2.25rem;
    aspect-ratio: 1;
    border: 1px solid var(--line-strong);
    border-radius: var(--radius-sm);

    @media (width >= 48em) {
      display: none;
    }

    & span {
      position: relative;
      display: block;
      inline-size: 1rem;
      block-size: 0.75rem;
    }

    & i {
      position: absolute;
      inset-inline: 0;
      block-size: 1px;
      background: var(--paper);
      transition:
        translate var(--speed) var(--ease-out-expo),
        rotate var(--speed) var(--ease-out-expo),
        opacity var(--speed);
    }
    & i:nth-child(1) {
      inset-block-start: 0;
    }
    & i:nth-child(2) {
      inset-block-start: 0.375rem;
    }
    & i:nth-child(3) {
      inset-block-start: 0.75rem;
    }
  }

  gv-site-nav[open] .nav-toggle {
    & i:nth-child(1) {
      translate: 0 0.375rem;
      rotate: 45deg;
    }
    & i:nth-child(2) {
      opacity: 0;
    }
    & i:nth-child(3) {
      translate: 0 -0.375rem;
      rotate: -45deg;
    }
  }

  .nav-drawer {
    display: grid;
    grid-template-rows: 0fr;
    background: color-mix(in srgb, var(--ink-900) 95%, transparent);
    backdrop-filter: blur(16px);
    transition: grid-template-rows var(--speed-slow) var(--ease-out-expo);

    @media (width >= 48em) {
      display: none;
    }

    & > div {
      overflow: hidden;
    }

    & a {
      display: block;
      padding-block: 0.875rem;
      border-block-end: 1px solid
        color-mix(in srgb, var(--paper) 5%, transparent);
      font-size: var(--step--1);
      color: var(--text);
      transition: color var(--speed);

      &:hover {
        color: var(--sand);
      }
    }

    /* The drawer has room to lay the "About me" group out flat, indented,
       rather than repeating the dropdown on a touch screen. */
    & .sub {
      padding-inline-start: 1.125rem;
      color: var(--text-muted);

      &::before {
        content: '';
        display: inline-block;
        inline-size: 0.5rem;
        margin-inline-start: -1.125rem;
        margin-inline-end: 0.625rem;
        border-block-end: 1px solid var(--line-strong);
        translate: 0 -0.25em;
      }
    }
  }

  gv-site-nav[open] .nav-drawer {
    grid-template-rows: 1fr;
    border-block-start: 1px solid var(--line);
  }

  /* ------------------------------------------------------------------ *
   * Hero
   * ------------------------------------------------------------------ */

  .hero {
    position: relative;
    isolation: isolate;
    display: flex;
    flex-direction: column;
    justify-content: center;
    min-block-size: 100svh;
    padding-block-start: 6rem;
    overflow: hidden;

    &::after {
      content: '';
      position: absolute;
      inset: 0;
      z-index: -1;
      pointer-events: none;
      background: linear-gradient(
        color-mix(in srgb, var(--ink-900) 40%, transparent),
        transparent 30%,
        var(--ink-900)
      );
    }
  }

  .hero-grid {
    display: grid;
    align-items: center;
    gap: 3rem;
    padding-block: 3rem;

    @media (width >= 62em) {
      grid-template-columns: 1.15fr 0.85fr;
    }
  }

  .hero-copy {
    display: flex;
    flex-direction: column;
    gap: 1.5rem;
  }

  .status {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;

    & .dot {
      position: relative;
      inline-size: 0.5rem;
      aspect-ratio: 1;
      border-radius: 50%;
      background: var(--emerald);

      &::before {
        content: '';
        position: absolute;
        inset: 0;
        border-radius: 50%;
        background: inherit;
        animation: pulse-ring 1.6s ease-out infinite;
      }
    }
  }

  .hero-name {
    font-size: var(--step-4);
    font-weight: 800;
    line-height: 1.02;

    & span {
      display: block;
    }
  }

  .hero-role {
    font-family: var(--font-mono);
    font-size: var(--step-1);
    color: var(--text);

    & .prompt {
      color: var(--sand);
    }
  }

  gv-rotating-text .caret {
    display: inline-block;
    inline-size: 2px;
    block-size: 1em;
    margin-inline-start: 2px;
    background: var(--sand);
    vertical-align: text-bottom;
    animation: caret-blink 1.1s step-end infinite;
  }

  .hero-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.75rem;
  }

  .hero-portrait {
    position: relative;
    inline-size: min(100%, 24rem);
    margin-inline: auto;
    animation: float 6s ease-in-out infinite;

    &::before {
      content: '';
      position: absolute;
      inset: -1rem;
      z-index: -1;
      border-radius: 2rem;
      background: linear-gradient(
        135deg,
        color-mix(in srgb, var(--sand) 40%, transparent),
        color-mix(in srgb, var(--violet) 25%, transparent),
        color-mix(in srgb, var(--sky) 40%, transparent)
      );
      filter: blur(32px);
    }

    & .card {
      position: relative;
      padding: 0.5rem;
      overflow: hidden;
    }

    & img {
      inline-size: 100%;
      border-radius: 0.75rem;
      object-fit: cover;
    }

    & figcaption {
      position: absolute;
      inset-inline: 0.5rem;
      inset-block-end: 0.5rem;
      padding: 0.75rem 1rem;
      border-radius: var(--radius-sm);
      background: color-mix(in srgb, var(--ink-900) 75%, transparent);
      backdrop-filter: blur(8px);
    }

    & .place {
      font-family: var(--font-display);
      font-size: var(--step--1);
      font-weight: 600;
    }
    & .since {
      font-family: var(--font-mono);
      font-size: 0.7rem;
      color: var(--text-muted);
    }
  }

  .stats {
    display: grid;
    gap: 1rem;
    padding-block: 1.5rem;
    border-block: 1px solid var(--line);

    @media (width >= 40em) {
      grid-template-columns: repeat(3, 1fr);
    }

    & dt {
      font-family: var(--font-display);
      font-size: var(--step-3);
      font-weight: 700;
      color: var(--sand);
      font-variant-numeric: tabular-nums;
    }

    & dd {
      font-size: var(--step--1);
      color: var(--text-muted);
    }
  }

  .scroll-cue {
    position: absolute;
    inset-block-end: 1rem;
    inset-inline-start: 50%;
    translate: -50% 0;
    display: none;
    flex-direction: column;
    align-items: center;
    gap: 0.25rem;
    pointer-events: none;

    @media (width >= 62em) {
      display: flex;
    }

    & span {
      font-family: var(--font-mono);
      font-size: 0.625rem;
      text-transform: uppercase;
      letter-spacing: 0.3em;
      color: var(--text-faint);
    }

    & i {
      inline-size: 1px;
      block-size: 2rem;
      background: linear-gradient(var(--sand), transparent);
      animation: scroll-cue 1.8s ease-in-out infinite;
    }
  }

  /* ------------------------------------------------------------------ *
   * Marquee
   * ------------------------------------------------------------------ */

  gv-marquee {
    --marquee-gap: 0.75rem;

    display: block;
    overflow: hidden;
    padding-block: 0.5rem;
    mask-image: linear-gradient(
      90deg,
      transparent,
      #000 12%,
      #000 88%,
      transparent
    );

    & .track {
      display: flex;
      inline-size: max-content;
      gap: var(--marquee-gap);
      animation: marquee var(--marquee-duration, 38s) linear infinite;
    }

    &:hover .track {
      animation-play-state: paused;
    }

    & .group {
      display: flex;
      flex-shrink: 0;
      gap: var(--marquee-gap);
    }

    & .chip {
      padding: 0.5rem 1rem;
      border-radius: var(--radius-pill);
      font-size: var(--step--1);
      color: var(--text);
      transition: border-color var(--speed), color var(--speed);

      &:hover {
        border-color: color-mix(in srgb, var(--sand) 60%, transparent);
        color: var(--sand);
      }
    }
  }

  /* ------------------------------------------------------------------ *
   * Section headings
   * ------------------------------------------------------------------ */

  .section-head {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.75rem;
    margin-block-end: 3rem;

    & h2 {
      font-size: var(--step-3);
      font-weight: 700;
    }

    & p {
      max-width: 44rem;
      color: var(--text-muted);
    }
  }

  .section-head--center {
    align-items: center;
    text-align: center;
  }

  /* ------------------------------------------------------------------ *
   * Spotlight cards (production work + personal projects)
   * ------------------------------------------------------------------ */

  gv-spotlight {
    position: relative;
    display: block;
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    background: color-mix(in srgb, var(--paper) 3.5%, transparent);
    overflow: hidden;
    block-size: 100%;
    transition:
      transform var(--speed) var(--ease-out-expo),
      border-color var(--speed),
      box-shadow var(--speed);

    /* Pointer-following highlight, painted above the background but below
       the content. Coordinates are written by the custom element. */
    &::before {
      content: '';
      position: absolute;
      inset: 0;
      z-index: 1;
      pointer-events: none;
      opacity: 0;
      background: radial-gradient(
        340px circle at var(--spot-x, 50%) var(--spot-y, 50%),
        color-mix(in srgb, var(--sand) 18%, transparent),
        transparent 62%
      );
      transition: opacity var(--speed);
    }

    &:hover {
      border-color: var(--line-strong);
      box-shadow: 0 24px 70px -30px color-mix(in srgb, var(--sky) 55%, transparent);
    }
    &:hover::before {
      opacity: 1;
    }

    & > a {
      position: relative;
      z-index: 2;
      display: flex;
      flex-direction: column;
      block-size: 100%;
    }
  }

  .work-banner {
    /* One named accent per card, rather than a gradient spelled out inline
       four times over. */
    --accent: var(--sand);
    --accent-deep: var(--sand);

    position: relative;
    block-size: 8rem;
    overflow: hidden;
    background-image: linear-gradient(
      135deg,
      color-mix(in srgb, var(--accent) 25%, transparent),
      color-mix(in srgb, var(--accent-deep) 10%, transparent),
      transparent
    );

    &[data-accent='sky'] {
      --accent: var(--sky);
      --accent-deep: var(--sky-deep);
    }
    &[data-accent='emerald'] {
      --accent: var(--emerald);
      --accent-deep: var(--emerald-deep);
    }
    &[data-accent='amber'] {
      --accent: var(--amber);
      --accent-deep: var(--amber-deep);
    }
    &[data-accent='violet'] {
      --accent: var(--violet);
      --accent-deep: var(--violet-deep);
    }

    & .glyph {
      position: absolute;
      inset-block-start: 50%;
      inset-inline-end: 1.25rem;
      translate: 0 -50%;
      font-size: 3.5rem;
      line-height: 1;
      color: color-mix(in srgb, var(--paper) 15%, transparent);
      transition: scale var(--speed-slow) var(--ease-out-expo), color var(--speed);
    }

    & .titles {
      position: absolute;
      inset-block-end: 1rem;
      inset-inline-start: 1.25rem;
    }

    & h3 {
      font-size: var(--step-1);
      font-weight: 700;
    }

    & .domain {
      font-family: var(--font-mono);
      font-size: var(--step--1);
      color: var(--text-muted);
    }
  }

  gv-spotlight:hover .work-banner .glyph {
    scale: 1.1;
    color: color-mix(in srgb, var(--paper) 25%, transparent);
  }

  .work-body {
    display: flex;
    flex: 1;
    flex-direction: column;
    gap: 1rem;
    padding: 1.25rem;

    & .role {
      font-family: var(--font-mono);
      font-size: 0.7rem;
      text-transform: uppercase;
      letter-spacing: 0.06em;
      color: var(--sand);
    }

    & .desc {
      font-size: var(--step--1);
      color: var(--text);
    }

    & .chip-row {
      margin-block-start: auto;
    }

    & .go {
      display: inline-flex;
      align-items: center;
      gap: 0.375rem;
      font-size: var(--step--1);
      font-weight: 500;
      color: var(--sand);
    }
  }

  gv-spotlight:hover .go .arrow {
    translate: 3px -3px;
  }

  .go .arrow {
    transition: translate var(--speed) var(--ease-out-expo);
  }

  .metrics {
    display: flex;
    flex-wrap: wrap;
    gap: 1.25rem;

    & .value {
      font-family: var(--font-display);
      font-size: var(--step-1);
      font-weight: 700;
    }

    & .label {
      font-size: 0.75rem;
      color: var(--text-faint);
    }
  }

  /* ------------------------------------------------------------------ *
   * Experience timeline
   * ------------------------------------------------------------------ */

  .timeline {
    position: relative;
    border-inline-start: 1px solid var(--line);
    padding-inline-start: clamp(1.5rem, 4vw, 2.5rem);

    & > li {
      position: relative;
      padding-block-end: 3rem;
    }
    & > li:last-child {
      padding-block-end: 0;
    }
  }

  .timeline-node {
    position: absolute;
    inset-block-start: 0.375rem;
    inset-inline-start: calc(clamp(1.5rem, 4vw, 2.5rem) * -1 - 0.4375rem);
    display: grid;
    place-items: center;
    inline-size: 0.875rem;
    aspect-ratio: 1;

    & i {
      inline-size: 100%;
      aspect-ratio: 1;
      border: 2px solid var(--line-strong);
      border-radius: 50%;
      background: var(--ink-800);
    }
  }

  .timeline-node--current i {
    border-color: var(--sand);
    background: var(--sand);
  }

  .timeline-node--current::after {
    content: '';
    position: absolute;
    inline-size: 100%;
    aspect-ratio: 1;
    border-radius: 50%;
    background: color-mix(in srgb, var(--sand) 60%, transparent);
    animation: pulse-ring 1.8s ease-out infinite;
  }

  .job {
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    background: color-mix(in srgb, var(--paper) 3%, transparent);
    padding: 1.5rem;
    transition:
      translate var(--speed) var(--ease-out-expo),
      border-color var(--speed),
      background-color var(--speed);

    &:hover {
      translate: 0 -4px;
      border-color: color-mix(in srgb, var(--sand) 30%, transparent);
      background: var(--fill-hover);
    }

    & .job-head {
      display: flex;
      flex-wrap: wrap;
      align-items: baseline;
      justify-content: space-between;
      gap: 0.5rem;
    }

    & h3 {
      font-size: var(--step-1);
      font-weight: 700;
    }

    & .period {
      padding: 0.25rem 0.75rem;
      border: 1px solid var(--line);
      border-radius: var(--radius-pill);
      font-family: var(--font-mono);
      font-size: 0.7rem;
      color: var(--text-muted);
      white-space: nowrap;
    }

    & .employer {
      display: inline-flex;
      flex-wrap: wrap;
      align-items: baseline;
      gap: 0.5rem;
      margin-block-start: 0.25rem;
      color: var(--sand);
      text-underline-offset: 4px;

      &:hover {
        text-decoration: underline;
      }

      & small {
        font-family: var(--font-mono);
        font-size: 0.7rem;
        color: var(--text-faint);
      }
    }

    & .blurb {
      margin-block-start: 0.75rem;
      font-size: var(--step--1);
      font-style: italic;
      color: var(--text-muted);
    }
  }

  .bullets {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    margin-block: 1rem;

    & li {
      display: flex;
      gap: 0.75rem;
      font-size: var(--step--1);
      color: var(--text);

      &::before {
        content: '';
        flex-shrink: 0;
        margin-block-start: 0.6em;
        inline-size: 4px;
        aspect-ratio: 1;
        border-radius: 50%;
        background: var(--bullet-color, var(--sand));
      }
    }
  }

  /* ------------------------------------------------------------------ *
   * About
   * ------------------------------------------------------------------ */

  .about-grid {
    display: grid;
    gap: 2.5rem;

    @media (width >= 62em) {
      grid-template-columns: 0.85fr 1.15fr;
    }
  }

  .portrait {
    position: relative;

    &::before {
      content: '';
      position: absolute;
      inset: -0.75rem;
      z-index: -1;
      border-radius: var(--radius-lg);
      background: linear-gradient(
        135deg,
        color-mix(in srgb, var(--sand) 30%, transparent),
        color-mix(in srgb, var(--sky) 30%, transparent)
      );
      filter: blur(32px);
      opacity: 0;
      transition: opacity var(--speed-slow);
    }

    &:hover::before {
      opacity: 1;
    }

    /* The source is a 3:2 landscape shot with the subject far right, so the
       frame sets the crop and --portrait-focus keeps his face in it. */
    --portrait-ratio: 4 / 5;
    --portrait-focus: 90% 30%;

    @media (width >= 62em) {
      --portrait-ratio: 3 / 4;
    }

    & .frame {
      position: relative;
      aspect-ratio: var(--portrait-ratio);
      border: 1px solid var(--line);
      border-radius: var(--radius-lg);
      overflow: hidden;
    }

    & img {
      inline-size: 100%;
      block-size: 100%;
      object-fit: cover;
      object-position: var(--portrait-focus);
      transition: scale var(--speed-slow) var(--ease-out-expo);
    }

    &:hover img {
      scale: 1.05;
    }
  }

  .bio {
    border-inline-start: 2px solid var(--sand);
    padding-inline-start: 1.25rem;

    & p + p {
      margin-block-start: 1rem;
      color: var(--text-muted);
    }

    & p:first-child {
      font-size: var(--step-1);
      color: var(--text);
    }
  }

  /* Education sits beside a longer list of certifications, so the two are
     given proportional widths rather than equal ones. */
  .credential-grid {
    display: grid;
    gap: 1.25rem;
    /* Two entries next to five: let each card end where its content does
       rather than stretching the short one to match. */
    align-items: start;

    @media (width >= 52em) {
      grid-template-columns: 1fr 1.35fr;
    }
  }

  .credential-card {
    padding: 1.5rem;
    transition: translate var(--speed) var(--ease-out-expo);

    &:hover {
      translate: 0 -4px;
    }

    & h3 {
      margin-block-end: 1rem;
    }

    & .degree {
      display: block;
      font-weight: 600;
      color: var(--text-strong);
    }

    & .school {
      display: block;
      font-family: var(--font-mono);
      font-size: var(--step--1);
      color: var(--text-faint);
    }

    & li + li {
      margin-block-start: 0.875rem;
    }
  }

  /* ------------------------------------------------------------------ *
   * Services accordion — native <details>, animated open/close
   * ------------------------------------------------------------------ */

  .services {
    border-block: 1px solid var(--line);
  }

  .service {
    border-block-end: 1px solid var(--line);

    &:last-child {
      border-block-end: 0;
    }

    /* Lets the closed -> open height change animate. */
    &::details-content {
      block-size: 0;
      overflow: hidden;
      transition:
        block-size var(--speed-slow) var(--ease-out-expo),
        content-visibility var(--speed-slow) allow-discrete;
    }

    &[open]::details-content {
      block-size: auto;
    }

    & summary {
      display: flex;
      align-items: center;
      justify-content: space-between;
      gap: 1.5rem;
      padding-block: 1.25rem;
      list-style: none;
      cursor: pointer;

      &::-webkit-details-marker {
        display: none;
      }
    }

    & .service-title {
      display: flex;
      align-items: baseline;
      gap: 1rem;
      font-family: var(--font-display);
      font-size: var(--step-1);
      font-weight: 600;
      color: var(--text);
      transition: color var(--speed);
    }

    & .num {
      font-family: var(--font-mono);
      font-size: var(--step--1);
      color: color-mix(in srgb, var(--sand) 70%, transparent);
    }

    &:hover .service-title {
      color: var(--text-strong);
    }

    &[open] .service-title {
      color: var(--sand);
    }

    & .plus {
      position: relative;
      display: grid;
      place-items: center;
      flex-shrink: 0;
      inline-size: 1.75rem;
      aspect-ratio: 1;
      border: 1px solid var(--line-strong);
      border-radius: 50%;
      color: var(--text-muted);
      transition: rotate var(--speed) var(--ease-out-expo), color var(--speed),
        border-color var(--speed);

      &::before,
      &::after {
        content: '';
        position: absolute;
        background: currentColor;
      }
      &::before {
        inline-size: 0.75rem;
        block-size: 1px;
      }
      &::after {
        inline-size: 1px;
        block-size: 0.75rem;
      }
    }

    &[open] .plus {
      rotate: 45deg;
      border-color: var(--sand);
      color: var(--sand);
    }

    & .service-body {
      max-width: 52rem;
      padding-block-end: 1.5rem;
      padding-inline-start: 2.5rem;
      font-size: var(--step--1);
      color: var(--text-muted);
    }
  }

  /* ------------------------------------------------------------------ *
   * Personal project cards
   * ------------------------------------------------------------------ */

  .project-shot {
    position: relative;
    aspect-ratio: 16 / 10;
    overflow: hidden;

    & img {
      inline-size: 100%;
      block-size: 100%;
      object-fit: cover;
      object-position: top;
      transition: scale var(--speed-slow) var(--ease-out-expo);
    }

    &::after {
      content: '';
      position: absolute;
      inset: 0;
      background: linear-gradient(
        transparent,
        color-mix(in srgb, var(--ink-900) 30%, transparent),
        var(--ink-900)
      );
    }
  }

  gv-spotlight:hover .project-shot img {
    scale: 1.1;
  }

  .project-body {
    display: flex;
    flex: 1;
    flex-direction: column;
    gap: 0.75rem;
    padding: 1.25rem;

    & h3 {
      font-size: var(--step-1);
      font-weight: 700;
      transition: color var(--speed);
    }

    & p {
      font-size: var(--step--1);
      color: var(--text-muted);
    }

    & .chip-row {
      margin-block-start: auto;
    }

    & .go {
      display: inline-flex;
      align-items: center;
      gap: 0.375rem;
      font-size: var(--step--1);
      font-weight: 500;
      color: var(--sand);
    }
  }

  gv-spotlight:hover .project-body h3 {
    color: var(--sand);
  }

  /* ------------------------------------------------------------------ *
   * Contact / footer
   * ------------------------------------------------------------------ */

  .contact {
    position: relative;
    isolation: isolate;
    overflow: hidden;
    border-block-start: 1px solid var(--line);
    padding-block-start: var(--section-gap);

    & h2 {
      max-width: 46rem;
      margin-inline: auto;
      margin-block-start: 1rem;
      font-size: var(--step-3);
      font-weight: 800;
      text-align: center;
    }

    & .contact-lede {
      max-width: 36rem;
      margin-inline: auto;
      margin-block-start: 1.25rem;
      color: var(--text-muted);
      text-align: center;
    }
  }

  .contact-actions {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 0.75rem;
    margin-block-start: 2.25rem;
  }

  .contact-cards {
    --col-min: 15rem;
    margin-block-start: 4rem;
  }

  .contact-card {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding: 1.25rem;
    transition: translate var(--speed) var(--ease-out-expo),
      border-color var(--speed);

    &:hover {
      translate: 0 -4px;
      border-color: color-mix(in srgb, var(--sand) 40%, transparent);
    }

    & .kind {
      display: block;
      font-family: var(--font-mono);
      font-size: 0.7rem;
      text-transform: uppercase;
      letter-spacing: 0.06em;
      color: var(--text-faint);
    }

    & .handle {
      display: block;
      margin-block-start: 0.25rem;
      font-size: var(--step--1);
    }

    & .arrow {
      color: var(--sand);
      transition: translate var(--speed) var(--ease-out-expo);
    }

    &:hover .arrow {
      translate: 4px 0;
    }
  }

  .colophon {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    margin-block-start: 4rem;
    padding-block: 2rem;
    border-block-start: 1px solid var(--line);
    font-family: var(--font-mono);
    font-size: 0.7rem;
    color: var(--text-faint);

    @media (width >= 40em) {
      flex-direction: row;
    }
  }

  /* ------------------------------------------------------------------ *
   * Project detail pages
   * ------------------------------------------------------------------ */

  .page-header {
    position: relative;
    isolation: isolate;
    overflow: hidden;
    border-block-end: 1px solid var(--line);
    padding-block: 5rem;

    & h1 {
      margin-block-start: 0.75rem;
      font-size: var(--step-3);
      font-weight: 800;
    }

    & .rule {
      margin-block-start: 1.25rem;
    }
  }

  .detail-grid {
    display: grid;
    gap: 3rem;

    @media (width >= 62em) {
      grid-template-columns: 1.1fr 0.9fr;
    }
  }

  .detail-block {
    border-inline-start: 2px solid color-mix(in srgb, var(--sand) 70%, transparent);
    padding-inline-start: 1.25rem;

    & h2 {
      margin-block-end: 0.5rem;
    }

    & p {
      color: var(--text);
    }
  }

  .detail-shot {
    position: relative;

    @media (width >= 62em) {
      position: sticky;
      inset-block-start: 6rem;
    }

    &::before {
      content: '';
      position: absolute;
      inset: -0.75rem;
      z-index: -1;
      border-radius: var(--radius-lg);
      background: linear-gradient(
        135deg,
        color-mix(in srgb, var(--sand) 25%, transparent),
        color-mix(in srgb, var(--sky) 25%, transparent)
      );
      filter: blur(32px);
      opacity: 0;
      transition: opacity var(--speed-slow);
    }

    &:hover::before {
      opacity: 1;
    }

    & .frame {
      border: 1px solid var(--line);
      border-radius: var(--radius-lg);
      overflow: hidden;
    }

    & img {
      inline-size: 100%;
      transition: scale var(--speed-slow) var(--ease-out-expo);
    }

    &:hover img {
      scale: 1.05;
    }
  }

  .tech-chips .chip {
    padding: 0.375rem 0.625rem;
    font-size: 0.7rem;
    transition: border-color var(--speed), color var(--speed);

    &:hover {
      border-color: color-mix(in srgb, var(--sand) 50%, transparent);
      color: var(--sand);
    }
  }

  .detail-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem;
  }

  .back-bar {
    position: sticky;
    inset-block-start: 0;
    z-index: 50;
    border-block-end: 1px solid var(--line);
    background: color-mix(in srgb, var(--ink-900) 80%, transparent);
    backdrop-filter: blur(16px);

    & .bar {
      display: flex;
      align-items: center;
      justify-content: space-between;
      gap: 1rem;
      block-size: 4rem;
    }

    & .back {
      display: inline-flex;
      align-items: center;
      gap: 0.5rem;
      font-size: var(--step--1);
      color: var(--text);
      transition: color var(--speed);

      & .arrow {
        transition: translate var(--speed) var(--ease-out-expo);
      }

      &:hover {
        color: var(--text-strong);
      }
      &:hover .arrow {
        translate: -4px 0;
      }
    }

    & .who {
      font-family: var(--font-display);
      font-weight: 600;
    }
  }
}

/* ==========================================================================
   Scroll reveal.
   Keyed off `data-reveal`, an attribute <gv-reveal> stamps onto whatever it
   is about to animate. No element is ever hidden by this stylesheet alone:
   if scripting is off or the module fails to load the attribute is never
   written and the content is simply visible.

   The reveal runs as an animation rather than a transition so that it stops
   contributing once it has finished — the cards underneath keep their own
   hover lifts instead of being pinned by a permanent `translate: 0 0`.
   ========================================================================== */
@layer components {
  gv-reveal {
    display: block;
  }

  /* A staggering group is bookkeeping, not a box: its children stay direct
     children of whatever grid or flex container surrounds it. */
  gv-reveal[stagger] {
    display: contents;
  }

  [data-reveal] {
    opacity: 0;
  }

  [data-reveal]:not([revealed]) {
    will-change: opacity, translate, scale;
  }

  [data-reveal][revealed] {
    opacity: 1;
    /* `backwards` holds the hidden first frame through the stagger delay,
       and nothing is held once the animation is over. */
    animation: fade-up var(--speed-slow) var(--ease-out-expo)
      var(--reveal-delay, 0ms) backwards;
  }

  [data-reveal='left'][revealed] {
    animation-name: fade-left;
  }
  [data-reveal='right'][revealed] {
    animation-name: fade-right;
  }
  [data-reveal='scale'][revealed] {
    animation-name: fade-scale;
  }

  /* <gv-reveal> leaves everything unmarked when reduced motion is set at load
     time; this covers the setting being turned on partway through a visit. */
  @media (prefers-reduced-motion: reduce) {
    [data-reveal] {
      opacity: 1;
    }
  }
}
