@font-face
   {
      font-family: 'Geist Mono';
      src: url('assets/fonts/geist-mono-latin.woff2') format('woff2');
      font-style: normal;
      font-weight: 300;
      font-display: swap;
   }

@font-face
   {
      font-family: 'Instrument Serif';
      src: url('assets/fonts/instrument-serif-latin-400.woff2') format('woff2');
      font-style: normal;
      font-weight: 400;
      font-display: swap;
   }

@font-face
   {
      font-family: 'Haval';
      src: url('assets/fonts/Haval-Regular.woff2') format('woff2');
      font-style: normal;
      font-weight: 400;
      font-display: swap;
   }

@font-face
   {
      font-family: 'Bounded';
      src: url('assets/fonts/bounded-variable.woff2') format('woff2');
      font-style: normal;
      font-weight: 200 900;
      font-display: swap;
   }

@font-face
   {
      font-family: 'Google Sans';
      src: url('assets/fonts/google-sans-latin-400.woff2') format('woff2');
      font-style: normal;
      font-weight: 400;
      font-display: swap;
   }

@font-face
   {
      font-family: 'Unbounded Outline';
      src: url('assets/fonts/unbounded-latin-700-overlap.woff2') format('woff2');
      font-style: normal;
      font-weight: 700;
      font-display: swap;
   }

/* Only Let's connect's "Hola" uses it. */
@font-face
   {
      font-family: 'Unbounded';
      src: url('assets/fonts/unbounded-latin-900-normal.woff') format('woff');
      font-style: normal;
      font-weight: 900;
      font-display: swap;
   }

:root
   {
      --error: #ff8f7f;
      --background: #060606;
      --surface: #0a0a0a;
      --muted: rgba(255, 255, 255, .65);
      --border: rgba(255, 255, 255, .15);
      --ink: #060606;
      --white: #ffffff;
      --accent: #ffffff;
      color-scheme: dark;
      --edge: clamp(20px, 2.8vw, 56px);
      --ease: cubic-bezier(.2, .75, .25, 1);
      --ease-out: cubic-bezier(.23, 1, .32, 1);
      --ease-in-out: cubic-bezier(.77, 0, .175, 1);

      /* Bezier equivalents of the reference intro's GSAP power2 and power3 eases. */
      --ease-out-cubic: cubic-bezier(.33, 1, .68, 1);
      --ease-out-quart: cubic-bezier(.25, 1, .5, 1);
      --ease-in-out-cubic: cubic-bezier(.65, 0, .35, 1);
      --ease-in-out-quart: cubic-bezier(.76, 0, .24, 1);
      --press-in: 100ms;
      --press-out: 160ms;
      font-family: Arial, Helvetica, sans-serif;
      color: var(--white);
      background: var(--background);
      font-synthesis: none;
   }

*
   {
      box-sizing: border-box;
   }

/* One screen-wide backdrop sits outside the hero's portrait clipping. Its critical
   styles live in the head so Safari sees the same background on the first paint. */
html
   {
      min-height: 100%;
      scroll-behavior: auto;
      scroll-padding-top: 0;
      scrollbar-gutter: stable;
      background: var(--background);
   }

/* Skills fits in one screen, but it keeps the scrollbar Projects has, so the
   turn between them changes nothing at the window's edge. */
html:has(.craft-section)
   {
      overflow-y: scroll;
   }

body
   {
      margin: 0;
   }

/* Anchored fine grain with four sparse, prepainted particle layers. */
.section
   {
      position: relative;
      isolation: isolate;
   }

.grain-particles
   {
      position: absolute;
      inset: 0;
      z-index: 1;
      overflow: hidden;
      contain: layout paint;
      pointer-events: none;
   }

.grain-particles.is-painted
   {
      background-image: none;
   }

.grain-drift-shell
   {
      position: absolute;
      inset: 0;
      pointer-events: none;
   }

.grain-surface
   {
      position: absolute;
      left: -40px;
      top: -40px;
      opacity: .85;
      pointer-events: none;
   }

.site-backdrop[data-ambient-motion="playing"] .grain-drift-shell, .site-backdrop[data-ambient-motion="playing"] .grain-drift
   {
      will-change: transform;
   }

a
   {
      color: inherit;
      text-decoration: none;
   }

/* Keep the browser's own tap feedback if the enhancement cannot start. */
.touch-feedback-ready :is(button, a)
   {
      -webkit-tap-highlight-color: transparent;
   }

button:focus-visible, a:focus-visible
   {
      outline: 3px solid currentColor;
      outline-offset: 6px;
   }

::selection
   {
      color: var(--ink);
      background: var(--accent);
   }

.skip-link
   {
      position: fixed;
      top: 12px;
      left: 12px;
      z-index: 20;
      padding: 14px;
      background: var(--accent);
      color: var(--ink);
      transform: translateY(-160%);
   }

.skip-link:focus
   {
      transform: translateY(0);
   }

/* Browser toolbars may move while scrolling; the composition must not resize. */
.hero
   {
      position: relative;
      background: transparent;
      height: 100vh;
      height: 100svh;
      overflow: clip;
      isolation: isolate;
      --content-bottom: calc(var(--footer-bottom) + 54px);
      --masthead-size: min(28vw, 40svh);
   }

/* A static backlight sits with the figure. Pointer motion only stirs the grain. */
.hero-atmosphere
   {
      position: absolute;
      inset: 0;
      z-index: 0;
      overflow: hidden;
      pointer-events: none;
   }

.ambient-light
   {
      position: absolute;
      left: 50%;
      top: 36%;
      width: clamp(280px, 58vw, 820px);
      height: clamp(380px, 72svh, 860px);
      border-radius: 50%;
      transform: translate(-50%, -50%);
      background: radial-gradient(ellipse, rgba(255, 255, 255, .28), rgba(255, 255, 255, .1) 38%, transparent 70%);
   }

@media (prefers-contrast: more)
   {
      .site-backdrop, .hero-atmosphere
         {
            display: none;
         }
   }

.wordmark
   {
      position: absolute;
      top: max(8px, env(safe-area-inset-top));
      left: 1.3%;
      width: 97.4%;
      text-align: center;
      color: transparent;
      -webkit-text-stroke: clamp(1px, .12vw, 2px) var(--accent);
      font-family: 'Unbounded Outline', Arial, sans-serif;
      font-size: var(--masthead-size);
      line-height: .88;
      font-weight: 700;
      letter-spacing: -.055em;
      white-space: nowrap;
      user-select: none;
      transform: scaleY(1.25);
      transform-origin: center top;
   }

.site-header
   {
      position: absolute;
      top: calc(max(8px, env(safe-area-inset-top)) + var(--masthead-size) * 1.06);
      left: max(var(--edge), env(safe-area-inset-left));
      right: max(var(--edge), env(safe-area-inset-right));
      display: flex;
      justify-content: space-between;
      align-items: center;
      z-index: 4;
      gap: 24px;
      flex-wrap: wrap;
   }

.nav-group
   {
      display: flex;
      align-items: center;
      gap: 15px;
   }

.nav-group-right
   {
      margin-left: auto;
   }

.nav-divider-mobile
   {
      display: none;
   }

.nav-link, .small-label
   {
      font-size: .8125rem;
      font-weight: 700;
      line-height: 1.3;
      text-transform: uppercase;
   }

.nav-link
   {
      position: relative;
      display: inline-flex;
      align-items: center;
      gap: .35em;
      min-height: 44px;
      padding: 8px 0;
      font-family: 'Google Sans', Arial, sans-serif;
      font-size: 1.125rem;
      font-weight: 400;
      letter-spacing: .04em;
      white-space: nowrap;
   }

.nav-label, .project-cta-label
   {
      position: relative;
      display: inline-block;
      overflow: hidden;
      vertical-align: top;
   }

.scramble-layer
   {
      position: absolute;
      inset: 0;
      opacity: 0;
      pointer-events: none;
   }

.scramble-char
   {
      position: absolute;
      top: 0;
      text-align: center;
   }

.is-scrambling .scramble-text
   {
      opacity: 0;
   }

.is-scrambling .scramble-layer
   {
      opacity: 1;
   }

.nav-divider
   {
      width: 1px;
      height: 10px;
      background: currentColor;
      opacity: .6;
   }

/* Origin stays outside .intro-pending so a scroll settle can't shift it mid-scale. */
.portrait-stage
   {
      position: absolute;
      inset: 32px 0 0;
      z-index: 2;
      pointer-events: none;
      transform-origin: center bottom;
   }

.portrait
   {
      position: absolute;
      top: 0;
      left: 50%;
      z-index: 1;
      width: auto;
      max-width: none;
      height: 190%;
      transform: translateX(-50%);
      object-fit: contain;
      object-position: center top;
   }

.hero-content
   {
      position: absolute;
      z-index: 5;
      bottom: var(--content-bottom);
      left: 50%;
      width: max-content;
      max-width: calc(100% - 2 * var(--edge));
      transform: translateX(-50%);
      color: var(--white);
      text-align: center;
   }

h1
   {
      font-family: 'Instrument Serif', Georgia, serif;
      text-transform: none;
      font-size: min(clamp(2.5rem, 5.4vw, 6.25rem), 9svh);
      font-weight: 400;
      line-height: 1.02;
      letter-spacing: -.015em;
      margin: 0 0 clamp(12px, 2.5svh, 25px);
      display: grid;
      grid-template-columns: 1fr auto 1fr;
      gap: .28em;
   }

h1 span
   {
      white-space: nowrap;
   }

/* Two roles share one spine, so the pair reads across the top or down either side. */
.hero-heading-column
   {
      display: flex;
      flex-direction: column;
   }

.hero-heading-column:first-of-type
   {
      align-items: flex-end;
   }

.hero-heading-column:last-of-type
   {
      align-items: flex-start;
   }

/* Translucency lives in the colour so the intro can still fade the rule in. */
.hero-heading-rule
   {
      flex: none;
      width: 1px;
      margin: .1em 0 .2em;
      background: rgba(255, 255, 255, .5);
   }

/* The arrow tile expands into a dark panel, as on the Armory reference. */
.button
   {
      position: relative;
      isolation: isolate;
      display: inline-flex;
      align-items: center;
      width: 210px;
      max-width: 100%;
      min-height: 48px;
      padding: 2px;
      overflow: hidden;
      font-family: 'Bounded', Arial, sans-serif;
      font-size: .875rem;
      font-weight: 300;
      letter-spacing: -.01em;
      border-radius: 0;
   }

.button-primary
   {
      color: var(--ink);
      background: var(--white);
   }

.button::before
   {
      content: '';
      position: absolute;
      z-index: -1;
      inset: 2px auto 2px 2px;
      width: 44px;
      border-radius: 2px;
      background: #0d0d0d;
      transition: width 450ms cubic-bezier(.22, 1, .36, 1);
   }

.button-label
   {
      display: block;
      width: 100%;
      padding-left: 46px;
      line-height: 1.5;
      text-align: center;
      white-space: nowrap;
      transition: color 300ms ease, padding 450ms cubic-bezier(.22, 1, .36, 1);
   }

.button-arrow
   {
      position: absolute;
      left: 2px;
      top: 2px;
      bottom: 2px;
      display: grid;
      place-items: center;
      width: 44px;
      color: var(--white);
      transition: left 450ms cubic-bezier(.22, 1, .36, 1);
   }

.button-arrow svg
   {
      width: 15px;
      height: 15px;
   }

.button:focus-visible::before
   {
      width: calc(100% - 4px);
   }

.button:focus-visible .button-label
   {
      color: var(--white);
      padding-left: 0;
      padding-right: 36px;
   }

.button:focus-visible .button-arrow
   {
      left: calc(100% - 46px);
   }

@media (hover: hover) and (pointer: fine)
   {
      :root:not(.touch-input) .button:hover::before
         {
            width: calc(100% - 4px);
         }

      :root:not(.touch-input) .button:hover .button-label
         {
            color: var(--white);
            padding-left: 0;
            padding-right: 36px;
         }

      :root:not(.touch-input) .button:hover .button-arrow
         {
            left: calc(100% - 46px);
         }
   }

/* The primary button with its colours swapped: dark face, white sliding block. */
.button-inverse
   {
      color: var(--white);
      background: #0d0d0d;
      box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .22);
   }

.button-inverse::before
   {
      background: var(--white);
   }

.button-inverse .button-arrow
   {
      color: var(--ink);
   }

.button-inverse:focus-visible .button-label
   {
      color: var(--ink);
   }

@media (hover: hover) and (pointer: fine)
   {
      :root:not(.touch-input) .button-inverse:hover .button-label
         {
            color: var(--ink);
         }
   }

.hero, .about
   {
      --footer-bottom: max(18px, env(safe-area-inset-bottom));
   }

.hero-footer
   {
      position: absolute;
      bottom: var(--footer-bottom);
      left: max(var(--edge), env(safe-area-inset-left));
      right: max(var(--edge), env(safe-area-inset-right));
      z-index: 7;
      display: flex;
      align-items: end;
      justify-content: space-between;
      flex-wrap: wrap;
      gap: 8px 16px;
      font-size: .75rem;
      line-height: 1.45;
   }

.signature
   {
      max-width: 100%;
      font-family: 'Geist Mono', monospace;
      font-size: 11px;
      font-weight: 300;
      letter-spacing: .02em;
      line-height: 1.3;
      text-transform: uppercase;
   }

.signature > span:not(.scramble-text):not(.scramble-layer)
   {
      opacity: .7;
   }

.social-links
   {
      display: flex;
      align-items: center;
      gap: 8px;
      margin-left: auto;
      flex-shrink: 0;
   }

.social-link
   {
      display: inline-flex;
      align-items: center;
      justify-content: center;
      width: 44px;
      height: 44px;
      color: inherit;
      opacity: 1;
      transform: translateY(0);
      overflow: visible;
      transition: opacity var(--press-out) var(--ease-out), transform var(--press-out) var(--ease-out);
   }

.social-icon
   {
      position: relative;
      display: grid;
      width: 20px;
      height: 20px;
      overflow: visible;
      transform: scale(1);
      transition: transform 200ms var(--ease-out);
   }

.social-icon svg
   {
      grid-area: 1 / 1;
      width: 20px;
      height: 20px;
   }

.social-icon-base
   {
      opacity: .7;
      transition: opacity var(--press-out) var(--ease-out);
   }

.social-icon-glow
   {
      pointer-events: none;
      -webkit-mask-image: linear-gradient(to top, #000 0%, #000 42%, transparent 72%);
      mask-image: linear-gradient(to top, #000 0%, #000 42%, transparent 72%);
      -webkit-mask-size: 100% 280%;
      mask-size: 100% 280%;
      -webkit-mask-repeat: no-repeat;
      mask-repeat: no-repeat;
      -webkit-mask-position: 50% 100%;
      mask-position: 50% 100%;
      filter: drop-shadow(0 0 2px rgba(255, 255, 255, .55));
      transition: -webkit-mask-position 200ms var(--ease-out), mask-position 200ms var(--ease-out);
   }

.social-link:focus-visible
   {
      outline: 2px solid currentColor;
      outline-offset: 3px;
   }

.social-link:focus-visible .social-icon-base
   {
      opacity: 1;
   }

.social-link:focus-visible .social-icon-glow
   {
      -webkit-mask-image: none;
      mask-image: none;
      transition: none;
   }

@media (hover: hover) and (pointer: fine)
   {
      :root:not(.touch-input) .social-link:hover .social-icon-glow
         {
            -webkit-mask-position: 50% 0%;
            mask-position: 50% 0%;
         }

      :root:not(.touch-input) .social-link:active
         {
            transition-duration: var(--press-in);
         }

      :root:not(.touch-input) .social-link:active .social-icon
         {
            transition-duration: var(--press-in);
         }
   }

@media (hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference)
   {
      :root:not(.touch-input) .social-link:hover .social-icon
         {
            transform: scale(1.12);
         }

      :root:not(.touch-input) .social-link:active .social-icon
         {
            transform: scale(.94);
         }
   }

.section
   {
      padding: 50px var(--edge) 90px;
   }

.about
   {
      position: relative;
      background: transparent;
      color: var(--white);
   }

.section-topline
   {
      display: flex;
      justify-content: space-between;
      align-items: center;
      gap: 24px;
      padding-bottom: 30px;
      border-bottom: 1px solid var(--border);
   }

.about .section-topline
   {
      color: var(--muted);
      border-color: var(--border);
   }

.about-grid
   {
      display: grid;
      grid-template-columns: 1fr 1fr;
      gap: 8vw;
      padding-top: 80px;
   }

h2
   {
      margin: 0;
      font-size: clamp(4rem, 9vw, 9rem);
      line-height: .92;
      font-weight: 600;
      letter-spacing: -.065em;
   }

.accent-dot
   {
      color: var(--accent);
   }

.about-copy
   {
      max-width: 540px;
   }

.about-copy p
   {
      color: var(--muted);
      font-size: 1rem;
      line-height: 1.6;
      margin: 0 0 20px;
   }

.about-copy .large-copy
   {
      color: var(--white);
      font-size: clamp(1.75rem, 3vw, 3rem);
      line-height: 1.12;
      letter-spacing: -.04em;
      margin-bottom: 34px;
   }

.text-link
   {
      display: inline-flex;
      align-items: center;
      gap: 30px;
      font-size: .9375rem;
      padding: 14px 0 9px;
      border-bottom: 1px solid rgba(255, 255, 255, .5);
   }

@media (hover: hover) and (pointer: fine)
   {
      :root:not(.touch-input) .text-link:hover
         {
            color: var(--accent);
         }
   }

@media (max-width: 1100px)
   {
      .nav-group
         {
            gap: 11px;
         }

      .nav-link
         {
            font-size: clamp(.875rem, 1.7vw, 1.125rem);
         }
   }

@media (max-width: 700px)
   {
      /* Hide the scrollbar using the reference's rules while keeping touch scrolling. */
      html, body
         {
            scrollbar-width: none;
         }

      html::-webkit-scrollbar, body::-webkit-scrollbar
         {
            width: 0;
            height: 0;
         }

      html::-webkit-scrollbar-thumb, body::-webkit-scrollbar-thumb
         {
            background: transparent;
         }

      :root
         {
            --edge: 20px;
         }

      .hero
         {
            --masthead-size: min(28vw, 24svh);
            --content-bottom: calc(var(--footer-bottom) + 88px);
         }

      .site-header
         {
            gap: clamp(3px, .8vw, 6px);
            flex-wrap: nowrap;
            justify-content: center;
         }

      .nav-group
         {
            gap: clamp(3px, .8vw, 6px);
         }

      .nav-group-right
         {
            margin-left: 0;
         }

      .nav-divider-mobile
         {
            display: block;
            flex-shrink: 0;
         }

      .nav-link
         {
            font-size: clamp(.75rem, 2.8vw, 1rem);
         }

      .portrait-stage
         {
            top: calc(var(--masthead-size) * 1.06 + 56px);
         }

      h1
         {
            font-size: min(8.2vw, 8svh, 3.75rem);
         }

      .button
         {
            min-height: 44px;
         }

      .hero-footer
         {
            flex-direction: column;
            flex-wrap: nowrap;
            align-items: center;
            gap: 6px;
            text-align: center;
         }

      .social-links
         {
            order: -1;
            margin-left: 0;
            justify-content: center;
         }

      .signature
         {
            font-size: .625rem;
         }

      .section
         {
            padding-top: 35px;
            padding-bottom: 55px;
         }

      .small-label
         {
            font-size: .75rem;
         }

      .section-topline
         {
            align-items: start;
            gap: 30px;
            padding-bottom: 22px;
         }

      .section-topline > :last-child
         {
            text-align: right;
         }

      .about-grid
         {
            grid-template-columns: 1fr;
            gap: 40px;
            padding-top: 48px;
         }

      h2
         {
            font-size: 5.5rem;
         }

      .about-copy .large-copy
         {
            font-size: 2rem;
         }
   }

/* Give short landscape screens a side-by-side composition. */
@media (max-height: 550px) and (min-aspect-ratio: 6/5)
   {
      .hero, .about
         {
            --footer-bottom: max(10px, env(safe-area-inset-bottom));
         }

      .hero
         {
            --content-bottom: calc(var(--footer-bottom) + 44px);
            --masthead-size: min(28vw, 50svh);
         }

      .wordmark
         {
            top: max(54px, calc(env(safe-area-inset-top) + 44px));
         }

      .site-header
         {
            top: max(8px, env(safe-area-inset-top));
         }

      .portrait-stage
         {
            top: 48px;
            right: 40%;
         }

      h1
         {
            font-size: min(5.4vw, 9svh);
            margin-bottom: 12px;
         }

      .button
         {
            min-height: 44px;
            gap: 20px;
         }

      .hero-footer
         {
            font-size: .6875rem;
         }
   }

@media (max-width: 370px)
   {
      :root
         {
            --edge: 14px;
         }

      .hero-footer
         {
            font-size: .6875rem;
         }
   }

@media (max-width: 700px) and (max-height: 550px) and (min-aspect-ratio: 6/5)
   {
      .hero
         {
            --content-bottom: calc(var(--footer-bottom) + 82px);
         }
   }

@media (prefers-reduced-motion: reduce)
   {
      html
         {
            scroll-behavior: auto;
         }

      *, *::before, *::after
         {
            transition: none !important;
            animation: none !important;
         }

      .social-link, .social-icon-base
         {
            transition: opacity 180ms ease !important;
         }

      .grain-drift-shell
         {
            transform: none !important;
         }
   }

/* The SVG owns the cutout and shading masks. The PNG is already grayscale. */
/* Separate scroll layers leave the loading animation and portrait sizing intact.
   The next surface travels over the pinned hero; its layout can change freely. */
.wordmark-stage, .portrait-scroll
   {
      position: absolute;
      inset: 0;
      pointer-events: none;
   }

.wordmark-stage
   {
      z-index: 0;
   }

.portrait-scroll
   {
      z-index: 2;
   }

/* The hero outranks About for the length of the fold: its name and its links
   are still travelling across the surface that is rising underneath them. */
.section-scroll-ready .hero
   {
      position: sticky;
      top: 0;
      z-index: 3;
   }

/* Both surfaces share a native sticky viewport. The negative margin overlaps
   About with Home; the matching bottom space preserves the scroll distance and
   lets long About content continue normally after the fold. */
.section-scroll-ready main::after
   {
      content: '';
      display: block;
      height: var(--fold-distance, 100svh);
   }

.section-scroll-ready .about
   {
      position: sticky;
      top: 0;
      margin-top: calc(-1 * var(--fold-distance, 100svh));
   }

.section-scroll-ready .wordmark-stage, .section-scroll-ready .hero .nav-link, .section-scroll-ready .hero-heading-line
   {
      transform-origin: 0 0;
   }

.fold-measuring .wordmark
   {
      transform: scaleY(1.25) !important;
   }

.fold-measuring .intro-letter, .fold-measuring .nav-label > .scramble-text
   {
      transform: none !important;
   }

/* Retain just the moving surfaces across both landings. Reallocating the large
   name and portrait on the first upward frame makes the return hitch. */
.section-scroll-ready .wordmark-stage,
.section-scroll-ready .portrait-scroll,
.section-scroll-ready .hero .nav-link,
.section-scroll-ready .hero-heading-line,
.section-scroll-ready .about,
.section-scroll-ready .about-topbar,
.section-scroll-ready .hero-footer .signature
   {
      will-change: transform, opacity;
   }

/* aria-current flips halfway through the fold; the travelling link's own
   highlight is drawn by the fold instead, so no other row flashes one. */
.section-folding .section-nav-link[aria-current]
   {
      --pill: 0;
   }

/* About is open to the shared star field. Panels keep a stable, unfiltered
   rendering mode so reversing the fold does not trigger a blur/repaint cliff. */
.section-scroll-ready .about .glass-panel
   {
      backdrop-filter: none;
      -webkit-backdrop-filter: none;
   }

/* One gesture completes the fold; no extra scroll runway is needed. Keep the
   zero-height marker for section measurement and the standalone fallback. */
.hero-runway
   {
      height: 0;
   }

.about
   {
      min-height: 100vh;
      min-height: 100svh;
      z-index: 2;
      background: transparent;
   }

.about:focus
   {
      outline: none;
   }

@media (prefers-reduced-motion: reduce)
   {
      /* Nothing is pinned here, so there is no fold and no runway to fold across. */
      .section-scroll-ready .hero
         {
            position: relative;
         }

      .section-scroll-ready .hero-runway
         {
            height: 0;
         }

      .section-scroll-ready main::after
         {
            display: none;
         }

      .section-scroll-ready .about
         {
            position: relative;
            margin-top: 0;
         }

      .section-scroll-ready .wordmark-stage,
      .section-scroll-ready .portrait-scroll,
      .section-scroll-ready .hero .nav-link,
      .section-scroll-ready .hero-heading-line,
      .section-scroll-ready .about,
      .section-scroll-ready .about-topbar
         {
            will-change: auto;
         }
   }

/* Name-first entrance. The outlined letters are the wordmark; they are not
   swapped for a second mark at the end of the climb. */
.hero-heading-line
   {
      display: inline-block;
   }

.intro-pending .wordmark,
.intro-pending .portrait-stage,
.intro-pending .hero-heading-line,
.intro-pending .hero-heading-rule,
.intro-pending .site-header,
.intro-pending .hero-footer .signature
   {
      opacity: 0;
   }

.intro-pending .site-header
   {
      pointer-events: none;
   }

.intro-pending .wordmark.is-entering, .intro-pending .site-header.is-entering
   {
      opacity: 1;
   }

.intro-pending .hero .nav-label > .scramble-text
   {
      transform: translateY(110%);
   }

.wordmark.intro-wordmark
   {
      /* Room above and below the short line box keeps the outline intact. */
      clip-path: inset(-.2em -.05em -.15em);
      pointer-events: none;
      will-change: transform;
   }

.intro-letter
   {
      display: inline-block;
   }

.intro-pending .intro-letter
   {
      will-change: transform;
   }

.intro-pending .portrait-stage
   {
      will-change: transform, opacity, filter;
   }

@media (prefers-reduced-motion: reduce)
   {
      .intro-pending .wordmark,
      .intro-pending .portrait-stage,
      .intro-pending .hero-heading-line,
      .intro-pending .hero-heading-rule,
      .intro-pending .site-header,
      .intro-pending .hero-footer .signature
         {
            opacity: 1;
            pointer-events: auto;
         }

      .intro-pending .hero .nav-label > .scramble-text
         {
            transform: none;
         }
   }

/* Stagger the icons themselves, including when touch skips the main intro.
   Keep their full hit areas active; only their presentation is delayed. */
.socials-pending .social-link
   {
      opacity: 0;
      transform: translateY(8px);
   }

.socials-revealing .social-link:nth-child(2)
   {
      transition-delay: 40ms;
   }

.socials-revealing .social-link:nth-child(3)
   {
      transition-delay: 80ms;
   }

.socials-revealing .social-link:nth-child(4)
   {
      transition-delay: 120ms;
   }

.socials-instant .social-link, .social-link:focus-visible, .social-link:focus-visible .social-icon, .social-link:focus-visible .social-icon-glow
   {
      transition: none;
   }

:root .social-link:is(:focus-visible, .is-touch-pressed)
   {
      transition-delay: 0ms;
   }

@media (hover: hover) and (pointer: fine)
   {
      :root:not(.touch-input) .social-link:hover
         {
            transition-delay: 0ms;
         }
   }

@media (prefers-reduced-motion: reduce)
   {
      .social-link, .socials-pending .social-link, .social-icon
         {
            transform: none;
         }

      .social-link, .socials-pending .social-link, .social-icon-base
         {
            transition: opacity var(--press-out) var(--ease-out) !important;
         }

      .social-icon-glow
         {
            filter: none;
            -webkit-mask-image: none;
            mask-image: none;
            opacity: 0;
            transition: opacity var(--press-out) var(--ease-out) !important;
         }

      :root:not(.touch-input) .social-link:hover .social-icon-glow, .social-link:focus-visible .social-icon-glow
         {
            opacity: 1;
         }

      .socials-instant .social-link, .social-link:focus-visible
         {
            transition: none !important;
         }
   }

/* Scramble overlays stay anchored to each text label. */
.hero-heading-line, .signature, .button-label
   {
      position: relative;
      display: inline-block;
   }

a[href], button, [role="button"], [role="link"]
   {
      cursor: pointer;
   }

span.nav-link
   {
      cursor: default;
   }

/* Full-screen mobile navigation: a native dialog keeps the page behind it inert. */
.navigation-panel
   {
      display: contents;
   }

.menu-toggle, .mobile-menu
   {
      display: none;
   }

html.menu-open, html.menu-open body
   {
      overflow: hidden;
      overscroll-behavior: none;
   }

.menu-open .hero .menu-toggle
   {
      visibility: hidden;
   }

@media (max-width: 700px)
   {
      .site-header
         {
            z-index: 10;
         }

      .menu-ready .site-header
         {
            top: max(8px, env(safe-area-inset-top));
            justify-content: flex-start;
         }

      .menu-ready .wordmark
         {
            top: calc(max(8px, env(safe-area-inset-top)) + 56px);
         }

      .menu-ready .site-header > .navigation-panel
         {
            display: none;
         }

      .menu-ready .menu-toggle
         {
            display: grid;
            place-items: center;
            width: 48px;
            height: 48px;
            flex-shrink: 0;
            padding: 0;
            border: 0;
            background: transparent;
            color: var(--white);
            touch-action: manipulation;
            transition: opacity 160ms ease;
         }

      :root:not(.touch-feedback-ready) .menu-toggle:active
         {
            opacity: .55;
         }

      .menu-icon
         {
            position: relative;
            display: block;
            width: 26px;
            height: 20px;
         }

      .menu-icon > span
         {
            position: absolute;
            left: 0;
            top: 9px;
            width: 26px;
            height: 2px;
            background: currentColor;
            transform: translateY(-7px) rotate(0deg);
            transform-origin: center;
            transition: transform 250ms var(--ease-in-out), opacity 160ms var(--ease-out);
         }

      .menu-icon > span:nth-child(2)
         {
            transform: translateY(0) rotate(0deg);
         }

      .menu-icon > span:nth-child(3)
         {
            transform: translateY(7px) rotate(0deg);
         }

      .mobile-menu.is-open .menu-icon > span:first-child
         {
            transform: translateY(0) rotate(45deg);
         }

      .mobile-menu.is-open .menu-icon > span:nth-child(2)
         {
            opacity: 0;
         }

      .mobile-menu.is-open .menu-icon > span:nth-child(3)
         {
            transform: translateY(0) rotate(-45deg);
         }

      .mobile-menu
         {
            position: fixed;
            inset: 0;
            width: 100%;
            height: 100%;
            height: 100dvh;
            max-width: none;
            max-height: none;
            margin: 0;
            padding: 0;
            border: 0;
            color: var(--white);
            background: transparent;
            overflow: hidden;
            overscroll-behavior: contain;
            /* Clip the black surface and links together, centered on the live control. */
            clip-path: circle(24px at var(--menu-reveal-x, 44px) var(--menu-reveal-y, 32px));
            transition: clip-path 280ms var(--ease-out);
         }

      .mobile-menu[open]
         {
            display: block;
         }

      .mobile-menu.is-open
         {
            clip-path: circle(var(--menu-reveal-radius, 150vmax) at var(--menu-reveal-x, 44px) var(--menu-reveal-y, 32px));
         }

      .mobile-menu::backdrop
         {
            background: transparent;
         }

      .mobile-menu-surface
         {
            position: absolute;
            inset: 0;
            background: #000;
            pointer-events: none;
         }

      .mobile-menu .site-header
         {
            bottom: max(24px, env(safe-area-inset-bottom));
            display: flex;
            flex-direction: column;
            align-items: stretch;
            flex-wrap: nowrap;
            gap: 0;
            opacity: 1;
         }

      .mobile-menu .menu-toggle
         {
            align-self: flex-start;
         }

      .menu-ready .mobile-menu .navigation-panel
         {
            display: grid;
            grid-template-rows: repeat(6, minmax(3.5rem, 1fr));
            flex-shrink: 1;
            min-height: 0;
            width: 100%;
            max-width: 38rem;
            height: min(28rem, 62dvh);
            margin: auto 0;
            padding: 16px 12px;
            overflow-y: auto;
            overscroll-behavior: contain;
         }

      .mobile-menu .nav-group
         {
            display: contents;
         }

      .mobile-menu .nav-divider
         {
            display: none;
         }

      .mobile-menu .nav-link
         {
            min-height: 3.5rem;
            padding: .5rem 0;
            font-family: 'Google Sans', Arial, sans-serif;
            font-size: clamp(1.4rem, 5.8vw, 2.5rem);
            font-weight: 400;
            line-height: 1.2;
            letter-spacing: .04em;
            white-space: normal;
            opacity: 0;
            transform: translateY(-24px);
            transition: opacity 150ms var(--ease-out), transform 150ms var(--ease-out);
         }

      .mobile-menu.is-open .nav-link
         {
            opacity: 1;
            transform: translateY(0);
         }

      .mobile-menu.is-open .nav-group:first-child .nav-link:nth-of-type(2)
         {
            transition-delay: 30ms;
         }

      .mobile-menu.is-open .nav-group:first-child .nav-link:nth-of-type(3)
         {
            transition-delay: 60ms;
         }

      .mobile-menu.is-open .nav-group:first-child .nav-link:nth-of-type(4)
         {
            transition-delay: 90ms;
         }

      .mobile-menu.is-open .nav-group-right .nav-link:nth-of-type(1)
         {
            transition-delay: 120ms;
         }

      .mobile-menu.is-open .nav-group-right .nav-link:nth-of-type(2)
         {
            transition-delay: 150ms;
         }

      .mobile-menu.is-open .nav-group-right .nav-link:nth-of-type(3)
         {
            transition-delay: 180ms;
         }

      .menu-keyboard .mobile-menu, .menu-keyboard .menu-icon > span, .menu-keyboard .mobile-menu .nav-link
         {
            transition: none !important;
         }
   }

@media (prefers-reduced-motion: reduce)
   {
      .mobile-menu
         {
            clip-path: none !important;
            opacity: 0;
            transition: opacity 180ms ease !important;
         }

      .mobile-menu.is-open
         {
            opacity: 1;
         }

      .mobile-menu .nav-link
         {
            transform: none !important;
            transition: opacity 180ms ease !important;
         }

      .menu-keyboard .mobile-menu, .menu-keyboard .mobile-menu .nav-link
         {
            transition: none !important;
         }
   }

/* Contact feedback: quiet compression, then a quick, interruptible release.
   Menu labels move independently of their row's existing entrance animation. */
:is(.button, .menu-toggle, .social-link, .text-link, .nav-link)
   {
      touch-action: manipulation;
   }

.button, .text-link
   {
      transition: transform var(--press-out) var(--ease-out), opacity var(--press-out) var(--ease-out);
   }

.button.is-touch-pressed, .text-link.is-touch-pressed
   {
      transform: scale(.97);
      opacity: .82;
      transition-duration: var(--press-in);
   }

.text-link
   {
      transform-origin: left center;
   }

.menu-toggle, .social-link
   {
      position: relative;
      isolation: isolate;
   }

.nav-link::before
   {
      content: '';
      position: absolute;
      inset: 0;
      z-index: -1;
      border-radius: inherit;
      background: rgba(255, 255, 255, .1);
      opacity: 0;
      pointer-events: none;
      transition: opacity var(--press-out) var(--ease-out);
   }

.nav-link
   {
      isolation: isolate;
   }

.nav-link::before
   {
      inset: 0 -8px;
      border-radius: 4px;
   }

.menu-icon, .nav-label
   {
      transition: transform var(--press-out) var(--ease-out), opacity var(--press-out) var(--ease-out);
   }

.nav-link.is-touch-pressed::before
   {
      opacity: 1;
      transition-duration: var(--press-in);
   }

.menu-toggle.is-touch-pressed .menu-icon, .social-link.is-touch-pressed .social-icon
   {
      transform: scale(.94);
      transition-duration: var(--press-in);
   }

.social-link.is-touch-pressed .social-icon-base
   {
      opacity: 1;
   }

.nav-link.is-touch-pressed .nav-label
   {
      transform: scale(.98);
      opacity: .72;
      transform-origin: left center;
      transition-duration: var(--press-in);
   }

/* Keyboard navigation is immediate, including the existing desktop CTA treatment. */
:is(.button, .text-link):focus-visible, .button:focus-visible::before, .button:focus-visible .button-label, .button:focus-visible .button-arrow
   {
      transition: none;
   }

@media (prefers-reduced-motion: reduce)
   {
      .button, .text-link, .menu-icon, .social-icon, .nav-label, .nav-link::before, .social-icon-base
         {
            transition: opacity var(--press-out) var(--ease-out) !important;
         }

      .button.is-touch-pressed,
      .text-link.is-touch-pressed,
      .menu-toggle.is-touch-pressed .menu-icon,
      .social-link.is-touch-pressed .social-icon,
      .nav-link.is-touch-pressed .nav-label
         {
            transform: none;
         }

      .menu-toggle.is-touch-pressed .menu-icon, .social-link.is-touch-pressed .social-icon-base
         {
            opacity: .72;
         }
   }

@media (prefers-contrast: more)
   {
      .nav-link::before
         {
            background: transparent;
            border: 2px solid currentColor;
         }
   }

/* One bar across the top of About and Projects: JOSH on the left, the site's
   links on the right. The content column runs the full width beneath it.
   Nothing sits above the bar, so it is flush with the top on every screen
   and never shifts up when it starts to stick. */
.about
   {
      --page-gutter: clamp(1rem, 2.8vw, 3.5rem);
      display: flex;
      flex-direction: column;
      gap: clamp(1.5rem, 4svh, 3rem);
      padding: 0 var(--page-gutter) 1rem;
   }

/* Home's header, settled at the top: no panel, the name and the two link
   groups straight on the grain. Content can scroll under it and backdrop blur
   is off during the fold, so it sits on a band of the page's own black that
   fades out below instead of ending on a rule. */
.about-topbar
   {
      position: sticky;
      top: 0;
      z-index: 3;
      display: flex;
      align-items: center;
      justify-content: space-between;
      gap: 1.5rem;
      margin: 0 calc(-1 * var(--page-gutter));
      padding: 1rem var(--page-gutter) .75rem;
      background: rgba(6, 6, 6, .82);
   }

.about-topbar::after
   {
      content: '';
      position: absolute;
      top: 100%;
      left: 0;
      right: 0;
      height: 1.5rem;
      background: linear-gradient(rgba(6, 6, 6, .82), rgba(6, 6, 6, 0));
      pointer-events: none;
   }

/* Same outlined JOSH as the masthead, only smaller. Fill, stroke and stretch
   have to match .wordmark or the fold would land on a different mark. Padding
   is the hit area; it is not a chip. Large enough to read as the masthead
   it came from, and pulled out by its padding so the ink meets the gutter. */
.topbar-wordmark
   {
      flex: none;
      display: inline-flex;
      align-items: start;
      margin-left: -.45rem;
      padding: .34rem .45rem;
      color: transparent;
      -webkit-text-stroke: .055em var(--accent);
      font-family: 'Unbounded Outline', Arial, sans-serif;
      font-weight: 700;
      font-size: clamp(1.5rem, 2.2vw, 2.125rem);
      letter-spacing: -.055em;
   }

/* Kept under the font's own glyph box, as .wordmark's .88 is, so that the box
   the fold measures is the glyphs at both ends rather than the line-height at
   one end and the glyphs at the other. */
.topbar-wordmark span
   {
      display: block;
      line-height: .88;
      transform: scaleY(1.25);
      transform-origin: center;
   }

/* The row hugs the right edge and scrolls sideways, fading at its ends, when
   the bar is narrower than its links. */
.section-nav
   {
      display: flex;
      align-items: center;
      gap: .15rem;
      min-width: 0;
      margin-right: -.6rem;
      overflow-x: auto;
      scrollbar-width: none;
   }

/* Home's split, kept: the site's sections, then the ways to talk to Josh. */
.section-nav-divider
   {
      flex: none;
      width: 1px;
      height: .75rem;
      margin-inline: .6rem;
      background: currentColor;
      opacity: .45;
   }

.section-nav::-webkit-scrollbar
   {
      display: none;
   }

/* Each label here is the landing point of the matching header link, so its
   typeface, weight, tracking and line-height all have to stay in step with
   .nav-link above. The fold measures line boxes and scales them uniformly, so
   line-height decides where a word lands vertically just as much as the face
   decides its shape. Size is the one thing free to differ. */
.section-nav-link
   {
      flex-shrink: 0;
      display: flex;
      align-items: center;
      gap: .5rem;
      min-height: 2.75rem;
      padding: .4rem .6rem;
      border-radius: .25rem;
      font-family: 'Google Sans', Arial, sans-serif;
      font-size: .9375rem;
      line-height: 1.3;
      font-weight: 400;
      letter-spacing: .04em;
      text-transform: uppercase;
      white-space: nowrap;
      transition: background-color var(--press-out) var(--ease-out), color var(--press-out) var(--ease-out);
   }

/* Matches .nav-label's box model, not just its face: an inline-block takes its
   height from line-height while a bare inline takes it from the font's own
   metrics, and the fold cannot scale one into the other. */
.section-nav-link .nav-label
   {
      display: inline-block;
      vertical-align: top;
   }

/* Home's links are words alone, so these are too. The glyphs stay in the
   markup for the fold, which fades them, but they are not drawn. */
.section-nav-link svg
   {
      display: none;
      width: 1.125rem;
      height: 1.125rem;
      flex-shrink: 0;
      fill: currentColor;
      overflow: visible;
   }

.section-nav-link svg .icon-tone
   {
      opacity: .4;
   }

/* The current page is underlined. The rule is its own layer, so the fold can
   draw it in under the landing label: --pill is how much of it is drawn, left
   to right. It spans the label, not the padded control, and sits just below
   the capitals (which reach .35em either side of the centred line box). */
.section-nav-link
   {
      --nav-pad: .6rem;
      position: relative;
      isolation: isolate;
      padding-inline: var(--nav-pad);
   }

.section-nav-link::before
   {
      content: '';
      position: absolute;
      left: var(--nav-pad);
      right: var(--nav-pad);
      top: calc(50% + .35em + .3rem);
      height: 2px;
      background: var(--white);
      transform: scaleX(var(--pill, 0));
      transform-origin: left center;
      pointer-events: none;
      transition: opacity var(--press-out) var(--ease-out);
   }

.section-nav-link[aria-current]
   {
      --pill: 1;
   }

/* Scrolling between About and Projects, the underline leaves both links and
   travels between them as one rule. It is 1px wide; scaleX sets its length.
   About's link can hold the fold's inline --pill, so Projects hides it. */
.section-nav
   {
      position: relative;
   }

.section-nav.is-gliding .section-nav-link::before, .section-nav.is-projects .section-nav-link[href$="#about"]::before
   {
      visibility: hidden;
   }

.section-nav-glide
   {
      position: absolute;
      left: 0;
      top: 0;
      width: 1px;
      height: 2px;
      background: var(--white);
      transform-origin: left top;
      pointer-events: none;
   }

/* Pinned to the same insets as .hero-footer, so the signature never moves
   between pages. The section reserves the room it takes. */
.about
   {
      padding-bottom: calc(var(--footer-bottom) + 4rem);
   }

.about-footer
   {
      position: absolute;
      bottom: var(--footer-bottom);
      left: max(var(--edge), env(safe-area-inset-left));
      right: max(var(--edge), env(safe-area-inset-right));
      display: flex;
      align-items: end;
      justify-content: space-between;
      gap: 8px 16px;
   }

/* Home's two roles, as icons. The heading words morph into them on the way
   down: --draw (0-1) is how much of each stroke the fold has drawn so far. */
.footer-roles
   {
      margin: 0;
      display: flex;
      align-items: center;
      gap: .9rem;
   }

.footer-role
   {
      display: grid;
      place-items: center;
      width: 1.625rem;
      height: 1.625rem;
   }

.footer-role svg
   {
      width: 100%;
      height: 100%;
      fill: none;
      stroke: currentColor;
      stroke-width: 1.5;
      stroke-linecap: round;
      stroke-linejoin: round;
      overflow: visible;
   }

.footer-role path
   {
      stroke-dasharray: 1;
      stroke-dashoffset: calc(1 - var(--draw, 1));
   }

.footer-roles-rule
   {
      width: 1px;
      height: 1.125rem;
      background: rgba(255, 255, 255, .35);
   }

.about-main
   {
      flex: 1 0 auto;
      min-width: 0;
   }

/* Projects is its own document on the About layout; only the content column
   differs. It is a pinned horizontal track: the runway's height is the travel,
   the frame sticks under the top bar, and scroll slides the cards sideways.
   Script sets --fill (0-1) on each card from its distance to the column centre
   and marks the nearest card current; everything else here is static. */
.projects-main:focus
   {
      outline: none;
   }

.projects-main
   {
      --projects-gutter: var(--page-gutter);
      --projects-lead: var(--page-gutter);
      --card-w: min(58vw, 860px, 100%);
      --card-gap: clamp(1rem, 3vw, 3.5rem);
   }

.projects-frame
   {
      display: flex;
      flex-direction: column;
      justify-content: center;
      gap: clamp(1rem, 3.2svh, 2.5rem);
      min-height: calc(100svh - 2rem);
      padding-block: clamp(.25rem, 2svh, 2rem);
   }

/* --projects-top is where the sticky bar ends, set by script. */
.projects-pinned .projects-frame
   {
      position: sticky;
      top: var(--projects-top, 1rem);
      height: calc(100svh - var(--projects-top, 1rem) - var(--footer-bottom) - 1.5rem);
      min-height: 0;
   }

.projects-eyebrow
   {
      display: flex;
      align-items: center;
      gap: 1rem;
      margin: 0 0 clamp(1rem, 2.4svh, 1.75rem);
      color: rgba(255, 255, 255, .5);
      font-family: 'Geist Mono', monospace;
      font-size: 10px;
      font-weight: 300;
      letter-spacing: .3em;
      line-height: 1.5;
      text-transform: uppercase;
   }

.projects-rule
   {
      position: relative;
      flex: 1;
      max-width: 280px;
      height: 1px;
      background: rgba(255, 255, 255, .1);
   }

.projects-rule::after
   {
      content: '';
      position: absolute;
      inset: 0;
      background: rgba(255, 255, 255, .4);
      transform-origin: left;
   }

.projects-pinned:not(.projects-entered) .projects-rule::after
   {
      transform: scaleX(0);
   }

.projects-entered .projects-rule::after
   {
      transition: transform 1.2s var(--ease-out) .15s;
   }

/* The global h1 is the hero's two-column heading; this is a plain two-weight stack. */
.projects-title
   {
      display: block;
      margin: 0;
      text-align: left;
      font-family: 'Bounded', Arial, sans-serif;
      font-size: min(clamp(2.4rem, 5.2vw, 5.6rem), 11svh);
      font-weight: 200;
      line-height: .92;
      letter-spacing: -.06em;
      text-transform: uppercase;
   }

.projects-title-line
   {
      display: block;
   }

.projects-title-strong
   {
      font-weight: 800;
   }

/* Entrance: the eyebrow fades up as About's do, the lines rise a fraction of
   an em, then the cards settle in. Turning back from Skills flips both
   offsets, as Education's do: the lines drop in from above and the cards
   come from the left, the way the track runs back. */
.projects-main
   {
      --projects-from-y: .35em;
      --projects-from-x: 2.5rem;
   }

html.turn-back .projects-main
   {
      --projects-from-y: -.35em;
      --projects-from-x: -2.5rem;
   }

.projects-pinned:not(.projects-entered) .projects-eyebrow
   {
      opacity: 0;
   }

.projects-entered .projects-eyebrow
   {
      transition: opacity .4s var(--ease-out);
   }

.projects-pinned:not(.projects-entered) :is(.projects-title-line, .project-card)
   {
      opacity: 0;
      transform: translateY(var(--projects-from-y));
   }

.projects-pinned:not(.projects-entered) .project-card
   {
      transform: translateX(var(--projects-from-x));
   }

.projects-entered :is(.projects-title-line, .project-card)
   {
      transition: opacity .9s var(--ease-out), transform .9s var(--ease-out);
   }

/* The rail waits for the entrance too, so it never shows a stale stop, or
   shows through the top bar above Skills while Projects is away. It returns
   the way it left: up the half rem it dropped. */
.projects-pinned:not(.projects-entered) .projects-rail
   {
      opacity: 0;
      translate: 0 .5rem;
   }

.projects-entered .projects-rail
   {
      transition: opacity .6s var(--ease-out) .36s, translate .6s var(--ease-out) .36s;
   }

.projects-entered .projects-title-strong
   {
      transition-delay: .08s;
   }

.projects-entered .project-card:nth-child(1)
   {
      transition-delay: .2s;
   }

.projects-entered .project-card:nth-child(2)
   {
      transition-delay: .28s;
   }

.projects-entered .project-card:nth-child(n + 3)
   {
      transition-delay: .36s;
   }

/* The column's right gutter is given back so cards run off the screen edge;
   the left edge bleeds and fades the same way instead of cutting. Clipping would
   let flex shrink this row below the cards and cut their bottom border, so
   it keeps its full height. */
.projects-viewport
   {
      flex: none;
      margin: 0 calc(-1 * var(--projects-gutter)) 0 calc(-1 * var(--projects-lead));
      overflow-x: auto;
      scrollbar-width: none;
      overscroll-behavior-x: contain;
      -webkit-mask-image: linear-gradient(to right, transparent, #000 var(--projects-lead));
      mask-image: linear-gradient(to right, transparent, #000 var(--projects-lead));
   }

.projects-viewport::-webkit-scrollbar
   {
      display: none;
   }

.projects-pinned .projects-viewport
   {
      overflow: clip;
   }

/* One snap point per card; scroll-snap-stop keeps a hard flick from skipping. */
html.projects-snapping
   {
      scroll-snap-type: y mandatory;
   }

.projects-pinned .projects-runway
   {
      position: relative;
   }

.projects-snap
   {
      position: absolute;
      left: 0;
      width: 1px;
      height: 1px;
      pointer-events: none;
      scroll-snap-align: start;
      scroll-snap-stop: always;
   }

.projects-track
   {
      position: relative;
      display: flex;
      gap: var(--card-gap);
      width: max-content;
      margin: 0;
      padding: 0 var(--projects-gutter) 0 var(--projects-lead);
      list-style: none;
   }

.projects-pinned .projects-track
   {
      will-change: transform;
   }

.project-card
   {
      --fill: 0;
      flex: none;
      width: var(--card-w);
   }

.project-link
   {
      position: relative;
      display: flex;
      flex-direction: column;
      justify-content: space-between;
      height: clamp(280px, 44svh, 520px);
      padding: clamp(1.5rem, 2.2vw, 2.5rem);
      overflow: hidden;
      background: rgba(255, 255, 255, .02);
      border: 1px solid rgba(255, 255, 255, .1);
      transition: border-color .7s var(--ease-out);
   }

.project-card.is-current .project-link
   {
      border-color: rgba(255, 255, 255, .3);
   }

.project-top
   {
      display: flex;
      align-items: flex-start;
      justify-content: space-between;
      gap: 1.5rem;
   }

.project-index, .project-tags, .project-cta, .projects-rail
   {
      font-family: 'Geist Mono', monospace;
      font-size: 10px;
      font-weight: 300;
      text-transform: uppercase;
   }

.project-index
   {
      color: rgba(255, 255, 255, .5);
      letter-spacing: .24em;
   }

.project-tags
   {
      font-size: 11px;
      display: flex;
      flex-wrap: wrap;
      justify-content: flex-end;
      gap: .25rem .5rem;
      max-width: 70%;
      margin: 0;
      padding: 0;
      list-style: none;
      color: rgba(255, 255, 255, calc(.3 + var(--fill) * .55));
      letter-spacing: .14em;
   }

.project-tags li
   {
      display: flex;
      align-items: center;
      gap: .5rem;
   }

.project-tags li + li::before
   {
      content: '';
      width: 3px;
      height: 3px;
      border-radius: 50%;
      background: rgba(255, 255, 255, calc(.2 + var(--fill) * .4));
   }

/* Stack logos wake into their brand colour as the card takes the centre.
   Black marks (Next.js, Express) stay white. */
.project-tag-icon
   {
      --brand: #fff;
      flex: none;
      width: 1.1em;
      height: 1.1em;
      margin-right: -.2rem;
      fill: color-mix(in srgb, var(--brand) calc(var(--fill) * 100%), currentColor);
   }

.project-tag-icon--python
   {
      --brand: #3776ab;
   }

.project-tag-icon--scikitlearn
   {
      --brand: #f7931e;
   }

.project-tag-icon--raspberrypi
   {
      --brand: #c51a4a;
   }

.project-tag-icon--swift
   {
      --brand: #f05138;
   }

.project-tag-icon--spotify
   {
      --brand: #1ed760;
   }

.project-tag-icon--electron
   {
      --brand: #9feaf9;
   }

.project-tag-icon--fastapi
   {
      --brand: #05998b;
   }

.project-tag-icon--nodedotjs
   {
      --brand: #5fa04e;
   }

.project-tag-icon--postgresql
   {
      --brand: #699eca;
   }

.project-tag-icon--react
   {
      --brand: #61dafb;
   }

.project-tag-icon--springboot
   {
      --brand: #6db33f;
   }

.project-tag-icon--html5
   {
      --brand: #e34f26;
   }

.project-tag-icon--css
   {
      --brand: #2f9be8;
   }

.project-tag-icon--javascript
   {
      --brand: #f7df1e;
   }

.project-tag-icon--sqlite
   {
      --brand: #44a8e0;
   }

.project-tag-icon--mongodb
   {
      --brand: #47a248;
   }

.project-tag-icon--tailwindcss
   {
      --brand: #06b6d4;
   }

.project-tag-icon--pytorch
   {
      --brand: #ee4c2c;
   }

.project-tag-icon--togetherai
   {
      --brand: #ef2cc1;
      --tai-lilac: color-mix(in srgb, #caaef5 calc(var(--fill) * 100%), currentColor);
      --tai-pink: color-mix(in srgb, #ef2cc1 calc(var(--fill) * 100%), currentColor);
      --tai-orange: color-mix(in srgb, #fc4c02 calc(var(--fill) * 100%), currentColor);
      fill: none;
   }

/* The name is drawn twice: a hairline outline, and a solid copy revealed
   through a soft 100deg edge as --fill runs 0 to 1. The numeral does the same. */
.project-name
   {
      position: relative;
      margin: 0;
      font-family: 'Google Sans', Arial, sans-serif;
      font-size: clamp(1.9rem, 3.2vw, 3.4rem);
      font-weight: 300;
      line-height: 1.05;
      letter-spacing: -.04em;
   }

.project-name-outline
   {
      display: block;
      color: transparent;
      -webkit-text-stroke: 1px rgba(255, 255, 255, .3);
   }

.project-name-fill, .project-numeral-fill
   {
      position: absolute;
      inset: 0;
      display: block;
      --edge-at: calc(var(--fill) * 124% - 12%);
      -webkit-mask-image: linear-gradient(100deg, #000 var(--edge-at), transparent calc(var(--edge-at) + 10%));
      mask-image: linear-gradient(100deg, #000 var(--edge-at), transparent calc(var(--edge-at) + 10%));
   }

.project-name-fill
   {
      color: var(--white);
   }

.project-desc
   {
      max-width: 38ch;
      margin: 1rem 0 0;
      color: rgba(255, 255, 255, .55);
      font-family: 'Google Sans', Arial, Helvetica, sans-serif;
      font-size: clamp(.875rem, .95vw, .9375rem);
      font-weight: 400;
      line-height: 1.65;
      transition: color .7s var(--ease-out);
   }

.project-card.is-current .project-desc
   {
      color: rgba(255, 255, 255, .8);
   }

.project-cta
   {
      position: relative;
      z-index: 1;
      display: inline-flex;
      align-items: center;
      gap: .5rem;
      align-self: flex-start;
      color: rgba(255, 255, 255, .8);
      font-size: 12px;
      font-weight: 400;
      letter-spacing: .16em;
      transition: color .5s var(--ease-out);
   }

/* Kisaka-style flying arrow: one copy exits top-right, the ghost enters from bottom-left. */
.project-cta-arrow-box
   {
      position: relative;
      flex: none;
      width: .875rem;
      height: .875rem;
      overflow: hidden;
   }

.project-cta-arrow
   {
      position: absolute;
      inset: 0;
      width: 100%;
      height: 100%;
      fill: none;
      stroke: currentColor;
      stroke-width: 2;
      stroke-linecap: round;
      stroke-linejoin: round;
      transition: transform 420ms cubic-bezier(.2, .7, .2, 1);
   }

.project-cta-arrow--ghost
   {
      transform: translate(-115%, 115%);
   }

.project-numeral
   {
      position: absolute;
      right: clamp(1.5rem, 2.2vw, 2.5rem);
      bottom: clamp(1rem, 1.6vw, 1.5rem);
      font-family: 'Bounded', Arial, sans-serif;
      font-size: clamp(4.5rem, 9vw, 8.5rem);
      font-weight: 200;
      line-height: 1;
      letter-spacing: -.06em;
      pointer-events: none;
      user-select: none;
   }

.project-numeral-outline
   {
      display: block;
      color: transparent;
      -webkit-text-stroke: 1px rgba(255, 255, 255, .12);
   }

.project-numeral-fill
   {
      color: rgba(255, 255, 255, .14);
   }

.project-logo-sprite
   {
      position: absolute;
      width: 0;
      height: 0;
      overflow: hidden;
   }

/* A logo stands in for the numeral: stroked like the outline digits, filled
   by the same --fill sweep. Width follows each mark's own proportions. */
.project-numeral--logo svg
   {
      display: block;
      height: .8em;
      width: auto;
      overflow: visible;
   }

.project-numeral--logo .project-numeral-outline
   {
      fill: none;
      stroke: rgba(255, 255, 255, .12);
      stroke-width: 1;
   }

.project-numeral--logo .project-numeral-fill svg
   {
      fill: currentColor;
   }

/* Supplied brand images keep their colours through the existing reveal. */
.project-numeral--image img
   {
      display: block;
      height: .9em;
      width: auto;
   }

.project-numeral--image > .project-numeral-outline
   {
      opacity: .18;
      filter: grayscale(1);
   }

.project-numeral--wordmark
   {
      width: clamp(8.5rem, 16vw, 15rem);
      max-width: 45%;
   }

.project-numeral--wordmark img
   {
      width: 100%;
      height: auto;
   }

@media (hover: hover) and (pointer: fine)
   {
      :root:not(.touch-input) .project-link:hover
         {
            border-color: rgba(255, 255, 255, .4);
         }

      :root:not(.touch-input) .project-link:hover .project-cta
         {
            color: var(--white);
         }

      :root:not(.touch-input) .project-link:hover .project-cta-arrow:not(.project-cta-arrow--ghost),
      :root:not(.touch-input) .project-link:focus-visible .project-cta-arrow:not(.project-cta-arrow--ghost)
         {
            transform: translate(115%, -115%);
         }

      :root:not(.touch-input) .project-link:hover .project-cta-arrow--ghost, :root:not(.touch-input) .project-link:focus-visible .project-cta-arrow--ghost
         {
            transform: translate(0, 0);
         }
   }

.project-link:focus-visible
   {
      outline: 2px solid var(--white);
      outline-offset: -2px;
   }

.project-link.is-touch-pressed
   {
      border-color: rgba(255, 255, 255, .4);
   }

/* Progress: a hairline that fills with the scroll, over one label per card. */
.projects-rail
   {
      position: relative;
      padding-top: 1rem;
      letter-spacing: .2em;
   }

.projects-rail-line
   {
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      height: 1px;
      background: rgba(255, 255, 255, .1);
   }

.projects-rail-fill
   {
      position: absolute;
      inset: 0;
      background: var(--white);
      transform-origin: left;
      transform: scaleX(var(--progress, 0));
   }

.projects-rail ol
   {
      display: flex;
      justify-content: space-between;
      margin: 0;
      padding: 0;
      list-style: none;
   }

.projects-rail a
   {
      display: inline-flex;
      gap: .5rem;
      padding-block: .5rem;
      color: rgba(255, 255, 255, .3);
      transition: color .5s var(--ease-out);
   }

.projects-rail a[aria-current]
   {
      color: var(--white);
   }

@media (hover: hover) and (pointer: fine)
   {
      :root:not(.touch-input) .projects-rail a:hover
         {
            color: rgba(255, 255, 255, .7);
         }
   }

.projects-rail a:focus-visible
   {
      outline-width: 2px;
      outline-offset: 2px;
   }

@media (max-width: 1200px)
   {
      .projects-rail-name
         {
            display: none;
         }
   }

@media (prefers-reduced-motion: reduce)
   {
      .projects-entered :is(.projects-title-line, .project-card)
         {
            transition: none;
         }

      .projects-entered .projects-rule::after
         {
            transition: none;
         }

      .project-cta-arrow, .core-desc-link-arrow-icon
         {
            transition: none;
         }

      .craft-cell.is-lit .craft-meter
         {
            animation: none;
         }
   }

/* Contact feedback. The fold writes opacity on the wordmark, labels and icons
   every frame, so presses only touch the row, the inner name and the social
   glyphs, never a property the fold is animating. */
:is(.section-nav-link, .topbar-wordmark)
   {
      touch-action: manipulation;
   }

.topbar-wordmark span
   {
      transition: opacity var(--press-out) var(--ease-out);
   }

.section-nav-link:not([aria-current]).is-touch-pressed
   {
      background: rgba(255, 255, 255, .12);
      transition-duration: var(--press-in);
   }

.section-nav-link[aria-current].is-touch-pressed::before
   {
      opacity: .82;
      transition-duration: var(--press-in);
   }

.topbar-wordmark.is-touch-pressed span
   {
      opacity: .6;
      transition-duration: var(--press-in);
   }

@media (hover: hover) and (pointer: fine)
   {
      :root:not(.touch-input) .section-nav-link:not([aria-current]):hover
         {
            background: rgba(255, 255, 255, .08);
         }

      :root:not(.touch-input) .section-nav-link:not([aria-current]):active
         {
            background: rgba(255, 255, 255, .14);
            transition-duration: var(--press-in);
         }

      :root:not(.touch-input) .section-nav-link[aria-current]:active::before
         {
            opacity: .82;
            transition-duration: var(--press-in);
         }

      :root:not(.touch-input) .topbar-wordmark:hover span
         {
            opacity: .75;
         }

      :root:not(.touch-input) .topbar-wordmark:active span
         {
            opacity: .6;
            transition-duration: var(--press-in);
         }
   }

/* Rings sit inside the controls: the panels and the scrolling nav row
   would otherwise cut off the page's default outset ring. */
.section-nav-link:focus-visible
   {
      outline-width: 2px;
      outline-offset: -2px;
   }

.topbar-wordmark:focus-visible
   {
      outline-width: 2px;
      outline-offset: 0;
   }

/* Seven labels need about 52rem. Below that the row tightens, then scrolls
   rather than wrapping. */
@media (max-width: 1100px)
   {
      .section-nav-link
         {
            --nav-pad: .5rem;
         }

      .section-nav
         {
            margin-right: 0;
         }

      /* Padding as wide as the fades, so a row that fits is never dimmed. */
      .section-nav
         {
            padding-inline: .75rem 1.25rem;
            -webkit-mask-image: linear-gradient(to right, transparent, #000 .75rem, #000 calc(100% - 1.25rem), transparent);
            mask-image: linear-gradient(to right, transparent, #000 .75rem, #000 calc(100% - 1.25rem), transparent);
         }
   }

@media (max-width: 700px)
   {
      .about
         {
            --page-gutter: 1.25rem;
            gap: 1.5rem;
            padding: 0 var(--page-gutter) calc(var(--footer-bottom) + 5rem);
         }

      /* Edge to edge under the status bar, the way a phone's own top bar sits. */
      .about-topbar
         {
            gap: .5rem;
            padding: max(.5rem, env(safe-area-inset-top)) 0 .5rem var(--page-gutter);
         }

      .about-topbar::after
         {
            height: 1.25rem;
         }

      .topbar-wordmark
         {
            font-size: 1.125rem;
         }

      .section-nav-divider
         {
            margin-inline: .35rem;
         }

      /* The row runs to the screen's right edge and fades there, so a clipped
         link reads as "more this way". */
      .section-nav
         {
            gap: .25rem;
         }

      .section-nav-link
         {
            font-size: .875rem;
         }

      /* Centred and stacked like Home's phone footer: icons above the signature. */
      .about-footer
         {
            flex-direction: column;
            align-items: center;
            gap: 10px;
         }

      .footer-roles
         {
            order: -1;
         }

      .projects-main
         {
            --card-w: 84vw;
            --card-gap: .75rem;
            --projects-gutter: 1.25rem;
            --projects-lead: 1.25rem;
         }

      .projects-frame
         {
            min-height: 0;
            padding-block: 1.5rem;
         }

      .projects-pinned .projects-frame
         {
            top: var(--projects-top, 0px);
            height: calc(100svh - var(--projects-top, 0px) - var(--footer-bottom) - 1rem);
            padding-block: 1rem;
         }

      .project-link
         {
            height: clamp(300px, 52svh, 440px);
         }
   }

@media (prefers-reduced-transparency: reduce)
   {
      .glass-panel
         {
            background: #1c1c1c;
            backdrop-filter: none;
            -webkit-backdrop-filter: none;
         }

      .about-topbar
         {
            background: var(--background);
         }

      .about-topbar::after
         {
            display: none;
         }
   }

@media (prefers-reduced-transparency: reduce)
   {
      .section-nav
         {
            -webkit-mask-image: none;
            mask-image: none;
         }
   }

@media (prefers-contrast: more)
   {
      .glass-panel
         {
            background: #111;
            border-color: #fff;
            backdrop-filter: none;
            -webkit-backdrop-filter: none;
         }

      .about-topbar
         {
            background: var(--background);
            border-bottom: 1px solid #fff;
         }

      .about-topbar::after
         {
            display: none;
         }

      .section-nav-divider
         {
            opacity: 1;
         }
   }

/* Forced colours would drop the rule's fill; keep it in the text colour. */
@media (forced-colors: active)
   {
      .section-nav-link::before
         {
            forced-color-adjust: none;
            background: CanvasText;
         }
   }

/* Phone hero: the desktop composition relies on width and scale that a phone
   does not have, so the heading stacks and the name drops its stroked overlaps. */
@font-face
   {
      font-family: 'Unbounded Outline Merged';
      src: url('assets/fonts/unbounded-latin-700-merged.woff2') format('woff2');
      font-style: normal;
      font-weight: 700;
      font-display: swap;
   }

@media (max-width: 700px)
   {
      /* A 1px stroke on the overlap cut reads as a glitch, so each letter gets one
         clean outline. The About top bar matches so the fold lands on the same name. */
      .wordmark, .topbar-wordmark
         {
            font-family: 'Unbounded Outline Merged', 'Unbounded Outline', Arial, sans-serif;
            letter-spacing: -.035em;
         }

      /* Each role becomes one centred row, divided by a short horizontal rule. */
      h1
         {
            grid-template-columns: auto;
            justify-items: center;
            gap: .2em;
         }

      .hero-heading-column
         {
            flex-direction: row;
            gap: .25em;
         }

      .hero-heading-rule
         {
            width: 2.5em;
            height: 1px;
            margin: 0;
         }

      .button
         {
            width: min(300px, 100%);
         }

      /* Settles the shirt behind the heading and button. */
      .portrait-stage::after
         {
            content: '';
            position: absolute;
            inset: auto 0 0;
            z-index: 2;
            height: 62%;
            background: linear-gradient(to bottom, rgba(6, 6, 6, 0), rgba(6, 6, 6, .72) 42%, rgba(6, 6, 6, .92));
         }
   }

/* Unstacked letters run wider; this keeps the name inside the page gutter. */
@media (max-width: 700px) and (min-height: 551px)
   {
      .hero
         {
            --masthead-size: min(26vw, 24svh);
         }
   }

/* On iOS the toolbar floats over the page, so the hero's small-viewport box ends
   above it. The portrait layer reaches the large-viewport edge so nothing stops short. */
@media (max-width: 700px)
   {
      .hero
         {
            overflow-x: clip;
            overflow-y: visible;
         }

      .portrait-scroll
         {
            bottom: calc(100svh - 100lvh);
            overflow: clip;
         }

      .portrait-stage
         {
            bottom: calc(100lvh - 100svh);
         }

      .portrait-stage::after
         {
            bottom: calc(100svh - 100lvh);
            height: calc(62% + 100lvh - 100svh);
         }
   }

/* About content, after daqconsulting.com/about: an outlined title that fills
   in as the page lands, then "The Core", an index of subjects beside a light
   reading panel. Without script the title is filled and every panel is
   listed; .about-motion holds the entrance back until .is-arrived. */
@property --about-fill
   {
      syntax: '<number>';
      inherits: true;
      initial-value: 1;
   }

.about-content
   {
      display: flex;
      flex-direction: column;
      gap: clamp(5rem, 14svh, 10rem);
      padding-top: clamp(1.5rem, 7svh, 5rem);
   }

.about-eyebrow
   {
      display: flex;
      align-items: center;
      gap: 1rem;
      margin: 0;
      color: rgba(255, 255, 255, .5);
      font-family: 'Geist Mono', monospace;
      font-size: 10px;
      font-weight: 300;
      letter-spacing: .3em;
      line-height: 1.5;
      text-transform: uppercase;
   }

.about-rule
   {
      position: relative;
      flex: 1;
      max-width: 240px;
      height: 1px;
      background: rgba(255, 255, 255, .1);
   }

.about-rule::after
   {
      content: '';
      position: absolute;
      inset: 0;
      background: rgba(255, 255, 255, .4);
      transform-origin: left;
   }

/* The hero fills the first screen on its own and sits to the right; the
   eyebrow's rule flips to lead into the label from the left. */
.about-hero
   {
      display: flex;
      flex-direction: column;
      align-items: flex-end;
      justify-content: center;
      min-height: calc(100svh - 5rem - clamp(1.5rem, 7svh, 5rem));
      padding-bottom: calc(var(--footer-bottom) + 4rem);
      text-align: right;
   }

/* Meet Josh's own footer sits on the bottom edge of the first screen, where
   the page lands, rather than at the end of the document. */
.about-footer-fold
   {
      top: calc(100svh - var(--footer-bottom));
      bottom: auto;
      translate: 0 -100%;
   }

.about-hero .about-eyebrow
   {
      flex-direction: row-reverse;
      width: 100%;
      margin-bottom: clamp(2rem, 5svh, 3rem);
   }

.about-hero .about-rule::after
   {
      transform-origin: right;
   }

.about-hero .about-title-line
   {
      margin-left: auto;
   }

.about-hero .about-lead
   {
      margin-left: auto;
   }

.about-resume
   {
      display: flex;
      flex-wrap: wrap;
      justify-content: flex-end;
      gap: 12px;
      margin: clamp(1.75rem, 4svh, 2.5rem) 0 0;
   }

.about-resume .button
   {
      font-family: 'Google Sans', Arial, Helvetica, sans-serif;
      font-size: .9375rem;
      font-weight: 400;
      letter-spacing: .01em;
   }

.about-resume .button-arrow svg
   {
      width: 18px;
      height: 18px;
   }

/* The workspace scene holds the empty half opposite the copy. Script clears
   its sky and floor, so the furniture stands straight on the grain with no
   frame to fade; it sits behind the copy in case the two meet. */
.about-hero
   {
      position: relative;
      isolation: isolate;
   }

.about-scene
   {
      position: absolute;
      z-index: -1;
      top: 50%;
      left: calc(-1 * var(--page-gutter));
      width: min(66%, 150svh);
      margin: 0;
      translate: 0 -58%;
      pointer-events: none;
      user-select: none;
   }

.about-scene-canvas
   {
      display: block;
      width: 100%;
      height: auto;
      aspect-ratio: 3 / 2;
      opacity: 0;
      transition: opacity .8s var(--ease-out);
   }

/* The canvas stays clear until the scene has loaded. Its camera is fixed, so
   clicks and taps reach the scene's own effects (the lamp and the rest) while
   Meet Josh is on screen, and a vertical swipe over it still scrolls. */
.about-scene-canvas.is-loaded
   {
      opacity: 1;
      pointer-events: auto;
      touch-action: pan-y !important;
   }

.about[inert] .about-scene-canvas, .about-motion:not(.is-arrived) .about-scene-canvas, .about-hero:is(.is-away, .is-leaving) .about-scene-canvas
   {
      pointer-events: none;
   }

/* Narrower screens bring the copy across the middle, so the scene steps
   further off the left edge and lets its far plant fall under the fade. */
@media (max-width: 1100px)
   {
      .about-scene
         {
            width: 60%;
            left: calc(-1 * var(--page-gutter) - 7%);
         }
   }

/* An upright tablet has its room above the copy instead, so the scene
   joins the column and leads it. */
@media (min-width: 701px) and (max-width: 1100px) and (orientation: portrait)
   {
      .about-scene
         {
            position: relative;
            top: auto;
            left: auto;
            align-self: center;
            width: min(70%, 44svh * 1.5);
            margin: 0 0 -2.5rem;
            translate: none;
         }
   }

.about-title
   {
      display: block;
      justify-items: start;
      margin: 0;
      font-family: 'Bounded', Arial, sans-serif;
      font-size: clamp(2.75rem, 9.5vw, 8.5rem);
      font-weight: 200;
      line-height: .92;
      letter-spacing: -.06em;
      text-transform: uppercase;
   }

.about-title-line
   {
      --about-fill: 1;
      position: relative;
      display: block;
      width: fit-content;
   }

.about-title-strong
   {
      font-weight: 800;
   }

/* Keep the two main display headings in Bounded; section/card headings use Google Sans. */
#meet-josh .about-title, #meet-josh .about-title span, #projects-title, #projects-title .projects-title-line
   {
      font-family: 'Bounded', Arial, sans-serif;
   }

/* The stroke is the resting state; the fill sweeps across it on a 100° edge,
   the same cut the project cards use. */
/* Tight tracking lets the last glyph's ink run past its advance; the padding
   keeps it inside the box the mask covers. */
.about-title-outline, .about-title-fill
   {
      padding-right: .12em;
   }

.about-title-outline
   {
      display: block;
      color: transparent;
      -webkit-text-stroke: 1px rgba(255, 255, 255, .32);
   }

.about-title-fill
   {
      position: absolute;
      inset: 0;
      display: block;
      color: var(--white);
      --edge-at: calc(var(--about-fill) * 124% - 12%);
      -webkit-mask-image: linear-gradient(var(--about-sweep, 100deg), #000 var(--edge-at), transparent calc(var(--edge-at) + 10%));
      mask-image: linear-gradient(var(--about-sweep, 100deg), #000 var(--edge-at), transparent calc(var(--edge-at) + 10%));
   }

.about-lead
   {
      max-width: 44ch;
      margin: clamp(2rem, 4svh, 2.5rem) 0 0;
      color: rgba(255, 255, 255, .55);
      font-family: 'Google Sans', Arial, Helvetica, sans-serif;
      font-size: clamp(1.05rem, 2vw, 1.4rem);
      font-weight: 400;
      line-height: 1.6;
      text-wrap: pretty;
   }

.about-motion:not(.is-arrived) .about-hero .about-title-line
   {
      --about-fill: 0;
   }

.about-motion:not(.is-arrived) .about-hero .about-rule::after
   {
      transform: scaleX(0);
   }

.about-motion:not(.is-arrived) :is(.about-lead, .about-resume)
   {
      opacity: 0;
      transform: translateY(12px);
   }

.about-motion.is-arrived .about-hero .about-title-line
   {
      transition: --about-fill 1.6s var(--ease-out) .15s;
   }

.about-motion.is-arrived .about-title-strong
   {
      transition-delay: .3s;
   }

.about-motion.is-arrived .about-hero .about-rule::after
   {
      transition: transform 1.2s var(--ease-out) .1s;
   }

.about-motion.is-arrived .about-lead
   {
      transition: opacity .9s var(--ease-out) .5s, transform .9s var(--ease-out) .5s;
   }

.about-motion.is-arrived .about-resume
   {
      transition: opacity .9s var(--ease-out) .65s, transform .9s var(--ease-out) .65s;
   }

/* Meet Josh arrives again when the page turns back up to it. Once Experience
   has carried it off the top of the screen it resets out of sight, set to
   come in the way the page is travelling: the fill sweeps from the right and
   the copy settles down into place. */
.about-motion .about-hero.is-away .about-title-line
   {
      --about-fill: 0;
      transition: none;
   }

.about-motion .about-hero.is-away .about-rule::after
   {
      transform: scaleX(0);
      transition: none;
   }

.about-motion .about-hero.is-away :is(.about-lead, .about-resume)
   {
      opacity: 0;
      transform: translateY(-12px);
      transition: none;
   }

.about-hero.is-returning
   {
      --about-sweep: 280deg;
   }

/* Leaving for Experience runs the entrance backwards and quicker, and has
   cleared by the time the page cuts to Experience: the buttons and lead
   lift away first, the title empties last line first, the rule draws back
   in and the scene fades. */
.about-motion.is-arrived .about-hero.is-leaving :is(.about-lead, .about-resume)
   {
      opacity: 0;
      transform: translateY(-12px);
   }

.about-motion.is-arrived .about-hero.is-leaving .about-resume
   {
      transition: opacity .25s var(--ease-out), transform .25s var(--ease-out);
   }

.about-motion.is-arrived .about-hero.is-leaving .about-lead
   {
      transition: opacity .25s var(--ease-out) .05s, transform .25s var(--ease-out) .05s;
   }

.about-motion.is-arrived .about-hero.is-leaving .about-title-line
   {
      --about-fill: 0;
      transition: --about-fill .28s var(--ease-out) .06s;
   }

.about-motion.is-arrived .about-hero.is-leaving .about-title-strong
   {
      transition-delay: 0s;
   }

.about-motion.is-arrived .about-hero.is-leaving .about-rule::after
   {
      transform: scaleX(0);
      transition: transform .28s var(--ease-out) .06s;
   }

.about-motion.is-arrived .about-hero.is-leaving .about-scene
   {
      opacity: 0;
      scale: 1.02;
      transition: opacity .3s var(--ease-out) .04s, scale .3s var(--ease-out) .04s;
   }

.about-motion.is-arrived .about-hero:not(.is-away, .is-leaving) :is(.about-eyebrow, .about-title-outline)
   {
      transition: opacity .4s var(--ease-out);
   }

.about-motion.is-arrived .about-hero.is-leaving :is(.about-eyebrow, .about-title-outline)
   {
      opacity: 0;
      transition: opacity .25s var(--ease-out) .06s;
   }

.about-motion .about-hero.is-away :is(.about-eyebrow, .about-title-outline)
   {
      opacity: 0;
      transition: none;
   }

/* The scene fades in as it settles back to its size, a beat before the
   title fills. It never blurs: a filter over a live WebGL canvas is redrawn
   every frame the scene draws. */
.about-motion:not(.is-arrived) .about-scene, .about-motion .about-hero.is-away .about-scene
   {
      opacity: 0;
      scale: 1.04;
   }

.about-motion .about-hero.is-away .about-scene
   {
      transition: none;
   }

.about-motion.is-arrived .about-scene
   {
      transition: opacity 1s var(--ease-out), scale 1.6s var(--ease-out);
   }

.about-block-head
   {
      display: flex;
      flex-direction: column;
      gap: clamp(1rem, 1.6vw, 1.25rem);
      margin-bottom: clamp(2.5rem, 4vw, 3.5rem);
   }

.about-heading
   {
      margin: 0;
      font-family: 'Bounded', Arial, sans-serif;
      font-size: clamp(2.1rem, 4.6vw, 4.25rem);
      font-weight: 300;
      line-height: .95;
      letter-spacing: -.045em;
   }

/* The index and its panel respond to the width of their block rather than
   the window. */
.about-block
   {
      container: core / inline-size;
   }

/* The Core is a screen of its own. The top padding clears the sticky bar,
   which overlays it once the page has turned to it. */
.core-section, .core-stage
   {
      display: flex;
      flex-direction: column;
      justify-content: center;
   }

.core-section
   {
      min-height: 100vh;
      min-height: 100svh;
      padding-block: 5rem var(--footer-room);
   }

/* When the screen holds the whole index, Experience pins and scrolls through
   its cards a screen at a time; script sets --core-steps to the card count. */
.core-pinned .core-section
   {
      display: block;
      height: calc(var(--core-steps) * 100svh);
      padding-block: 0;
   }

.core-pinned .core-stage
   {
      position: sticky;
      top: 0;
      height: 100vh;
      height: 100svh;
      padding-block: 5rem var(--footer-room);
   }

.core-section .about-block-head
   {
      margin-bottom: clamp(1.5rem, 4svh, 3.5rem);
   }

.core-section .about-heading
   {
      font-family: 'Google Sans', Arial, sans-serif;
      font-size: min(clamp(2.1rem, 4.6vw, 4.25rem), max(2.1rem, 7svh));
   }

/* About ends with Experience, so its screen is the last one and the footer
   sits on it; the section reserves that room instead of the page. */
.about:has(.core-section)
   {
      --footer-room: calc(var(--footer-bottom) + 3.5rem);
      padding-bottom: 0;
   }

/* About turns a page at a time; script places one marker per page. A hard
   flick still turns only one. */
html.about-paging
   {
      scroll-snap-type: y mandatory;
   }

.page-snap
   {
      position: absolute;
      left: 0;
      width: 1px;
      height: 1px;
      pointer-events: none;
      scroll-snap-align: start;
      scroll-snap-stop: always;
   }

.core
   {
      color: #eee;
   }

.core-subjects
   {
      display: flex;
      flex-direction: column;
      min-width: 0;
   }

.core-subject
   {
      display: flex;
      align-items: center;
      justify-content: space-between;
      gap: 1.25rem;
      width: 100%;
      min-height: 54px;
      padding: 13px 0;
      border: 0;
      border-top: 1px solid rgba(255, 255, 255, .14);
      background: none;
      color: rgba(255, 255, 255, .58);
      font-family: 'Instrument Serif', Georgia, serif;
      font-size: 1.125rem;
      font-weight: 400;
      line-height: 1.25;
      letter-spacing: -.015em;
      text-align: left;
      cursor: pointer;
      touch-action: manipulation;
      -webkit-tap-highlight-color: transparent;
      transition: color var(--press-out) var(--ease-out);
   }

/* A position can carry its terms and dates under it, set like the card's own
   small labels. */
.core-subject:has(.core-subject-org)
   {
      flex-direction: column;
      align-items: flex-start;
      justify-content: center;
      gap: .55rem;
   }

.core-subject-org
   {
      color: rgba(255, 255, 255, .55);
      font-family: 'Geist Mono', monospace;
      font-size: 11px;
      font-weight: 300;
      letter-spacing: .18em;
      line-height: 1.4;
      text-transform: uppercase;
   }

.core-subject-sep
   {
      margin-inline: .5em;
      opacity: .6;
   }

.core-subject:last-child
   {
      border-bottom: 1px solid rgba(255, 255, 255, .14);
   }

.core-subject[aria-selected="true"]
   {
      color: var(--white);
   }

/* A rail runs down the list's left edge. The job being read carries one
   marker that slides and stretches from job to job; script places it. While
   Experience is pinned, a fainter fill shows how far through the jobs the
   page has turned. */
.core-subject
   {
      position: relative;
   }

.core-subjects
   {
      position: relative;
   }

.core-rail
   {
      position: absolute;
      top: 0;
      bottom: 0;
      left: -12px;
      width: 1px;
      background: rgba(255, 255, 255, .12);
      pointer-events: none;
   }

.core-rail-fill, .core-rail-marker
   {
      position: absolute;
      top: 0;
      left: 0;
      width: 1px;
      transform-origin: top;
   }

.core-rail-fill
   {
      height: 100%;
      background: rgba(255, 255, 255, .32);
      transform: scaleY(var(--core-progress, 0));
      transition: transform 380ms var(--ease-out-quart);
   }

:root:not(.core-pinned) .core-rail-fill
   {
      visibility: hidden;
   }

.core-rail-marker
   {
      height: 1px;
      background: var(--white);
      transform: translateY(var(--marker-y, 0)) scaleY(var(--marker-h, 0));
   }

.core-rail.is-placed .core-rail-marker
   {
      transition: transform 380ms var(--ease-out-quart);
   }

/* Where scroll-driven animation exists, the fill follows the scroll itself
   rather than stepping from job to job. */
@keyframes core-progress
   {
      from
         {
            transform: scaleY(0);
         }

      to
         {
            transform: scaleY(1);
         }
   }

@supports (animation-timeline: view())
   {
      .core-pinned .core-section
         {
            view-timeline: --core-runway block;
         }

      .core-pinned .core-rail-fill
         {
            transition: none;
            animation: core-progress linear both;
            animation-timeline: --core-runway;
            animation-range: contain 0% contain 100%;
         }
   }

.core-subject:focus-visible
   {
      outline: 1px solid rgba(255, 255, 255, .9);
      outline-offset: -5px;
   }

.core-subject.is-touch-pressed
   {
      color: rgba(255, 255, 255, .85);
      transition-duration: var(--press-in);
   }

.core-reading
   {
      display: grid;
      min-width: 0;
      margin-top: 1.25rem;
      background: #f0f0eb;
      color: #171717;
   }

.core-reading ::selection
   {
      background: #171717;
      color: #f0f0eb;
   }

.core-panel
   {
      display: flex;
      flex-direction: column;
      min-width: 0;
      padding: 27px 24px 19px;
   }

.core-panel + .core-panel
   {
      border-top: 1px solid #bdbdb7;
   }

.core-panel:focus-visible
   {
      outline: 1px solid #171717;
      outline-offset: -7px;
   }

/* A workplace's mark sits behind the card as a watermark, cropped by the
   bottom-right corner: a faint outline always, with an ink fill that sweeps
   in with the title. Same build as the project logos, inked for paper. */
.core-panel
   {
      position: relative;
      isolation: isolate;
   }

/* Each mark sets --logo-ratio (width over height) and is sized to whichever
   runs out first, the card's height or 60% of its width, so wide wordmarks
   don't overrun a phone. The corner crops a fixed share of the mark itself. */
.core-logo
   {
      position: absolute;
      inset: 0;
      z-index: -1;
      overflow: hidden;
      container-type: size;
      pointer-events: none;
      user-select: none;
   }

.core-logo-outline, .core-logo-fill
   {
      position: absolute;
      right: 0;
      bottom: 0;
      height: min(82cqh, 60cqw / var(--logo-ratio, 1));
      aspect-ratio: var(--logo-ratio, 1);
      translate: 9% 19.5%;
   }

.core-logo svg
   {
      display: block;
      overflow: visible;
   }

.core-logo-fill svg
   {
      width: 100%;
      height: 100%;
   }

.core-logo-outline
   {
      fill: none;
      stroke: rgba(23, 23, 23, .12);
      stroke-width: 1;
   }

.core-logo-fill
   {
      color: rgba(23, 23, 23, .045);
   }

.core-logo-fill svg
   {
      fill: currentColor;
   }

/* With script, the panels stack in one cell inside a card of fixed height,
   so the card never jumps as the subject changes and long copy can't grow
   it. Copy is written to fit; anything past the card is clipped. */
.core-ready .core-reading
   {
      grid-template-rows: minmax(0, 1fr);
      min-height: var(--core-card-height, 400px);
      overflow: hidden;
   }

/* The incoming job waits for the outgoing one to clear so the two never read
   on top of each other. Script moves them in the direction of the scroll. */
.core-ready .core-panel
   {
      grid-area: 1 / 1;
      border-top: 0;
      transition: opacity 240ms var(--ease-out) 100ms;
   }

.core-ready .core-panel[aria-hidden="true"]
   {
      visibility: hidden;
      opacity: 0;
      pointer-events: none;
      transition: opacity 160ms var(--ease-out), visibility 0s linear 160ms;
   }

/* A turn keeps the card still and hands its contents over piece by piece,
   so script owns the handover and the crossfade stands down. */
.core-reading.is-turning .core-panel
   {
      transition: none;
   }

/* Titles sweep in on the same 100° edge as the About title and the project
   cards; script runs --core-reveal from 0 to 1 and flips the angle when the
   scroll runs back. At rest the mask is fully open. */
@property --core-reveal
   {
      syntax: '<number>';
      inherits: false;
      initial-value: 1;
   }

.core-title-text, .core-logo-fill
   {
      --edge-at: calc(var(--core-reveal) * 124% - 12%);
      display: inline-block;
      -webkit-mask-image: linear-gradient(var(--sweep, 100deg), #000 var(--edge-at), transparent calc(var(--edge-at) + 10%));
      mask-image: linear-gradient(var(--sweep, 100deg), #000 var(--edge-at), transparent calc(var(--edge-at) + 10%));
   }

.core-title-text.is-erasing, .core-logo-fill.is-erasing
   {
      -webkit-mask-image: linear-gradient(var(--sweep, 100deg), transparent var(--edge-at), #000 calc(var(--edge-at) + 10%));
      mask-image: linear-gradient(var(--sweep, 100deg), transparent var(--edge-at), #000 calc(var(--edge-at) + 10%));
   }

/* The workplace draws its letters on an SVG copy laid over the real name:
   each word traces its outline, fills, then hands back to the text. */
.core-org
   {
      position: relative;
   }

.core-org-mark
   {
      display: inline-block;
      width: 0;
      height: 0;
   }

.core-org-ink
   {
      position: absolute;
      top: 0;
      left: 0;
      overflow: visible;
      pointer-events: none;
   }

.core-org-ink text
   {
      font: inherit;
      letter-spacing: inherit;
      fill: currentColor;
      stroke: currentColor;
      stroke-width: .6px;
      stroke-opacity: 0;
   }

.core-org.is-inking > .core-org-word
   {
      color: transparent;
   }

/* Detail words decode on a copy laid over the real words. */
.core-decode
   {
      position: relative;
   }

.core-decode-code
   {
      position: absolute;
      inset: 0;
      white-space: nowrap;
      pointer-events: none;
   }

.core-decode.is-decoding .core-decode-text
   {
      color: transparent;
   }

/* Titles hold one line; the script shrinks any that would run past it. */
.core-ready .core-title
   {
      white-space: nowrap;
   }

.core-org
   {
      margin: 14px 0 0;
      color: #6b6b66;
      font-family: 'Geist Mono', monospace;
      font-size: 12px;
      font-weight: 300;
      letter-spacing: .18em;
      line-height: 1.5;
      text-transform: uppercase;
   }

.core-meta
   {
      display: flex;
      flex-wrap: wrap;
      gap: 8px 18px;
      margin: auto 0 0;
      padding: 24px 0 0;
      color: #454541;
      font-family: 'Geist Mono', monospace;
      font-size: 12px;
      font-weight: 300;
      letter-spacing: .12em;
      line-height: 1.5;
      text-transform: uppercase;
      list-style: none;
   }

.core-meta li
   {
      display: flex;
      align-items: center;
      gap: 9px;
   }

.core-meta svg
   {
      flex: none;
      width: 15px;
      height: 15px;
      fill: none;
      stroke: currentColor;
      stroke-width: 1.5;
      stroke-linecap: round;
      stroke-linejoin: round;
   }

.core-title
   {
      margin: 0;
      font-family: 'Instrument Serif', Georgia, serif;
      font-size: clamp(1.6rem, 6.4cqw, 2.25rem);
      font-weight: 400;
      line-height: 1.04;
      letter-spacing: -.015em;
      text-wrap: balance;
   }

.core-desc
   {
      margin: 27px 0 0;
      color: #454541;
      font-family: 'Google Sans', Arial, Helvetica, sans-serif;
      font-size: clamp(17px, 4.6cqw, 21px);
      line-height: 1.55;
      text-wrap: pretty;
   }

.core-desc + .core-desc
   {
      margin-top: 1em;
   }

/* A link inside the card copy, in the same type as the sentence, darker so it
   reads as something to open. The arrow flies like the project cards: one
   copy leaves up-right, the ghost arrives from below. */
.core-desc-link
   {
      display: inline-flex;
      align-items: center;
      gap: .28em;
      color: #171717;
      font: inherit;
      letter-spacing: inherit;
      line-height: inherit;
      text-transform: none;
      white-space: nowrap;
      vertical-align: baseline;
      touch-action: manipulation;
      -webkit-tap-highlight-color: transparent;
   }

.core-desc-link-arrow
   {
      position: relative;
      flex: none;
      width: 1em;
      height: 1em;
      overflow: hidden;
   }

.core-desc-link-arrow-icon
   {
      position: absolute;
      inset: 0;
      width: 100%;
      height: 100%;
      fill: none;
      stroke: currentColor;
      stroke-width: 2;
      stroke-linecap: round;
      stroke-linejoin: round;
      transition: transform 420ms cubic-bezier(.2, .7, .2, 1);
   }

.core-desc-link-arrow-icon--ghost
   {
      transform: translate(-115%, 115%);
   }

.core-desc-link:focus-visible
   {
      outline: 1px solid #171717;
      outline-offset: 4px;
   }

.core-desc-link.is-touch-pressed
   {
      opacity: .72;
   }

.core-desc-link.is-touch-pressed .core-desc-link-arrow-icon:not(.core-desc-link-arrow-icon--ghost)
   {
      transform: translate(115%, -115%);
   }

.core-desc-link.is-touch-pressed .core-desc-link-arrow-icon--ghost
   {
      transform: translate(0, 0);
   }

.core-detail
   {
      margin-top: auto;
      padding-top: 32px;
   }

.core-tech
   {
      display: flex;
      flex-wrap: wrap;
      gap: 8px 18px;
      margin: 0 0 20px;
      padding: 0;
      color: #585854;
      font-family: 'Google Sans', Arial, Helvetica, sans-serif;
      font-size: 12px;
      line-height: 1.5;
      list-style: none;
   }

.core-tech li
   {
      display: flex;
      align-items: center;
      gap: 7px;
   }

.core-tech li::before
   {
      content: '';
      width: 3px;
      height: 3px;
      background: currentColor;
   }

.core-link
   {
      display: flex;
      align-items: center;
      justify-content: space-between;
      gap: 18px;
      min-height: 52px;
      padding-block: 14px 5px;
      border-top: 1px solid #bdbdb7;
      color: #171717;
      font-family: 'Google Sans', Arial, Helvetica, sans-serif;
      font-size: 13px;
      line-height: 1.4;
      touch-action: manipulation;
      -webkit-tap-highlight-color: transparent;
   }

.core-link svg
   {
      flex: none;
      width: 1rem;
      height: 1rem;
      fill: none;
      stroke: currentColor;
      stroke-width: 1.5;
      stroke-linecap: round;
      stroke-linejoin: round;
      transition: transform 200ms var(--ease-out);
   }

.core-link:focus-visible
   {
      outline: 1px solid #171717;
      outline-offset: 5px;
   }

.core-link.is-touch-pressed svg
   {
      transform: translate(2px, -2px);
      transition-duration: var(--press-in);
   }

@container core (min-width: 640px)
   {
      .core
         {
            display: grid;
            grid-template-columns: minmax(0, .82fr) minmax(0, 1.18fr);
         }

      .core-subjects
         {
            padding-right: 28px;
         }

      /* Rows and card share one height, and it gives way on short screens so
         the whole of Experience and the footer fit the window together. */
      .core-subject
         {
            flex: 1 1 auto;
            min-height: 0;
            padding-block: clamp(8px, 1.5svh, 12px);
            font-size: min(clamp(20px, 2.4cqw, 28px), max(20px, 3.4svh));
         }

      .core-reading
         {
            margin-top: 0;
         }

      .core-reading
         {
            --core-card-height: clamp(320px, calc(100svh - 21rem), 370px);
         }

      .core-panel
         {
            padding: clamp(22px, 4svh, 34px) 30px clamp(18px, 3svh, 25px);
         }

      .core-title
         {
            font-size: clamp(1.75rem, 3.4cqw, 2.4rem);
         }

      .core-desc
         {
            margin-top: clamp(20px, 4svh, 35px);
            font-size: clamp(18px, 2cqw, 22px);
         }

      .core-desc + .core-desc
         {
            margin-top: 1em;
         }

      .core-detail
         {
            padding-top: clamp(20px, 4svh, 40px);
         }

      .core-meta
         {
            gap: 10px 26px;
            padding-top: clamp(18px, 3.5svh, 32px);
            letter-spacing: .2em;
         }
   }

@container core (min-width: 1000px)
   {
      .core-subjects
         {
            padding-right: 44px;
         }

      .core-reading
         {
            --core-card-height: clamp(320px, calc(100svh - 21rem), 440px);
         }

      .core-panel
         {
            padding: clamp(28px, 4.5svh, 44px) 42px clamp(22px, 3svh, 31px);
         }

      .core-title
         {
            font-size: 2.6rem;
         }

      .core-desc
         {
            margin-top: clamp(24px, 4.5svh, 44px);
         }
   }

/* Tech Hub, Kisaka, Pearl, Tellistic, and GCE carry three paragraphs in the same card as the shorter jobs.
   The card does not grow: tighter spacing here, and script sizes the type
   to the room that is left. */
#core-consultant .core-desc, #core-kisaka .core-desc, #core-intern .core-desc, #core-tellistic .core-desc, #core-gce .core-desc, #core-kisaka-support .core-desc
   {
      margin-top: 20px;
      line-height: 1.45;
   }

#core-consultant .core-desc + .core-desc, #core-kisaka .core-desc + .core-desc, #core-intern .core-desc + .core-desc, #core-tellistic .core-desc + .core-desc, #core-gce .core-desc + .core-desc, #core-kisaka-support .core-desc + .core-desc
   {
      margin-top: .7em;
   }

#core-kisaka .core-desc-link
   {
      color: #cc0033;
   }

#core-kisaka .core-desc-link:focus-visible
   {
      outline-color: #cc0033;
   }

/* Education, after the "Evolution of AI" diptych on
   daqconsulting.com/ai-engineering: two sheets side by side. A finished
   degree is solid, its outlined title inked over; one still under way is a
   dashed draft. It is the
   last screen, so it reserves the footer's room like Experience does. */
.edu-section
   {
      display: flex;
      flex-direction: column;
      justify-content: center;
      min-height: 100vh;
      min-height: 100svh;
      padding-block: 5rem var(--footer-room);
   }

.edu-section .about-heading
   {
      font-family: 'Google Sans', Arial, sans-serif;
      font-size: min(clamp(2.1rem, 4.6vw, 4.25rem), max(2.1rem, 7svh));
   }

.edu-sheets
   {
      display: grid;
      grid-template-columns: minmax(0, 1fr);
      gap: 16px;
   }

.edu-sheet
   {
      position: relative;
      display: grid;
      grid-template-rows: auto auto auto;
      align-items: start;
      gap: 20px;
      min-width: 0;
      padding: 24px;
      border: 1px solid rgba(255, 255, 255, .3);
   }

.edu-sheet-draft
   {
      border-style: dashed;
      border-color: rgba(255, 255, 255, .15);
      color: #a5a5a0;
   }

.edu-sheet-ink
   {
      background: rgba(255, 255, 255, .02);
      color: #b8b8b2;
   }

.edu-label
   {
      display: flex;
      align-items: center;
      gap: 12px;
      margin: 0;
      font-family: 'Geist Mono', monospace;
      font-size: 10px;
      font-weight: 300;
      letter-spacing: .25em;
      line-height: 1.5;
      text-transform: uppercase;
   }

/* Where the school is follows its status, pinned like the job locations in
   Experience. */
.edu-place
   {
      display: flex;
      align-items: center;
      gap: 8px;
      margin-left: 8px;
   }

.edu-place svg
   {
      flex: none;
      width: 13px;
      height: 13px;
      fill: none;
      stroke: currentColor;
      stroke-width: 1.5;
      stroke-linecap: round;
      stroke-linejoin: round;
   }

.edu-marker
   {
      flex: 0 0 6px;
      width: 6px;
      height: 6px;
      border: 1px solid;
   }

.edu-sheet-ink .edu-marker
   {
      border-color: var(--white);
      background: var(--white);
   }

/* A degree still under way carries the square too, but only its outline:
   a dash runs round it without end, the sheet's dashed lines drawing
   themselves. The dash and its gap sum to the perimeter, so one lap per
   cycle loops seamlessly. The global reduced-motion rule holds it still. */
@keyframes edu-trace
   {
      to
         {
            stroke-dashoffset: -28;
         }
   }

.edu-tracer
   {
      flex: 0 0 8px;
      width: 8px;
      height: 8px;
      overflow: visible;
      fill: none;
      stroke: currentColor;
      stroke-width: 1;
      stroke-dasharray: 11 17;
      animation: edu-trace 1.6s linear infinite;
   }

.edu-title
   {
      position: relative;
      min-width: 0;
      margin: 0;
      color: var(--white);
      font-family: 'Instrument Serif', Georgia, serif;
      font-size: 30px;
      font-weight: 400;
      line-height: 1.15;
      letter-spacing: -.015em;
   }

.edu-title-draft
   {
      display: block;
      color: transparent;
      -webkit-text-stroke: 1px rgba(255, 255, 255, .6);
   }

/* Script runs the ink's wipe; at rest it covers the draft completely. */
.edu-title-ink
   {
      position: absolute;
      inset: 0;
      display: block;
      clip-path: inset(0);
   }

.edu-dates
   {
      margin: 12px 0 0;
      color: #8a8a85;
      font-family: 'Geist Mono', monospace;
      font-size: 11px;
      font-weight: 300;
      letter-spacing: .18em;
      line-height: 1.5;
      text-transform: uppercase;
   }

.edu-desc
   {
      min-width: 0;
      margin: 0;
      font-family: 'Google Sans', Arial, Helvetica, sans-serif;
      font-size: 17px;
      font-weight: 400;
      line-height: 1.6;
      text-wrap: pretty;
   }

/* A concentration sits on its own line under the degree, set in the mono
   of the labels so it reads as a note on the degree rather than more of it. */
.edu-emphasis
   {
      display: block;
      margin-top: .7em;
      font-family: 'Geist Mono', monospace;
      font-size: 11px;
      font-weight: 300;
      letter-spacing: .16em;
      line-height: 1.6;
      text-transform: uppercase;
      opacity: .75;
   }

/* Each school's mark sits behind its sheet as a watermark, cropped by the
   bottom-right corner, built like the workplace logos in Experience but
   inked for the dark sheet. A finished degree's mark fills in with its
   title; one still under way stays a dashed outline, like its sheet. */
.edu-sheet
   {
      isolation: isolate;
   }

.edu-logo
   {
      position: absolute;
      inset: 0;
      z-index: -1;
      overflow: hidden;
      container-type: size;
      pointer-events: none;
      user-select: none;
   }

.edu-logo-outline, .edu-logo-fill
   {
      position: absolute;
      right: 0;
      bottom: 0;
      height: min(82cqh, 48cqw / var(--logo-ratio, 1));
      aspect-ratio: var(--logo-ratio, 1);
      translate: 9% 19.5%;
   }

.edu-logo svg
   {
      display: block;
      overflow: visible;
   }

.edu-logo-fill svg
   {
      width: 100%;
      height: 100%;
      fill: currentColor;
   }

.edu-logo-outline
   {
      fill: none;
      stroke: rgba(255, 255, 255, .14);
      stroke-width: 1;
   }

.edu-sheet-draft .edu-logo-outline
   {
      stroke: rgba(255, 255, 255, .26);
      stroke-dasharray: 3 3;
   }

.edu-logo-fill
   {
      color: rgba(255, 255, 255, .05);
      clip-path: inset(0);
   }

/* The sheet under way catches the light: a band crosses it left to right,
   brightening whatever the sheet paints as it passes, the dashed border,
   the label, the outlined title, the copy and the mark, and nothing behind
   it. The clip keeps the band inside the sheet and makes the sheet the
   filter's backdrop, so the page and its grain stay out of it. The band
   waits off the left edge between crossings, which is also where reduced
   motion leaves it. */
.edu-sheet-draft
   {
      clip-path: inset(-1px);
   }

.edu-sheet-draft::after
   {
      content: '';
      position: absolute;
      inset: -1px;
      z-index: 1;
      pointer-events: none;
      -webkit-backdrop-filter: brightness(1.9);
      backdrop-filter: brightness(1.9);
      -webkit-mask-image: linear-gradient(105deg, transparent 36%, #000 50%, transparent 64%);
      mask-image: linear-gradient(105deg, transparent 36%, #000 50%, transparent 64%);
      transform: translateX(-100%);
      will-change: transform;
   }

@keyframes edu-shine
   {
      from
         {
            transform: translateX(-100%);
         }

      30%, to
         {
            transform: translateX(100%);
         }
   }

/* Side by side, the sheets share their rows so labels, titles and copy line
   up across both. */
@container core (min-width: 760px)
   {
      .edu-sheets
         {
            grid-template-columns: repeat(2, minmax(0, 1fr));
            gap: 20px 24px;
         }

      .edu-sheet
         {
            grid-row: span 3;
            grid-template-rows: subgrid;
            padding: 32px;
         }

      .edu-title
         {
            font-size: 38px;
         }

      .edu-desc
         {
            font-size: 19px;
         }
   }

@media (hover: hover) and (pointer: fine)
   {
      :root:not(.touch-input) .core-subject:hover
         {
            color: var(--white);
         }

      :root:not(.touch-input) .core-link:hover span
         {
            text-decoration: underline 1px;
            text-underline-offset: 4px;
         }

      :root:not(.touch-input) .core-link:hover svg
         {
            transform: translate(3px, -3px);
         }

      :root:not(.touch-input) .core-desc-link:hover .core-desc-link-arrow-icon:not(.core-desc-link-arrow-icon--ghost),
      :root:not(.touch-input) .core-desc-link:focus-visible .core-desc-link-arrow-icon:not(.core-desc-link-arrow-icon--ghost)
         {
            transform: translate(115%, -115%);
         }

      :root:not(.touch-input) .core-desc-link:hover .core-desc-link-arrow-icon--ghost,
      :root:not(.touch-input) .core-desc-link:focus-visible .core-desc-link-arrow-icon--ghost
         {
            transform: translate(0, 0);
         }
   }

@media (max-width: 700px)
   {
      .about-content
         {
            gap: 5rem;
            padding-top: 1.5rem;
         }

      .about-hero
         {
            min-height: calc(100svh - 4rem - 1.5rem);
         }

      .core-section, .edu-section
         {
            padding-top: 4rem;
         }

      .about:has(.core-section)
         {
            --footer-room: calc(var(--footer-bottom) + 5rem);
            padding-bottom: 0;
         }

      .about-title
         {
            font-size: clamp(2.75rem, 17vw, 5rem);
         }

      /* Phones leave the 3D scene out; script does not load it at this width. */
      .about-scene
         {
            display: none;
         }
   }

/* The big boxes that arrive after a page cut (Experience's card, How it
   works' sheet) come in through a soft edge that sweeps up from their foot
   as they rise into place, so no hard line crosses a bright box and nothing
   pops. --sheet-reveal runs 0 to 1; the edge is feathered over 15% of the
   box and starts and ends past its edges, so 0 hides it all and 1 shows it
   all. */
@property --sheet-reveal
   {
      syntax: '<number>';
      inherits: false;
      initial-value: 1;
   }

:is(.core-motion .core-reading, .how-motion .arch-sheet)
   {
      --reveal-at: calc(var(--sheet-reveal) * 130% - 15%);
      -webkit-mask-image: linear-gradient(to top, #000 var(--reveal-at), transparent calc(var(--reveal-at) + 15%));
      mask-image: linear-gradient(to top, #000 var(--reveal-at), transparent calc(var(--reveal-at) + 15%));
   }

/* Experience arrives once, the first time the page turns to it: the rule
   draws, the heading fills, the jobs fade in one after another and the card
   rises in through a soft edge. Its eyebrow and outline fade up from nothing
   too, so the page it replaces never shows through. Script adds .core-motion
   only when motion is allowed, so without it everything is already in place. */
.core-motion:not(.is-entered) .about-rule::after
   {
      transform: scaleX(0);
   }

.core-motion:not(.is-entered) .about-title-line
   {
      --about-fill: 0;
   }

.core-motion:not(.is-entered) .core-subject
   {
      opacity: 0;
   }

.core-motion:not(.is-entered) .core-rail
   {
      opacity: 0;
   }

.core-motion:not(.is-entered) .core-reading
   {
      --sheet-reveal: 0;
      translate: 0 32px;
   }

.core-motion:not(.is-entered) :is(.about-eyebrow, .about-title-outline)
   {
      opacity: 0;
   }

.core-motion.is-entered .about-rule::after
   {
      transition: transform 1.2s var(--ease-out) .1s;
   }

.core-motion.is-entered .about-title-line
   {
      transition: --about-fill 1.6s var(--ease-out) .15s;
   }

.core-motion.is-entered .core-subject
   {
      transition: color var(--press-out) var(--ease-out), opacity .6s var(--ease-out) calc(.35s + var(--i, 0) * 60ms);
   }

.core-motion.is-entered .core-subject.is-touch-pressed
   {
      transition-duration: var(--press-in);
   }

.core-motion.is-entered .core-rail
   {
      transition: opacity .6s var(--ease-out) .35s;
   }

.core-motion.is-entered .core-reading
   {
      transition: --sheet-reveal 1s var(--ease-out-cubic) .3s, translate 1s var(--ease-out-cubic) .3s;
   }

.core-motion.is-entered:not(.is-leaving) :is(.about-eyebrow, .about-title-outline)
   {
      transition: opacity .4s var(--ease-out);
   }

/* Leaving, in either direction, runs the entrance backwards and quicker, so
   the page is empty before it turns: the card wipes back down, the jobs
   fade last one first, the heading empties and the rule draws back in.
   These are transitions, so scrolling back mid-exit simply returns them. */
.core-motion.is-entered.is-leaving .core-reading
   {
      --sheet-reveal: 0;
      translate: 0 32px;
      transition: --sheet-reveal .3s var(--ease-out), translate .3s var(--ease-out);
   }

.core-motion.is-entered.is-leaving .core-subject
   {
      opacity: 0;
      transition: color var(--press-out) var(--ease-out), opacity .2s var(--ease-out) calc(.05s + (var(--n, 1) - 1 - var(--i, 0)) * 20ms);
   }

.core-motion.is-entered.is-leaving .core-rail
   {
      opacity: 0;
      transition: opacity .2s var(--ease-out) .08s;
   }

.core-motion.is-entered.is-leaving .about-title-line
   {
      --about-fill: 0;
      transition: --about-fill .28s var(--ease-out) .06s;
   }

.core-motion.is-entered.is-leaving .about-rule::after
   {
      transform: scaleX(0);
      transition: transform .28s var(--ease-out) .06s;
   }

.core-motion.is-entered.is-leaving :is(.about-eyebrow, .about-title-outline)
   {
      opacity: 0;
      transition: opacity .25s var(--ease-out) .06s;
   }

/* Education arrives each time the page turns to it: the rule draws, the
   heading fills, the sheets settle in one after the other, then the
   finished degree's title inks in from the left. Once it is off screen it resets out of sight.
   Script adds .edu-motion only when motion is allowed. The sheets settle
   from the side the page turns in from: below from Experience, above from
   Projects. */
.edu-motion:not(.is-entered) .about-rule::after
   {
      transform: scaleX(0);
   }

.edu-motion:not(.is-entered) .about-title-line
   {
      --about-fill: 0;
   }

.edu-motion:not(.is-entered) .edu-sheet
   {
      opacity: 0;
      transform: translateY(var(--edu-from, 16px));
   }

.edu-motion:not(.is-entered) .edu-title-ink
   {
      clip-path: inset(0 100% 0 0);
   }

.edu-motion:not(.is-entered) .edu-logo-fill
   {
      clip-path: inset(0 100% 0 0);
   }

.edu-motion.is-entered .about-rule::after
   {
      transition: transform 1.2s var(--ease-out) .1s;
   }

.edu-motion.is-entered .about-title-line
   {
      transition: --about-fill 1.6s var(--ease-out) .15s;
   }

.edu-motion.is-entered .edu-sheet
   {
      transition: opacity .8s var(--ease-out) .35s, transform .8s var(--ease-out) .35s;
   }

.edu-motion.is-entered .edu-sheet + .edu-sheet
   {
      transition-delay: .5s;
   }

.edu-motion.is-entered .edu-title-ink
   {
      transition: clip-path 1.4s var(--ease-out-quart) .95s;
   }

.edu-motion.is-entered .edu-logo-fill
   {
      transition: clip-path 1.6s var(--ease-out-quart) 1.1s;
   }

/* The light first crosses once the sheet has settled, then every 4.4s. */
.edu-motion.is-entered:not(.is-leaving) .edu-sheet-draft::after
   {
      animation: edu-shine 4.4s linear 1.6s infinite;
   }

/* Leaving, in either direction, runs the entrance backwards and quicker, as
   Experience does: the sheets drift off with the page, last one first, the
   heading empties and the rule draws back in. The ink stays put; it goes
   with its sheet and resets out of sight. */
.edu-motion.is-entered.is-leaving .edu-sheet
   {
      opacity: 0;
      transform: translateY(var(--edu-exit, 12px));
      transition: opacity .25s var(--ease-out) .05s, transform .25s var(--ease-out) .05s;
   }

.edu-motion.is-entered.is-leaving .edu-sheet + .edu-sheet
   {
      transition-delay: 0s;
   }

.edu-motion.is-entered.is-leaving .about-title-line
   {
      --about-fill: 0;
      transition: --about-fill .5s var(--ease-out) .12s;
   }

.edu-motion.is-entered.is-leaving .about-rule::after
   {
      transform: scaleX(0);
      transition: transform .4s var(--ease-out) .18s;
   }

/* An exit taken back returns at the exit's pace, not the first arrival's. */
.edu-motion.is-entered.is-resuming:not(.is-leaving) .edu-sheet
   {
      transition: opacity .3s var(--ease-out), transform .3s var(--ease-out);
   }

.edu-motion.is-entered.is-resuming:not(.is-leaving) .about-title-line
   {
      transition: --about-fill .5s var(--ease-out);
   }

.edu-motion.is-entered.is-resuming:not(.is-leaving) .about-rule::after
   {
      transition: transform .4s var(--ease-out);
   }

/* Skills: What I do. The panel follows meritfirst.us's "Real world
   assessments": a field of points beside the disciplines, inside one plain
   frame. The field is the hero and takes whatever the three 260px columns of
   cells leave, about 650px on a laptop; the cells stay compact. Script draws
   the frame to the panel's size; without it the panel keeps a plain border. */
/* Skills follows Projects under the same top bar. Script sets --skills-top
   to where the bar ends; Skills rests there, fills the screen down to the
   footer and centres the panel in it. It is the track's last snap point.
   Nothing follows it, so it carries the room above the footer as its own
   padding; without it the page ends short and Skills can't scroll up to
   the bar. */
.skills-main
   {
      display: flex;
      flex-direction: column;
      min-height: calc(100svh - var(--skills-top, 0px));
      padding-bottom: calc(var(--footer-bottom) + 48px);
      scroll-margin-top: var(--skills-top, 0px);
   }

html.projects-snapping .skills-main
   {
      scroll-snap-align: start;
   }

.skills-main:focus
   {
      outline: none;
   }

.craft-section
   {
      display: flex;
      flex: 1 0 auto;
      flex-direction: column;
      justify-content: center;
      padding-block: 24px;
   }

.craft-section .about-block-head
   {
      margin-bottom: 24px;
   }

.craft-section .about-heading
   {
      font-family: 'Google Sans', Arial, sans-serif;
      font-size: min(clamp(2.1rem, 4.6vw, 4.25rem), max(2.1rem, 7svh));
   }

.craft
   {
      --craft-line: rgba(255, 255, 255, .2);
      position: relative;
      display: grid;
      grid-template-columns: minmax(0, 1fr) repeat(3, 260px);
      grid-template-rows: repeat(3, 155px);
      color: var(--white);
   }

/* Beside the field, the panel takes the height the screen has left, so a
   tall window gives the points more room instead of empty space above the
   heading. The rows never drop below 155px and stop growing at 240px. */
@media (min-width: 1181px)
   {
      .craft
         {
            flex: 1 1 auto;
            max-height: 720px;
            grid-template-rows: repeat(3, minmax(155px, 1fr));
         }
   }

.craft:not(.has-frame)
   {
      border: 1px solid var(--craft-line);
   }

.craft-frame
   {
      position: absolute;
      inset: 0;
      width: 100%;
      height: 100%;
      overflow: visible;
      pointer-events: none;
      fill: none;
      stroke: var(--craft-line);
      stroke-width: 1;
   }

.craft-frame path
   {
      stroke-dasharray: 1 1;
      stroke-dashoffset: 0;
   }

.craft-field
   {
      position: relative;
      grid-area: 1 / 1 / 4 / 2;
      min-width: 0;
      margin: 0;
      overflow: hidden;
   }

/* A soft pool of light under the points, so the shape reads as lit. */
.craft-field::after
   {
      content: '';
      position: absolute;
      inset: 0;
      background: radial-gradient(closest-side, rgba(255, 255, 255, .07), transparent 70%);
      pointer-events: none;
   }

/* A faint graph-paper grid under the points, fading out toward the edges. */
.craft-field::before
   {
      content: '';
      position: absolute;
      inset: 0;
      background-image: linear-gradient(rgba(255, 255, 255, .045) 1px, transparent 1px), linear-gradient(90deg, rgba(255, 255, 255, .045) 1px, transparent 1px);
      background-position: center;
      background-size: 48px 48px;
      -webkit-mask-image: radial-gradient(closest-side, #000 35%, transparent);
      mask-image: radial-gradient(closest-side, #000 35%, transparent);
   }

.craft-canvas
   {
      position: absolute;
      inset: 0;
      z-index: 1;
      display: block;
      width: 100%;
      height: 100%;
   }

/* The field names what it is showing at the top and lists its tools at the
   foot. A new name rises in as the old one lifts away; the tools follow it
   in one by one. At rest, on the sheet, both stand empty. */
.craft-now
   {
      position: absolute;
      top: 26px;
      right: 0;
      left: 0;
      z-index: 2;
      height: 40px;
      margin: 0;
      pointer-events: none;
   }

.craft-now span
   {
      position: absolute;
      inset: 0;
      color: var(--white);
      font-family: 'Google Sans', Arial, Helvetica, sans-serif;
      font-size: 30px;
      font-weight: 300;
      letter-spacing: -.04em;
      line-height: 40px;
      text-align: center;
      transition: opacity .5s var(--ease-out), transform .7s var(--ease-out-quart);
   }

.craft-now span.is-before
   {
      opacity: 0;
      transform: translateY(14px);
   }

.craft-now span.is-after
   {
      opacity: 0;
      transform: translateY(-14px);
      transition-duration: .25s;
   }

.craft-tools
   {
      position: absolute;
      bottom: 24px;
      left: 50%;
      z-index: 2;
      display: flex;
      align-items: center;
      gap: 16px;
      transform: translateX(-50%);
      opacity: 0;
      transition: opacity .3s var(--ease-out);
   }

.craft-field.has-tools .craft-tools
   {
      opacity: 1;
   }

.craft-tools-label
   {
      color: rgba(255, 255, 255, .6);
      font-family: 'Geist Mono', monospace;
      font-size: 11px;
      font-weight: 300;
      letter-spacing: .14em;
      line-height: 1;
      text-transform: uppercase;
   }

.craft-sprite
   {
      position: absolute;
      width: 0;
      height: 0;
      overflow: hidden;
   }

.craft-tools-set
   {
      --tai-lilac: currentColor;
      --tai-pink: currentColor;
      --tai-orange: currentColor;
      display: flex;
      gap: 10px;
      margin: 0;
      padding: 0;
      color: rgba(255, 255, 255, .9);
      list-style: none;
   }

.craft-tools-set[hidden]
   {
      display: none;
   }

/* Keep longer tool lists inside the field at every width. */
.craft-tools:has(:is([data-shape="server"], [data-shape="backend"], [data-shape="frontend"]):not([hidden]))
   {
      width: calc(100% - 40px);
      max-width: 410px;
   }

.craft-tools-set:is([data-shape="server"], [data-shape="backend"], [data-shape="frontend"])
   {
      flex: 1;
      min-width: 0;
      flex-wrap: wrap;
      justify-content: center;
      row-gap: 12px;
   }

.craft-tools-set:is([data-shape="server"], [data-shape="backend"], [data-shape="frontend"]) li
   {
      flex-shrink: 0;
   }

.craft-tools-set img
   {
      display: block;
      width: 100%;
      height: 100%;
      object-fit: contain;
   }

.craft-tools-set li:has([href="#icon-powershell"])
   {
      --brand: #5391fe;
   }

.craft-tools-set li:has([href="#icon-gnubash"])
   {
      --brand: #8fcf48;
   }

.craft-tools-set li
   {
      display: block;
      width: 22px;
      height: 22px;
      transition: opacity .45s var(--ease-out) calc(.12s + var(--k, 0) * 50ms), transform .55s var(--ease-out-quart) calc(.12s + var(--k, 0) * 50ms);
   }

.craft-tools-set li.is-before
   {
      opacity: 0;
      transform: translateY(8px);
   }

.craft-tools-set svg
   {
      display: block;
      width: 100%;
      height: 100%;
      fill: var(--brand, currentColor);
   }

/* Multicolor tools use local original-color SVG images.
   Monochrome brands use their white marks on this dark surface. */
.craft-tools-set li:has([href="#icon-react"])
   {
      --brand: #61dafb;
   }

.craft-tools-set li:has([href="#icon-javascript"])
   {
      --brand: #f7df1e;
   }

.craft-tools-set li:has([href="#icon-html5"])
   {
      --brand: #e34f26;
   }

.craft-tools-set li:has([href="#icon-css"])
   {
      --brand: #2f9be8;
   }

.craft-tools-set li:has([href="#icon-vite"])
   {
      --brand: #9e75ff;
   }

.craft-tools-set li:has([href="#icon-tailwindcss"])
   {
      --brand: #06b6d4;
   }

.craft-tools-set li:has([href="#icon-bootstrap"])
   {
      --brand: #9b72dd;
   }

.craft-tools-set li:has([href="#icon-swift"])
   {
      --brand: #f05138;
   }

.craft-tools-set li:has([href="#icon-electron"])
   {
      --brand: #9feaf9;
   }

.craft-tools-set li:has([href="#icon-nodedotjs"])
   {
      --brand: #5fa04e;
   }

.craft-tools-set li:has([href="#icon-springboot"])
   {
      --brand: #6db33f;
   }

.craft-tools-set li:has([href="#icon-postgresql"])
   {
      --brand: #699eca;
   }

.craft-tools-set li:has([href="#icon-sqlite"])
   {
      --brand: #44a8e0;
   }

.craft-tools-set li:has([href="#icon-togetherai"])
   {
      --tai-lilac: #caaef5;
      --tai-pink: #ef2cc1;
      --tai-orange: #fc4c02;
   }

.craft-tools-set li:has([href="#icon-python"])
   {
      --brand: #3776ab;
   }

.craft-tools-set li:has([href="#icon-scikitlearn"])
   {
      --brand: #f7931e;
   }

.craft-tools-set li:has([href="#icon-raspberrypi"])
   {
      --brand: #c51a4a;
   }

.craft-tools-set li:has([href="#icon-git"])
   {
      --brand: #f05032;
   }

.craft-tools-set li:has([href="#icon-windows"])
   {
      --brand: #4cc2ff;
   }

@media (max-width: 700px)
   {
      .craft-now
         {
            top: 16px;
         }

      .craft-now span
         {
            font-size: 24px;
         }

      .craft-tools
         {
            bottom: 16px;
            gap: 12px;
         }

      .craft-tools-set
         {
            gap: 12px;
         }

      .craft-tools-set li
         {
            width: 18px;
            height: 18px;
         }
   }

/* Each cell draws its own hairlines: --l shows the left one, --t the top.
   The meter on the left edge fills while that cell's shape holds the field. */
.craft-cell
   {
      --l: 1;
      --t: 0;
      position: relative;
      display: flex;
      flex-direction: column;
      justify-content: space-between;
      min-width: 0;
      margin: 0;
      padding: 22px 22px 20px;
   }

.craft-cell
   {
      justify-content: flex-start;
      gap: 10px;
   }

.craft-cell::before
   {
      content: '';
      position: absolute;
      top: 0;
      bottom: 0;
      left: 0;
      width: 1px;
      background: var(--craft-line);
      opacity: var(--l);
      transform-origin: top;
   }

.craft-cell::after
   {
      content: '';
      position: absolute;
      top: 0;
      right: 0;
      left: 0;
      height: 1px;
      background: var(--craft-line);
      opacity: var(--t);
      transform-origin: left;
   }

.craft-cell:nth-of-type(n + 4)
   {
      --t: 1;
   }

.craft-meter
   {
      position: absolute;
      top: 0;
      bottom: 0;
      left: 0;
      z-index: 1;
      width: 1px;
      background: var(--white);
      transform: scaleY(0);
      transform-origin: top;
      pointer-events: none;
   }

.craft-cell.is-lit .craft-meter
   {
      animation: craft-meter var(--dwell, 3.6s) linear forwards;
   }

.craft-cell.is-held .craft-meter
   {
      transform: none;
      transition: transform .5s var(--ease-out);
   }

@keyframes craft-meter
   {
      to
         {
            transform: none;
         }
   }

.craft-text
   {
      margin: 0;
      color: rgba(255, 255, 255, .78);
      font-family: 'Google Sans', Arial, Helvetica, sans-serif;
      font-size: 16px;
      font-weight: 400;
      line-height: 1.45;
      text-wrap: pretty;
      transition: color .45s var(--ease-out);
   }

.craft-cell:is(.is-lit, .is-held) .craft-text
   {
      color: var(--white);
   }

/* The discipline's name heads its cell, beside its mark. */
.craft-name
   {
      display: flex;
      align-items: center;
      gap: 10px;
      margin: 0;
      text-wrap: balance;
      color: rgba(255, 255, 255, .7);
      font-family: 'Geist Mono', monospace;
      font-size: 11px;
      font-weight: 300;
      letter-spacing: .14em;
      line-height: 1.5;
      text-transform: uppercase;
      transition: color .45s var(--ease-out);
   }

/* The discipline's mark, drawn by script from the same shape the points
   take in the field. */
.craft-mark
   {
      flex: none;
      width: 20px;
      height: 20px;
      opacity: .8;
      transition: opacity .45s var(--ease-out);
   }

.craft-cell:is(.is-lit, .is-held) .craft-mark
   {
      opacity: 1;
   }

.craft-cell:is(.is-lit, .is-held) .craft-name
   {
      color: var(--white);
   }

/* Below that the field goes across the top with the cells three to a row
   beneath it. */
@media (max-width: 1180px)
   {
      .craft
         {
            grid-template-columns: repeat(3, minmax(0, 1fr));
            grid-template-rows: 320px;
            grid-auto-rows: 155px;
         }

      .craft-field
         {
            grid-area: 1 / 1 / 2 / -1;
         }

      .craft-cell, .craft-cell:nth-of-type(n)
         {
            --t: 1;
         }

      .craft-cell:nth-of-type(3n + 1)
         {
            --l: 0;
         }
   }

/* Phones stack everything under a shorter field. */
@media (max-width: 700px)
   {
      .craft-section
         {
            justify-content: flex-start;
         }

      .craft
         {
            grid-template-columns: minmax(0, 1fr);
            grid-template-rows: 260px;
            grid-auto-rows: auto;
         }

      .craft-cell, .craft-cell:nth-of-type(n)
         {
            --l: 0;
            --t: 1;
            gap: 24px;
            padding: 24px 20px;
         }
   }

/* Skills arrives once it is on screen: the rule draws, the heading fills,
   the frame traces itself round from its top-left corner, the hairlines
   grow, and the copy rises in cell by cell while the points gather into
   their sheet. Script adds .craft-motion only when motion is allowed. */
.craft-motion:not(.is-entered) .about-rule::after
   {
      transform: scaleX(0);
   }

.craft-motion:not(.is-entered) .about-title-line
   {
      --about-fill: 0;
   }

.craft-motion:not(.is-entered) :is(.about-eyebrow, .about-title-outline)
   {
      opacity: 0;
   }

.craft-motion:not(.is-entered) .craft-frame path
   {
      stroke-dashoffset: 1;
   }

.craft-motion:not(.is-entered) .craft-cell::before
   {
      transform: scaleY(0);
   }

.craft-motion:not(.is-entered) .craft-cell::after
   {
      transform: scaleX(0);
   }

.craft-motion:not(.is-entered) .craft-copy
   {
      opacity: 0;
      transform: translateY(14px);
   }

.craft-motion:not(.is-entered) .craft-field
   {
      opacity: 0;
   }

.craft-motion.is-entered .about-rule::after
   {
      transition: transform 1.2s var(--ease-out) .1s;
   }

.craft-motion.is-entered .about-title-line
   {
      transition: --about-fill 1.6s var(--ease-out) .15s;
   }

.craft-motion.is-entered:not(.is-leaving) :is(.about-eyebrow, .about-title-outline)
   {
      transition: opacity .4s var(--ease-out);
   }

.craft-motion.is-entered .craft-frame path
   {
      transition: stroke-dashoffset 1.5s var(--ease-in-out-cubic) .15s;
   }

.craft-motion.is-entered .craft-cell::before, .craft-motion.is-entered .craft-cell::after
   {
      transition: transform .9s var(--ease-out-quart) calc(.55s + var(--i, 0) * 90ms);
   }

.craft-motion.is-entered .craft-copy
   {
      transition: opacity .7s var(--ease-out) calc(.7s + var(--i, 0) * 90ms), transform .7s var(--ease-out) calc(.7s + var(--i, 0) * 90ms), color .45s var(--ease-out);
   }

.craft-motion.is-entered .craft-name
   {
      transition: opacity .7s var(--ease-out) calc(.85s + var(--i, 0) * 90ms), transform .7s var(--ease-out) calc(.85s + var(--i, 0) * 90ms), color .45s var(--ease-out);
   }

.craft-motion.is-entered .craft-field
   {
      transition: opacity .8s var(--ease-out) .3s;
   }

.craft-motion.is-entered.is-leaving .about-title-line
   {
      --about-fill: 0;
      transition: --about-fill .28s var(--ease-out) .06s;
   }

.craft-motion.is-entered.is-leaving .about-rule::after
   {
      transform: scaleX(0);
      transition: transform .28s var(--ease-out) .06s;
   }

.craft-motion.is-entered.is-leaving :is(.about-eyebrow, .about-title-outline)
   {
      opacity: 0;
      transition: opacity .25s var(--ease-out) .06s;
   }

.craft-motion.is-entered.is-leaving .craft-frame path
   {
      stroke-dashoffset: 1;
      transition: stroke-dashoffset .34s var(--ease-in-out-cubic);
   }

.craft-motion.is-entered.is-leaving .craft-cell::before
   {
      transform: scaleY(0);
      transition: transform .26s var(--ease-out);
   }

.craft-motion.is-entered.is-leaving .craft-cell::after
   {
      transform: scaleX(0);
      transition: transform .26s var(--ease-out);
   }

/* Skills only ever turns back up to Projects, so its copy drops as it goes,
   with the page, as Education's does when it turns up. */
.craft-motion.is-entered.is-leaving .craft-copy
   {
      opacity: 0;
      transform: translateY(10px);
      transition: opacity .2s var(--ease-out), transform .2s var(--ease-out);
   }

.craft-motion.is-entered.is-leaving .craft-field
   {
      opacity: 0;
      transition: opacity .26s var(--ease-out);
   }

/* Turning on down to Let's connect, the copy lifts with the page instead. */
.craft-motion.is-entered.is-leaving.is-leaving-down .craft-copy
   {
      transform: translateY(-10px);
   }

/* Skills' own footer sits on the foot of its screen, on the same insets as
   the page's footer, which now follows Let's connect. */
.skills-main
   {
      position: relative;
   }

.about-footer-skills
   {
      left: calc(max(var(--edge), env(safe-area-inset-left)) - var(--page-gutter));
      right: calc(max(var(--edge), env(safe-area-inset-right)) - var(--page-gutter));
   }

/* Skills ends just above Let's connect's screen, where the footer would show
   through the top bar, so it is shown only while Skills is the page. */
.about-footer-skills
   {
      transition: opacity .4s var(--ease-out);
   }

.skills-main:has(.craft-motion:not(.is-entered)) .about-footer-skills
   {
      opacity: 0;
      transition-duration: 0s;
   }

/* Let's connect follows Skills and rests the same way: against the bar, one
   screen tall, its greeting centred in the room above the footer. */
.connect-main
   {
      display: flex;
      flex-direction: column;
      min-height: calc(100svh - var(--skills-top, 0px));
      padding-bottom: calc(var(--footer-bottom) + 48px);
      scroll-margin-top: var(--skills-top, 0px);
   }

html.projects-snapping .connect-main
   {
      scroll-snap-align: start;
   }

.connect-main:focus
   {
      outline: none;
   }

.connect-section
   {
      display: flex;
      flex: 1 0 auto;
      flex-direction: column;
      justify-content: center;
      padding-block: 24px;
   }

.connect-section .about-block-head
   {
      gap: 32px;
      margin-bottom: 0;
   }

.connect-title
   {
      margin: 0;
      font-weight: 400;
   }

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

/* The greeting is the heading. Every word shares one grid cell on a common
   baseline, so the cell is always the widest and tallest of them and a cut
   never moves anything else; only the current word is visible. Each word has
   its own face. A change runs letter by letter: the old word's letters lift
   and fade in quick succession while the new word's rise into place a beat
   behind them, a little slower, so the cell is never empty. Each letter
   sharpens out of a slight blur as it lands and softens into one as it goes. Offsets are in em
   so every face moves in proportion to its size. Nothing clips the letters,
   so descenders survive; the fade does the hiding. */
.greeting
   {
      --greeting-scale: 1.2;
      display: flex;
      margin: 0;
      padding: 0;
      border: 0;
      background: none;
      color: var(--white);
      font: inherit;
      text-align: left;
      cursor: pointer;
      touch-action: manipulation;
      -webkit-tap-highlight-color: transparent;
   }

.greeting:focus-visible
   {
      outline: 2px solid currentColor;
      outline-offset: 12px;
   }

.greeting-words
   {
      display: grid;
      align-items: baseline;
   }

.greeting-word
   {
      grid-area: 1 / 1;
      justify-self: start;
      line-height: 1;
      white-space: nowrap;
      visibility: hidden;
      transition: visibility 0s linear 400ms;
   }

.greeting-word.is-current
   {
      visibility: visible;
      transition: visibility 0s;
   }

.greeting-letter
   {
      display: inline-block;
      opacity: 0;
      transform: translateY(.35em);
      filter: blur(6px);
      transition: opacity 240ms var(--ease-in-out), transform 240ms var(--ease-in-out), filter 240ms var(--ease-in-out);
      transition-delay: calc(var(--i) * 14ms);
   }

.is-past > .greeting-letter
   {
      transform: translateY(-.35em);
   }

.is-current > .greeting-letter
   {
      opacity: 1;
      transform: none;
      filter: none;
      transition-duration: 460ms;
      transition-timing-function: var(--ease-out);
      transition-delay: calc(80ms + var(--i) * 22ms);
   }

.greeting-word[data-face="hello"]
   {
      font-family: 'Instrument Serif', Georgia, serif;
      font-size: calc(280px * var(--greeting-scale));
      letter-spacing: -.02em;
   }

.greeting-word[data-face="wassup"]
   {
      color: transparent;
      -webkit-text-stroke: .018em var(--white);
      font-family: 'Unbounded Outline', Arial, sans-serif;
      font-size: calc(180px * var(--greeting-scale));
      font-weight: 700;
      letter-spacing: -.04em;
      text-transform: uppercase;
   }

.greeting-word[data-face="gyebale"]
   {
      font-family: 'Bounded', Arial, sans-serif;
      font-size: calc(190px * var(--greeting-scale));
      font-weight: 200;
      letter-spacing: -.06em;
      text-transform: uppercase;
   }

.greeting-word[data-face="bwanji"]
   {
      font-family: 'Bounded', Arial, sans-serif;
      font-size: calc(220px * var(--greeting-scale));
      font-weight: 800;
      letter-spacing: -.035em;
   }

.greeting-word[data-face="jambo"]
   {
      font-family: 'Haval', Arial, sans-serif;
      font-size: calc(200px * var(--greeting-scale));
      letter-spacing: -.02em;
   }

.greeting-word[data-face="amakuru"]
   {
      font-family: 'Geist Mono', monospace;
      font-size: calc(200px * var(--greeting-scale));
      font-weight: 300;
      letter-spacing: -.04em;
   }

.greeting-word[data-face="namaste"]
   {
      font-family: 'Google Sans', Arial, sans-serif;
      font-size: calc(220px * var(--greeting-scale));
      letter-spacing: -.04em;
   }

.greeting-word[data-face="hola"]
   {
      font-family: 'Unbounded', Arial, sans-serif;
      font-size: calc(200px * var(--greeting-scale));
      font-weight: 900;
      letter-spacing: -.04em;
   }

.greeting-word[data-face="konnichiwa"]
   {
      font-family: 'Instrument Serif', Georgia, serif;
      font-size: calc(240px * var(--greeting-scale));
      letter-spacing: -.02em;
   }

.greeting-word[data-face="suosdey"]
   {
      font-family: 'Geist Mono', monospace;
      font-size: calc(190px * var(--greeting-scale));
      font-weight: 300;
      letter-spacing: -.04em;
      text-transform: uppercase;
   }

.greeting-word[data-face="bula"]
   {
      font-family: 'Haval', Arial, sans-serif;
      font-size: calc(220px * var(--greeting-scale));
      letter-spacing: -.02em;
      text-transform: uppercase;
   }

/* Optical left edge: each face's first letter carries its own side bearing,
   which at display size reads as an indent under the lead line. Each word is
   pulled left by that letter's measured bearing (from the font files: the
   glyph's xMin), so the ink, the lead line and the button share one edge.
   WASSUP's outline stroke already reaches past its bearing, so it needs
   almost none. */
.greeting-word
   {
      margin-left: calc(var(--ink-start, 0) * -1em);
   }

.greeting-word[data-face="gyebale"]
   {
      --ink-start: .052;
   }

.greeting-word[data-face="wassup"]
   {
      --ink-start: .001;
   }

.greeting-word[data-face="hello"]
   {
      --ink-start: .019;
   }

.greeting-word[data-face="jambo"]
   {
      --ink-start: .06;
   }

.greeting-word[data-face="bwanji"]
   {
      --ink-start: .0436;
   }

.greeting-word[data-face="amakuru"]
   {
      --ink-start: .0333;
   }

.greeting-word[data-face="namaste"]
   {
      --ink-start: .079;
   }

.greeting-word[data-face="hola"]
   {
      --ink-start: .06;
   }

.greeting-word[data-face="konnichiwa"]
   {
      --ink-start: .019;
   }

.greeting-word[data-face="suosdey"]
   {
      --ink-start: .0487;
   }

.greeting-word[data-face="bula"]
   {
      --ink-start: .08;
   }

/* The word's flag changes in place at the end of the eyebrow's rule, all of
   them stacked in one cell like the words. The cell is a fixed frame: its
   hairline and tint never move or fade with the flags, so the rectangle
   holds its place through every change, even before the first flag arrives.
   The next flag wipes up over the last one inside it, on the letters' own
   timing (80ms in, 460ms out), so they rise together and two flags never
   show through each other. The last flag stays whole underneath until it is
   covered. */
.greeting-mark
   {
      display: flex;
      align-items: center;
      gap: 14px;
   }

.greeting-flags
   {
      position: relative;
      display: grid;
      width: 32px;
      height: 24px;
      overflow: hidden;
      border-radius: 2px;
      background: rgba(255, 255, 255, .06);
      isolation: isolate;
   }

.greeting-flags::after
   {
      content: "";
      position: absolute;
      inset: 0;
      z-index: 2;
      border-radius: inherit;
      box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .18);
      pointer-events: none;
   }

.greeting-flag
   {
      grid-area: 1 / 1;
      display: block;
      width: 32px;
      height: 24px;
      z-index: 0;
      visibility: hidden;
      clip-path: inset(100% 0 0 0);
      transition: clip-path 0s linear 600ms, visibility 0s linear 600ms;
   }

.greeting-flag.is-current
   {
      z-index: 1;
      visibility: visible;
      clip-path: inset(0 0 0 0);
      transition: clip-path 460ms var(--ease-out) 80ms, visibility 0s;
   }

/* Reduced motion keeps the change soft: opacity only, nothing moves. The new
   flag fades in over the last, which stays whole until it is covered. */
@media (prefers-reduced-motion: reduce)
   {
      .greeting-letter
         {
            transform: none !important;
            filter: none !important;
            transition: opacity 200ms var(--ease-out), visibility 0s linear 200ms !important;
         }

      .greeting-word
         {
            transition: visibility 0s linear 200ms !important;
         }

      .greeting-word.is-current
         {
            transition: opacity 200ms var(--ease-out), visibility 0s !important;
         }

      .greeting-flag
         {
            clip-path: none !important;
            opacity: 0;
            transition: opacity 0s linear 200ms, visibility 0s linear 200ms !important;
         }

      .greeting-flag.is-current
         {
            opacity: 1;
            transition: opacity 200ms var(--ease-out), visibility 0s !important;
         }
   }

/* Held on a word, the language is named under it, in the eyebrow's own
   voice. It hangs below the heading out of flow, so showing it never moves
   the button beneath. */
.connect-title
   {
      position: relative;
   }

.greeting-language
   {
      position: absolute;
      top: 100%;
      left: 0;
      margin-top: 8px;
      color: var(--white);
      font-family: 'Geist Mono', monospace;
      font-size: 11px;
      font-weight: 300;
      letter-spacing: .3em;
      line-height: 1.5;
      text-transform: uppercase;
      opacity: 0;
      white-space: nowrap;
      transition: opacity var(--press-out) var(--ease-out);
   }

.connect-section:has(.greeting.is-paused) .greeting-language
   {
      opacity: .7;
   }

@media (max-width: 1100px)
   {
      .greeting
         {
            --greeting-scale: .84;
         }
   }

@media (max-width: 700px)
   {
      .greeting
         {
            --greeting-scale: .48;
         }
   }

/* Let's connect arrives as Skills does: the rule draws and the eyebrow fades
   in, then the greeting cuts in on its first word. Leaving, the greeting cuts
   out at once and the rule withdraws. Script adds .connect-motion only when
   motion is allowed. */
.connect-motion:not(.is-entered) .about-rule::after
   {
      transform: scaleX(0);
   }

.connect-motion:not(.is-entered) .about-eyebrow
   {
      opacity: 0;
   }

.connect-motion.is-entered .about-rule::after
   {
      transition: transform 1.2s var(--ease-out) .1s;
   }

.connect-motion.is-entered:not(.is-leaving) .about-eyebrow
   {
      transition: opacity .4s var(--ease-out);
   }

.connect-motion.is-entered.is-leaving .about-rule::after
   {
      transform: scaleX(0);
      transition: transform .28s var(--ease-out) .06s;
   }

.connect-motion.is-entered.is-leaving .about-eyebrow
   {
      opacity: 0;
      transition: opacity .25s var(--ease-out) .06s;
   }

.connect-motion.is-leaving .greeting
   {
      opacity: 0;
   }

.connect-motion:not(.is-entered) :is(.connect-compose, .connect-elsewhere)
   {
      opacity: 0;
   }

.connect-motion.is-entered:not(.is-leaving) :is(.connect-compose, .connect-elsewhere)
   {
      transition: opacity .5s var(--ease-out) .35s;
   }

.connect-motion.is-entered.is-leaving :is(.connect-compose, .connect-elsewhere)
   {
      opacity: 0;
      transition: opacity .2s var(--ease-out);
   }

/* The greeting and the contact rail share a row: the rail keeps to the right
   edge, centred on the greeting, so the longest word still clears it. */
.connect-body
   {
      display: grid;
      grid-template-columns: minmax(0, 1fr) auto;
      align-items: center;
      gap: 48px;
   }

/* At rest the stage shows the greeting with its answer beneath it on the
   word's left edge: the message button, long and set in the serif the form's
   fields use, so it already speaks like the form it opens. */
.connect-hello
   {
      display: flex;
      flex-direction: column;
      align-items: flex-start;
      gap: 32px;
   }

/* The line the greeting finishes: it reads straight into the word, so it sits
   close above it in the softer serif voice the rail's caption uses. */
/* Its D's side bearing (.019em) is pulled in the same way. */
.connect-lead
   {
      margin: 0 0 -20px -.019em;
      color: rgba(255, 255, 255, .62);
      font-family: 'Instrument Serif', Georgia, serif;
      font-size: 36px;
      line-height: 1.2;
      letter-spacing: -.01em;
   }

.connect-section.is-composing .connect-lead
   {
      visibility: hidden;
   }

/* On arrival the line rises in word by word, 45ms apart, before the greeting
   cuts in to finish it; leaving, the words lift out quickly. Script splits the
   words and adds .connect-motion only when motion is allowed. */
.connect-lead-word
   {
      display: inline-block;
   }

.connect-motion .connect-lead-word
   {
      opacity: 0;
      transform: translateY(.4em);
   }

.connect-motion.is-entered:not(.is-leaving) .connect-lead-word
   {
      opacity: 1;
      transform: none;
      transition: opacity 460ms var(--ease-out) calc(var(--i, 0) * 45ms), transform 460ms var(--ease-out) calc(var(--i, 0) * 45ms);
   }

.connect-motion.is-entered.is-leaving .connect-lead-word
   {
      opacity: 0;
      transform: translateY(-.25em);
      transition: opacity 200ms var(--ease-out), transform 200ms var(--ease-out);
   }

.connect-compose
   {
      flex: none;
      width: 400px;
      min-height: 64px;
      border: 0;
      font-family: 'Instrument Serif', Georgia, serif;
      font-size: 26px;
      font-weight: 400;
      letter-spacing: -.005em;
      cursor: pointer;
   }

.connect-compose::before
   {
      width: 60px;
   }

.connect-compose .button-label
   {
      padding-left: 62px;
      line-height: 1.2;
   }

.connect-compose .button-arrow
   {
      width: 60px;
   }

.connect-compose .button-arrow svg
   {
      width: 20px;
      height: 20px;
   }

.connect-compose:focus-visible .button-label
   {
      padding-right: 52px;
   }

.connect-compose:focus-visible .button-arrow
   {
      left: calc(100% - 62px);
   }

@media (hover: hover) and (pointer: fine)
   {
      :root:not(.touch-input) .connect-compose:hover .button-label
         {
            padding-right: 52px;
         }

      :root:not(.touch-input) .connect-compose:hover .button-arrow
         {
            left: calc(100% - 62px);
         }
   }

.connect-section.is-composing .connect-compose
   {
      visibility: hidden;
   }

/* The caption sits over the marks, right-aligned to them, and takes no width
   of its own, so the rail stays as narrow as its marks and the longest word
   still clears it. */
.connect-elsewhere
   {
      position: relative;
   }

.connect-elsewhere-caption
   {
      position: absolute;
      right: 8px;
      bottom: calc(100% + 14px);
      margin: 0;
      color: rgba(255, 255, 255, .6);
      font-family: 'Instrument Serif', Georgia, serif;
      font-size: 20px;
      line-height: 1.15;
      letter-spacing: -.005em;
      text-align: right;
      white-space: nowrap;
   }

/* Four marks, the same ones Home uses, larger. Under a fine pointer or
   keyboard focus a mark slides left by its handle's width (script measures it
   into --shift) and the handle wipes in behind it, on its right, ending on the
   rail's edge. The other marks step back. Touch relies on the marks
   and their accessible names. */
.connect-rail
   {
      display: flex;
      flex-direction: column;
      gap: 6px;
      margin: 0;
      padding: 0;
      list-style: none;
   }

.connect-rail-link
   {
      position: relative;
      display: grid;
      place-items: center;
      width: 56px;
      height: 56px;
      color: var(--white);
      touch-action: manipulation;
   }

.connect-rail-icon
   {
      width: 28px;
      height: 28px;
      opacity: .7;
      overflow: visible;
      transition: opacity .2s var(--ease-out), transform var(--press-out) var(--ease-out), translate .32s var(--ease-out);
   }

.connect-rail-name
   {
      position: absolute;
      right: 0;
      top: 50%;
      color: var(--white);
      font-family: 'Geist Mono', monospace;
      font-size: 11px;
      font-weight: 300;
      letter-spacing: .14em;
      line-height: 1;
      text-transform: uppercase;
      white-space: nowrap;
      opacity: 0;
      clip-path: inset(0 100% 0 0);
      transform: translateY(-50%);
      pointer-events: none;
      transition: clip-path .16s var(--ease-out), opacity .16s var(--ease-out);
   }

.connect-rail-link:focus-visible .connect-rail-name
   {
      opacity: 1;
      clip-path: inset(0);
      transition: clip-path .24s var(--ease-out), opacity .12s var(--ease-out);
   }

/* Real handles keep their own capitals. */
.connect-handle .connect-rail-name
   {
      font-size: 13px;
      letter-spacing: .02em;
      text-transform: none;
   }

.connect-rail-link:focus-visible
   {
      outline: 2px solid currentColor;
      outline-offset: -2px;
   }

.connect-rail-link:focus-visible .connect-rail-icon
   {
      opacity: 1;
      translate: calc(var(--shift, 0px) * -1) 0;
   }

@media (hover: hover) and (pointer: fine)
   {
      :root:not(.touch-input) .connect-rail:has(.connect-rail-link:hover) .connect-rail-icon
         {
            opacity: .35;
         }

      :root:not(.touch-input) .connect-rail:has(.connect-rail-link:hover) .connect-rail-link:hover .connect-rail-icon
         {
            opacity: 1;
         }

      :root:not(.touch-input) .connect-rail-link:hover .connect-rail-icon
         {
            translate: calc(var(--shift, 0px) * -1) 0;
         }

      :root:not(.touch-input) .connect-rail-link:hover .connect-rail-name
         {
            opacity: 1;
            clip-path: inset(0);
            transition: clip-path .24s var(--ease-out), opacity .12s var(--ease-out);
         }

      :root:not(.touch-input) .connect-rail-link:active .connect-rail-icon
         {
            transform: scale(.94);
            transition-duration: var(--press-in);
         }
   }

.connect-rail-link.is-touch-pressed .connect-rail-icon
   {
      opacity: 1;
      transform: scale(.94);
      transition-duration: var(--press-in);
   }

.connect-label
   {
      margin: 0;
      color: rgba(255, 255, 255, .55);
      font-family: 'Geist Mono', monospace;
      font-size: 11px;
      font-weight: 300;
      letter-spacing: .18em;
      line-height: 1.4;
      text-transform: uppercase;
   }

@media (prefers-reduced-motion: reduce)
   {
      .connect-rail-name, .connect-rail-link:is(:hover, :focus-visible) .connect-rail-name
         {
            clip-path: none !important;
            transition: opacity .15s var(--ease-out) !important;
         }

      .connect-rail-link:active .connect-rail-icon
         {
            transform: none !important;
         }

      .connect-rail-icon
         {
            transition: opacity .2s var(--ease-out) !important;
         }

      .connect-rail-link.is-touch-pressed .connect-rail-icon
         {
            transform: none;
         }
   }

/* The message form shares the greeting's cell. Opening it cuts the greeting
   (and its flag) out and lets the fields rise in one after another, each
   hairline drawing on from the left; closing cuts the greeting back in on
   the word it left. Fields are large serif text on hairlines, labelled in the
   page's own label voice. */
.connect-stage
   {
      display: grid;
   }

.connect-stage > *
   {
      grid-area: 1 / 1;
      min-width: 0;
   }

.connect-form
   {
      display: flex;
      flex-direction: column;
      justify-content: center;
      gap: 20px;
      visibility: hidden;
   }

.connect-section.is-composing .connect-form
   {
      visibility: visible;
   }

.connect-section.is-composing :is(.greeting-word.is-current, .greeting-flag.is-current)
   {
      visibility: hidden;
   }

.connect-section.is-composing .greeting-language
   {
      opacity: 0;
   }

.connect-botcheck
   {
      position: absolute;
      width: 1px;
      height: 1px;
      opacity: 0;
      pointer-events: none;
   }

.connect-fields
   {
      display: grid;
      grid-template-columns: repeat(2, minmax(0, 1fr));
      gap: 20px 40px;
   }

.connect-field
   {
      display: flex;
      flex-direction: column;
      gap: 6px;
      min-width: 0;
   }

.connect-field-message
   {
      grid-column: 1 / -1;
   }

.connect-input
   {
      width: 100%;
      min-height: 48px;
      margin: 0;
      padding: 4px 0 8px;
      border: 0;
      border-radius: 0;
      background: linear-gradient(var(--white), var(--white)) 0 100% / 0 2px no-repeat;
      box-shadow: inset 0 -1px 0 rgba(255, 255, 255, .28);
      color: var(--white);
      font-family: 'Instrument Serif', Georgia, serif;
      font-size: 30px;
      line-height: 1.2;
      letter-spacing: -.01em;
      caret-color: var(--white);
      transition: background-size .45s var(--ease-out);
   }

textarea.connect-input
   {
      display: block;
      resize: none;
   }

.connect-input:focus
   {
      outline: none;
      background-size: 100% 2px;
   }

.connect-input[aria-invalid="true"]
   {
      box-shadow: inset 0 -1px 0 var(--error);
   }

.connect-error
   {
      min-height: 16px;
      margin: 0;
      color: var(--error);
      font-family: 'Google Sans', Arial, sans-serif;
      font-size: 13px;
      line-height: 1.25;
   }

.connect-form-actions
   {
      display: flex;
      align-items: center;
      justify-content: flex-end;
      gap: 24px;
   }

.connect-form-status
   {
      margin: 0 auto 0 0;
      color: var(--error);
      font-family: 'Google Sans', Arial, sans-serif;
      font-size: 15px;
      line-height: 1.35;
   }

.connect-cancel
   {
      min-height: 44px;
      padding: 0 4px;
      border: 0;
      background: none;
      color: rgba(255, 255, 255, .7);
      font-family: 'Google Sans', Arial, sans-serif;
      font-size: 15px;
      letter-spacing: .04em;
      text-transform: uppercase;
      cursor: pointer;
      touch-action: manipulation;
      transition: color var(--press-out) var(--ease-out);
   }

.connect-cancel:focus-visible
   {
      outline: 2px solid currentColor;
      outline-offset: 2px;
   }

.connect-send
   {
      border: 0;
      cursor: pointer;
   }

.connect-send[aria-busy="true"]
   {
      opacity: .6;
      cursor: progress;
   }

/* Sent, the fields give way to one line in the display serif. */
.connect-sent
   {
      display: none;
      flex-direction: column;
      align-items: flex-start;
      gap: 12px;
   }

.connect-sent:focus
   {
      outline: none;
   }

.connect-form.is-sent > :not(.connect-sent)
   {
      display: none;
   }

.connect-form.is-sent .connect-sent
   {
      display: flex;
   }

.connect-sent-title
   {
      margin: 0;
      font-family: 'Instrument Serif', Georgia, serif;
      font-size: 96px;
      line-height: 1;
      letter-spacing: -.02em;
   }

.connect-sent-text
   {
      margin: 0;
      color: rgba(255, 255, 255, .7);
      font-family: 'Google Sans', Arial, sans-serif;
      font-size: 20px;
   }

@media (hover: hover) and (pointer: fine)
   {
      :root:not(.touch-input) .connect-cancel:hover
         {
            color: var(--white);
         }
   }

.connect-motion .connect-form :is(.connect-field, .connect-form-actions)
   {
      opacity: 0;
      transform: translateY(14px);
   }

.connect-motion .connect-form .connect-input
   {
      background-size: 0 2px;
   }

.connect-motion.is-composing .connect-form :is(.connect-field, .connect-form-actions)
   {
      opacity: 1;
      transform: none;
      transition: opacity .45s var(--ease-out) calc(var(--i, 0) * 50ms), transform .45s var(--ease-out) calc(var(--i, 0) * 50ms);
   }

.connect-motion.is-composing .connect-form .connect-input:focus
   {
      background-size: 100% 2px;
   }

@media (max-width: 700px)
   {
      .connect-body
         {
            grid-template-columns: minmax(0, 1fr);
            gap: 24px;
         }

      .connect-rail
         {
            flex-direction: row;
            margin-left: -14px;
         }

      .connect-elsewhere-caption
         {
            position: static;
            margin-bottom: 4px;
            text-align: left;
         }

      .connect-lead
         {
            font-size: 24px;
            margin-bottom: -16px;
         }

      .connect-elsewhere-caption br
         {
            display: none;
         }

      .connect-compose
         {
            width: 100%;
         }

      .connect-fields
         {
            grid-template-columns: minmax(0, 1fr);
         }

      .connect-sent-title
         {
            font-size: 56px;
         }
   }

.craft-motion.is-entered.is-leaving .craft-meter
   {
      opacity: 0;
      transition: opacity .2s var(--ease-out);
   }

/* Reduced motion lands on the finished state; the global rule already drops
   the transitions. */
@media (prefers-reduced-motion: reduce)
   {
      .about-motion:not(.is-arrived) .about-hero .about-title-line
         {
            --about-fill: 1;
         }

      .about-motion:not(.is-arrived) .about-hero .about-rule::after
         {
            transform: none;
         }

      .about-motion:not(.is-arrived) :is(.about-lead, .about-resume)
         {
            opacity: 1;
            transform: none;
         }

      .about-motion:not(.is-arrived) .about-scene, .about-motion .about-hero.is-away .about-scene
         {
            opacity: 1;
            scale: none;
         }
   }

@media (forced-colors: active)
   {
      .core-reading
         {
            forced-color-adjust: auto;
            border: 1px solid CanvasText;
         }
   }

/* Project sheet. A card opens its project as a full page over the site. It is
   clear, so the site's own drifting grain stays behind it while the page's
   content fades out. The hero is About's: the eyebrow's rule draws out, each
   title line fills from its outline, then the story and links rise, on the
   timeline of DAQ Consulting's service pages. Everything rests on its
   finished state, so reduced motion simply lands there. */
.project-sheet
   {
      --page-gutter: clamp(1rem, 2.8vw, 3.5rem);
      position: fixed;
      inset: 0;
      width: 100%;
      height: 100%;
      max-width: none;
      max-height: none;
      margin: 0;
      padding: 0;
      border: 0;
      overflow-y: auto;
      overscroll-behavior: contain;
      background: transparent;
      color: var(--white);
   }

.project-sheet::backdrop
   {
      background: transparent;
   }

/* On its own the project page is the sheet, in the page's flow. */
.project-sheet--page
   {
      position: relative;
      inset: auto;
      height: auto;
      min-height: 100svh;
      overflow: visible;
   }

html.project-sheet-open
   {
      overflow: hidden;
   }

body > :not(.site-backdrop, .project-sheet)
   {
      transition: opacity .24s var(--ease-out);
   }

html.project-sheet-open body > :not(.site-backdrop, .project-sheet)
   {
      opacity: 0;
   }

/* Closing a project sheet brings the top bar back with Projects: the
   wordmark and each link drop in from above, 30ms apart, and the current
   page's line draws back on. Only the contents move; the bar and its
   gradient fade in with the page. Script adds .nav-return only when motion
   is allowed. */
html.nav-return .about-topbar :is(.topbar-wordmark, .section-nav > *)
   {
      animation: nav-return .5s var(--ease-out) calc(.06s + var(--nav-i, 0) * 30ms) both;
   }

html.nav-return .about-topbar .section-nav-link[aria-current]::before
   {
      animation: nav-return-line .5s var(--ease-out) .35s both;
   }

@keyframes nav-return
   {
      from
         {
            opacity: 0;
            translate: 0 -.75rem;
         }
   }

@keyframes nav-return-line
   {
      from
         {
            transform: scaleX(0);
         }
   }

/* Opening a project plays Projects out first: the title lifts, the cards
   slide on to the left and the chosen one brightens and goes last, and the
   top bar's links lift out, before the sheet cuts in. Script adds these
   classes only when motion is allowed. */
html.projects-leaving .projects-main :is(.projects-title-line, .project-card)
   {
      opacity: 0;
      transition: opacity .28s var(--ease-in-out), transform .28s var(--ease-in-out);
   }

html.projects-leaving .projects-main .projects-title-line
   {
      transform: translateY(-.35em);
   }

html.projects-leaving .projects-main .projects-title-strong
   {
      transition-delay: .04s;
   }

html.projects-leaving .projects-main .project-card
   {
      transform: translateX(-2.5rem);
      transition-delay: .04s;
   }

html.projects-leaving .projects-main .project-card.is-opening
   {
      transform: none;
      transition: opacity .2s var(--ease-in-out) .12s;
   }

html.projects-leaving .projects-main .project-card.is-opening .project-link
   {
      border-color: rgba(255, 255, 255, .6);
      transition-duration: .15s;
   }

html.projects-leaving .projects-main .projects-rule::after
   {
      transform: scaleX(0);
      transform-origin: right;
      transition: transform .28s var(--ease-in-out);
   }

/* The eyebrow goes whole, its faint track too, so no hairline is left to jump
   to wherever the next page draws its own. */
html.projects-leaving .projects-main .projects-eyebrow
   {
      opacity: 0;
      transition: opacity .28s var(--ease-in-out);
   }

html.projects-leaving .projects-main .projects-rail
   {
      opacity: 0;
      translate: 0 .5rem;
      transition: opacity .28s var(--ease-in-out) .04s, translate .28s var(--ease-in-out) .04s;
   }

html.nav-leave .about-topbar :is(.topbar-wordmark, .section-nav > *)
   {
      animation: nav-leave .24s var(--ease-in-out) calc(var(--nav-i, 0) * 15ms) both;
   }

html.nav-leave .about-topbar .section-nav-link[aria-current]::before
   {
      transform-origin: right;
      animation: nav-leave-line .24s var(--ease-in-out) both;
   }

/* Between Projects and Skills the bar stays and only the underline travels:
   it lifts off toward the next page during the exit and draws on from that
   side once the next page lands. Script adds these only when motion is on. */
html.nav-line-leave .about-topbar .section-nav-link[aria-current]::before
   {
      transform-origin: right;
      animation: nav-leave-line .26s var(--ease-in-out) both;
   }

html.nav-line-enter .about-topbar .section-nav-link[aria-current]::before
   {
      animation: nav-return-line .45s var(--ease-out) .06s both;
   }

html.nav-line-back.nav-line-leave .about-topbar .section-nav-link[aria-current]::before
   {
      transform-origin: left;
   }

html.nav-line-back.nav-line-enter .about-topbar .section-nav-link[aria-current]::before
   {
      transform-origin: right;
   }

/* Returning Home from deeper in the page, the page cuts to About's top on its
   way into the fold. Meet Josh and About's underline, which the fold would show
   there, stay out of sight until the fold has hidden them by itself. */
html.home-return .about-content
   {
      opacity: 0 !important;
   }

html.home-return .about-topbar .section-nav-link::before
   {
      visibility: hidden;
   }

@keyframes nav-leave
   {
      to
         {
            opacity: 0;
            translate: 0 -.75rem;
         }
   }

@keyframes nav-leave-line
   {
      to
         {
            transform: scaleX(0);
         }
   }

/* Back sits where a way out is expected, in the site's nav type. */
.project-sheet-bar
   {
      position: sticky;
      top: 0;
      z-index: 2;
      display: flex;
      align-items: center;
      height: 5rem;
      padding: 0 var(--page-gutter);
      pointer-events: none;
   }

/* Content scrolling up under the bar fades out behind a soft, blurred edge
   instead of running into the link. */
.project-sheet-bar::before
   {
      content: '';
      position: absolute;
      inset: 0 0 -1.5rem;
      z-index: -1;
      background: linear-gradient(#060606, rgba(6, 6, 6, .7) 60%, rgba(6, 6, 6, 0));
      -webkit-backdrop-filter: blur(6px);
      backdrop-filter: blur(6px);
      -webkit-mask-image: linear-gradient(#000 55%, transparent);
      mask-image: linear-gradient(#000 55%, transparent);
   }

.project-sheet-back
   {
      display: inline-flex;
      align-items: center;
      gap: .35rem;
      min-height: 2.75rem;
      margin-left: -.6rem;
      padding: .4rem .6rem;
      border: 0;
      border-radius: .25rem;
      background: none;
      color: rgba(255, 255, 255, .7);
      font-family: 'Google Sans', Arial, sans-serif;
      font-size: .9375rem;
      font-weight: 400;
      line-height: 1.3;
      letter-spacing: .04em;
      text-transform: uppercase;
      white-space: nowrap;
      cursor: pointer;
      pointer-events: auto;
      touch-action: manipulation;
      transition: color .2s ease, background-color var(--press-out) var(--ease-out);
   }

.project-sheet-back svg
   {
      width: 1.125rem;
      height: 1.125rem;
      fill: none;
      stroke: currentColor;
      stroke-width: 1.75;
      stroke-linecap: round;
      stroke-linejoin: round;
      transition: transform .2s var(--ease-out);
   }

.project-sheet-back:focus-visible
   {
      outline: 1px solid rgba(255, 255, 255, .5);
      outline-offset: 2px;
      color: var(--white);
   }

.project-sheet-back.is-touch-pressed
   {
      background: rgba(255, 255, 255, .12);
      transition-duration: var(--press-in);
   }

.project-sheet-hero
   {
      max-width: 1600px;
      margin: 0 auto;
      padding: clamp(1.5rem, 7svh, 5rem) var(--page-gutter) 8rem;
   }

.project-sheet-hero .about-eyebrow
   {
      margin-bottom: clamp(2rem, 5svh, 3rem);
   }

.project-sheet-actions
   {
      display: flex;
      flex-wrap: wrap;
      gap: 12px;
      margin: clamp(1.75rem, 4svh, 2.5rem) 0 0;
      padding: 0;
      list-style: none;
   }

/* The site's inverse button with a clear face: the white block and its icon
   slide across to fill it on hover, as My Resume does. The labels run longer
   than My Resume's, so the button grows to fit them, keeping its width while
   the label shifts. */
.project-sheet-actions .button
   {
      width: auto;
      min-width: 210px;
      background: transparent;
      font-family: 'Google Sans', Arial, Helvetica, sans-serif;
      font-size: .9375rem;
      font-weight: 400;
      letter-spacing: .01em;
   }

.project-sheet-actions .button-arrow svg
   {
      width: 18px;
      height: 18px;
   }

/* A project's mark is sparser than a glyph icon, so it gets a little more room. */
.project-sheet-actions .button-arrow .project-sheet-mark
   {
      width: 22px;
      height: 22px;
   }

/* SleekBoard's mark is wide and short in both its broken and whole states, so
   it is drawn larger to read at the same weight. */
.project-sheet-actions .button-arrow .sleek-mark
   {
      width: 26px;
      height: 26px;
   }

.project-sheet-actions .button-label
   {
      padding-right: 18px;
   }

.project-sheet-actions .button:not([aria-disabled="true"]):focus-visible .button-label
   {
      padding-right: 64px;
   }

.project-sheet-actions .button[aria-disabled="true"]
   {
      opacity: .35;
      cursor: not-allowed;
   }

.project-sheet-actions .button[aria-disabled="true"]::before
   {
      width: 44px !important;
   }

.project-sheet-actions .button[aria-disabled="true"] .button-label
   {
      padding: 0 18px 0 46px !important;
      color: inherit !important;
   }

.project-sheet-actions .button[aria-disabled="true"] .button-arrow
   {
      left: 2px !important;
   }

@media (hover: hover) and (pointer: fine)
   {
      :root:not(.touch-input) .project-sheet-actions .button:not([aria-disabled="true"]):hover .button-label
         {
            padding-right: 64px;
         }

      .project-sheet-back:hover
         {
            color: var(--white);
         }

      .project-sheet-back:hover svg
         {
            transform: translateX(-3px);
         }
   }

@media (max-width: 700px)
   {
      .project-sheet
         {
            --page-gutter: 1.25rem;
         }
   }

/* The first screen pairs the project with its story. The story starts a set
   distance after the copy and runs toward the page edge, so the two read as
   one group; narrower screens stack it under the links. */
.project-sheet-hero
   {
      display: grid;
      grid-template-columns: minmax(0, auto) minmax(0, 1fr);
      align-items: center;
      gap: clamp(2.5rem, 7vw, 8rem);
      min-height: calc(100svh - 5rem);
      padding-bottom: clamp(3rem, 8svh, 6rem);
   }

/* The story takes the rest of the row, up to a comfortable line length. */
.project-story
   {
      max-width: 68ch;
   }

.project-story .about-eyebrow
   {
      margin-bottom: clamp(1.25rem, 3svh, 1.75rem);
   }

.project-story p:not(.about-eyebrow)
   {
      margin: 0;
      color: rgba(255, 255, 255, .7);
      font-family: 'Google Sans', Arial, Helvetica, sans-serif;
      font-size: clamp(1rem, 1.2vw, 1.125rem);
      line-height: 1.7;
      text-wrap: pretty;
   }

.project-story p:not(.about-eyebrow) + p
   {
      margin-top: 1em;
   }

@media (max-width: 1000px)
   {
      .project-sheet-hero
         {
            grid-template-columns: minmax(0, 1fr);
            align-items: start;
            min-height: 0;
         }
   }

/* The way on, after Karim Bastami's Click To Scroll: a mouse whose wheel keeps
   rolling down, and a pair of chevrons. It rests centred at the foot of the
   first screen and turns the page as a flick of the wheel does. Stacked, it follows
   the story. */
.project-sheet-hero
   {
      position: relative;
   }

.project-scroll-cue
   {
      position: absolute;
      left: 0;
      right: 0;
      bottom: 24px;
      display: flex;
      align-items: center;
      gap: 10px;
      width: fit-content;
      min-height: 44px;
      margin: 0 auto;
      padding: 0 8px;
      border-radius: 4px;
      color: rgba(255, 255, 255, .7);
      font-family: 'Google Sans', Arial, sans-serif;
      font-size: 15px;
      line-height: 1.3;
      letter-spacing: .04em;
      text-transform: uppercase;
      text-decoration: none;
      white-space: nowrap;
      touch-action: manipulation;
      transition: color .2s ease;
   }

.project-scroll-cue svg
   {
      flex: none;
      fill: none;
      stroke: currentColor;
      stroke-linecap: round;
      stroke-linejoin: round;
   }

.project-scroll-mouse
   {
      width: 20px;
      height: 30px;
      stroke-width: 1.5;
   }

.project-scroll-wheel
   {
      stroke-width: 2;
      animation: project-scroll-wheel 1.8s var(--ease-in-out) infinite;
   }

.project-scroll-chevrons
   {
      width: 18px;
      height: 18px;
      stroke-width: 1.75;
      transition: transform .3s var(--ease-out);
   }

.project-scroll-chevrons path:last-child
   {
      animation: project-scroll-chevron 1.8s var(--ease-in-out) infinite;
   }

.project-scroll-cue:focus-visible
   {
      outline: 1px solid rgba(255, 255, 255, .5);
      outline-offset: 2px;
      color: var(--white);
   }

@keyframes project-scroll-wheel
   {
      0%
         {
            opacity: 0;
            transform: translateY(-2px);
         }

      20%
         {
            opacity: 1;
            transform: translateY(0);
         }

      70%, 100%
         {
            opacity: 0;
            transform: translateY(8px);
         }
   }

@keyframes project-scroll-chevron
   {
      0%, 30%, 100%
         {
            opacity: 1;
         }

      55%
         {
            opacity: .35;
         }
   }

@media (hover: hover) and (pointer: fine)
   {
      .project-scroll-cue:hover
         {
            color: var(--white);
         }

      .project-scroll-cue:hover .project-scroll-chevrons
         {
            transform: translateY(3px);
         }
   }

@media (max-width: 1000px)
   {
      .project-scroll-cue
         {
            position: static;
            justify-self: center;
            margin-top: -8px;
         }
   }

@media (prefers-reduced-motion: reduce)
   {
      .project-scroll-cue svg *
         {
            animation: none;
         }
   }

/* Architecture figure, after DAQ Consulting's case-study drawing: a diagram of
   drawing of the stack over an inspector for the selected stage. Every size is
   fixed and the whole section is 770px tall, so it fits one laptop screen; a
   narrower screen scrolls the figure sideways instead of reflowing it. */
/* A page turn rests this section at the top of the screen, so it takes a whole
   screen and centers the figure in the room below the bar. */
.project-how
   {
      display: grid;
      align-content: center;
      box-sizing: border-box;
      min-height: 100svh;
      max-width: 1600px;
      margin: 0 auto;
      padding: 5rem var(--page-gutter) 0;
      overflow-x: auto;
      scrollbar-width: thin;
      scrollbar-color: #777 #111;
   }

.arch
   {
      width: 1378px;
      color: #fff;
   }

.arch-head
   {
      margin-bottom: 20px;
   }

.arch-head .about-eyebrow
   {
      margin-bottom: 0;
   }

.arch-sheet
   {
      background: #030303;
      border: 1px solid rgba(255, 255, 255, .18);
   }

/* A project whose drawing is still to come keeps the sheet at the figure's
   full size, so the section turns and reveals as the drawn ones do. */
.arch-sheet--blank
   {
      box-sizing: border-box;
      height: 558px;
   }

.arch-legend
   {
      display: flex;
      align-items: center;
      gap: 24px;
      height: 36px;
      padding: 0 24px;
      border-bottom: 1px solid rgba(255, 255, 255, .1);
      color: rgba(255, 255, 255, .62);
      font-family: 'Geist Mono', monospace;
      font-size: 10px;
      letter-spacing: .04em;
   }

.arch-legend > span
   {
      display: inline-flex;
      align-items: center;
      gap: 8px;
   }

.arch-legend i
   {
      display: block;
      width: 20px;
      height: 0;
      border-top: 1px solid;
   }

.arch-legend i[data-kind="control"]
   {
      border-top-style: dashed;
   }

.arch-hint
   {
      margin-left: auto;
   }

.arch-drawing
   {
      display: block;
      width: 1376px;
      height: 376px;
      overflow: visible;
   }

.arch-drawing text
   {
      user-select: none;
      -webkit-user-select: none;
   }

.arch-region rect
   {
      fill: rgba(255, 255, 255, .01);
      stroke: rgba(255, 255, 255, .16);
      stroke-width: .6;
   }

.arch-region text
   {
      fill: rgba(255, 255, 255, .57);
      stroke: #030303;
      stroke-width: 6px;
      paint-order: stroke;
      stroke-linejoin: round;
      font-family: 'Geist Mono', monospace;
      font-size: 12px;
      letter-spacing: 1px;
   }

.arch-edge
   {
      color: #777;
   }

.arch-route
   {
      fill: none;
      stroke: currentColor;
      stroke-width: 1;
      stroke-linejoin: round;
   }

.arch-edge[data-kind="control"] .arch-route
   {
      stroke-dasharray: 6 5;
   }

.arch-edge[data-active="true"]
   {
      color: #ddd;
   }

.arch-edge[data-active="true"] .arch-route
   {
      stroke-width: 1.4;
   }

.arch-ink
   {
      fill: none;
      stroke: #fff;
      stroke-width: 1.6;
      stroke-dasharray: 1;
      pointer-events: none;
      animation: arch-ink .45s cubic-bezier(.22, 1, .36, 1) both;
   }

@keyframes arch-ink
   {
      from
         {
            stroke-dashoffset: 1;
         }

      to
         {
            stroke-dashoffset: 0;
         }
   }

.arch-node
   {
      cursor: pointer;
      outline: none;
      touch-action: manipulation;
      -webkit-tap-highlight-color: transparent;
   }

.arch-node-body
   {
      fill: #090909;
      stroke: #666;
      stroke-width: 1;
      transition: fill .18s, stroke .18s;
   }

.arch-node-backing
   {
      fill: none;
      stroke: #555;
      stroke-width: 1;
   }

.arch-node-notation
   {
      fill: none;
      stroke: #666;
      stroke-width: 1;
   }

.arch-layer-band
   {
      fill: #1a1a1a;
   }

.arch-layer-rule
   {
      fill: none;
      stroke: #666;
      stroke-width: .7;
   }

.arch-layer-label
   {
      fill: #ddd;
      font-family: 'Geist Mono', monospace;
      font-size: 10px;
      letter-spacing: 1.6px;
      pointer-events: none;
   }

.arch-node[data-layer] .arch-node-body
   {
      fill: #101010;
      stroke: #888;
   }

.arch-node-title
   {
      fill: #ddd;
      font-family: 'Google Sans', Arial, sans-serif;
      font-size: 18px;
      letter-spacing: -.5px;
      pointer-events: none;
   }

.arch-node[data-layer] .arch-node-title
   {
      fill: #f0f0f0;
   }

.arch-node-subtitle
   {
      fill: #aaa;
      font-family: 'Geist Mono', monospace;
      font-size: 11px;
      pointer-events: none;
   }

.arch-node[data-connected="true"] .arch-node-body
   {
      stroke: #999;
   }

.arch-node[data-selected="true"] .arch-node-body
   {
      fill: #1b1b1b;
      stroke: #fff;
      stroke-width: 1.4;
   }

.arch-node[data-selected="true"] .arch-layer-band
   {
      fill: #ededed;
   }

.arch-node[data-selected="true"] .arch-layer-label
   {
      fill: #111;
   }

.arch-node[data-selected="true"] .arch-node-title
   {
      fill: #fff;
   }

.arch-node:focus-visible .arch-node-body
   {
      stroke: #fff;
      stroke-width: 2.5;
   }

.arch-node-icons
   {
      color: #888;
      pointer-events: none;
      transition: color .18s;
   }

.arch-band-icons
   {
      color: #aaa;
   }

.arch-node[data-connected="true"] .arch-node-icons
   {
      color: #aaa;
   }

.arch-node[data-selected="true"] .arch-node-icons
   {
      color: #fff;
   }

.arch-node[data-selected="true"] .arch-band-icons
   {
      color: #111;
   }

/* The selected box shows its logos in brand color. */
.arch-icon-color
   {
      display: none;
   }

.arch-node[data-selected="true"] .arch-icon-color
   {
      display: inline;
   }

.arch-node[data-selected="true"] .arch-icon-mono:has(+ .arch-icon-color)
   {
      display: none;
   }

.arch-selection
   {
      fill: none;
      stroke: #fff;
      stroke-width: 1.3;
      pointer-events: none;
   }

.arch-ports
   {
      pointer-events: none;
   }

.arch-ports rect
   {
      fill: #030303;
      stroke: #888;
      stroke-width: .8;
   }

.arch-ports rect[data-active="true"]
   {
      fill: #eee;
      stroke: #eee;
   }

@media (hover: hover)
   {
      .arch-node:hover .arch-node-body
         {
            fill: #181818;
            stroke: #eee;
         }

      .arch-node:not([data-selected="true"]):hover .arch-node-icons
         {
            color: #eee;
         }
   }

.arch-inspector
   {
      display: grid;
      grid-template-columns: 300px 972px;
      gap: 40px;
      height: 144px;
      padding: 20px 32px;
      box-sizing: border-box;
      border-top: 1px solid rgba(255, 255, 255, .38);
      background: #080808;
   }

.arch-stage
   {
      display: flex;
      flex-direction: column;
      align-items: flex-start;
   }

.arch-stage-type
   {
      color: rgba(255, 255, 255, .6);
      font-family: 'Geist Mono', monospace;
      font-size: 10px;
      line-height: 15px;
      letter-spacing: .08em;
      text-transform: uppercase;
   }

.arch-stage-title
   {
      margin: 8px 0 0;
      font-family: 'Google Sans', Arial, sans-serif;
      font-size: 22px;
      font-weight: 400;
      line-height: 26px;
      letter-spacing: -.03em;
      white-space: nowrap;
   }

.arch-stepper
   {
      display: flex;
      align-items: center;
      gap: 8px;
      margin-top: auto;
   }

.arch-stepper button
   {
      display: grid;
      place-items: center;
      width: 40px;
      height: 40px;
      padding: 0;
      border: 1px solid rgba(255, 255, 255, .25);
      background: none;
      color: #fff;
      cursor: pointer;
      touch-action: manipulation;
      transition: background-color .15s;
   }

.arch-stepper button:active
   {
      background: rgba(255, 255, 255, .12);
   }

.arch-stepper button:focus-visible
   {
      outline: 1px solid #fff;
      outline-offset: 4px;
   }

.arch-stepper > span
   {
      width: 60px;
      font-family: 'Geist Mono', monospace;
      font-size: 10px;
      text-align: center;
      font-variant-numeric: tabular-nums;
   }

.arch-stepper > span > span + span
   {
      color: rgba(255, 255, 255, .6);
   }

@media (hover: hover)
   {
      .arch-stepper button:hover
         {
            background: rgba(255, 255, 255, .08);
         }
   }

.arch-desc
   {
      margin: 0;
      color: rgba(255, 255, 255, .7);
      font-family: 'Google Sans', Arial, sans-serif;
      font-size: 13px;
      line-height: 22px;
   }

.arch-tech
   {
      display: flex;
      gap: 16px;
      margin: 14px 0 0;
      padding: 0;
      color: rgba(255, 255, 255, .6);
      font-family: 'Geist Mono', monospace;
      font-size: 10px;
      line-height: 15px;
      list-style: none;
   }

.arch-tech li
   {
      display: flex;
      align-items: center;
      gap: 6px;
   }

.arch-tech svg
   {
      flex: none;
      width: 12px;
      height: 12px;
   }

@media (prefers-reduced-motion: reduce)
   {
      .arch-ink
         {
            animation: none;
         }

      .arch-node-body
         {
            transition: none;
         }
   }

/* GSAP's power2.inOut and power3.out. */
.project-sheet.is-playing .project-sheet-hero .about-rule::after
   {
      animation: project-sheet-rule 1s cubic-bezier(.455, .03, .515, .955) .1s both;
   }

.project-sheet.is-playing .project-sheet-hero .about-title-line
   {
      animation: project-sheet-fill 1.3s cubic-bezier(.455, .03, .515, .955) calc(.2s + var(--line, 0) * .18s) both;
   }

.project-sheet.is-playing .project-sheet-rise
   {
      animation: project-sheet-rise .8s cubic-bezier(.215, .61, .355, 1) calc(.8s + var(--rise, 0) * .1s) both;
   }

/* The title drops in through its lines' top edges as the fill starts, so
   the page moves from its first frame, and the way back rises with it. */
.project-sheet.is-playing .handover-motion .about-title-line > *
   {
      animation: project-sheet-drop .7s var(--ease-out-quart) calc(var(--line, 0) * 80ms) both;
   }

.project-sheet.is-playing .project-sheet-back
   {
      animation: project-sheet-rise .8s cubic-bezier(.215, .61, .355, 1) .15s both;
   }

@keyframes project-sheet-drop
   {
      from
         {
            translate: 0 -105%;
         }
   }

/* Closing, the way back lifts out with the page's exit. */
.project-sheet.is-closing .project-sheet-back
   {
      opacity: 0;
      translate: 0 -.5rem;
      transition: opacity .24s var(--ease-in-out) .06s, translate .24s var(--ease-in-out) .06s;
   }

@keyframes project-sheet-rule
   {
      from
         {
            transform: scaleX(0);
         }
   }

@keyframes project-sheet-fill
   {
      from
         {
            --about-fill: 0;
         }
   }

@keyframes project-sheet-rise
   {
      from
         {
            opacity: 0;
            transform: translateY(14px);
         }
   }

/* The View Live mark assembles as its button lands: the ring opens out, then
   the block rises and the step slides in to meet it. */
.project-sheet-mark :is(.mark-ring, .mark-block, .mark-step)
   {
      transform-box: fill-box;
      transform-origin: center;
   }

.project-sheet.is-playing .project-sheet-mark .mark-ring
   {
      animation: project-mark-ring .5s var(--ease-out) 1s both;
   }

.project-sheet.is-playing .project-sheet-mark .mark-block
   {
      animation: project-mark-block .5s var(--ease-out) 1.08s both;
   }

.project-sheet.is-playing .project-sheet-mark .mark-step
   {
      animation: project-mark-step .5s var(--ease-out) 1.16s both;
   }

@keyframes project-mark-ring
   {
      from
         {
            opacity: 0;
            transform: scale(.6);
         }
   }

@keyframes project-mark-block
   {
      from
         {
            opacity: 0;
            transform: translateY(24px);
         }
   }

@keyframes project-mark-step
   {
      from
         {
            opacity: 0;
            transform: translateX(-24px);
         }
   }

/* Once the fill has carried the mark across, it parts and closes again. */
@media (hover: hover) and (pointer: fine)
   {
      :root:not(.touch-input) .project-sheet:not(.is-playing) .button:hover .mark-ring
         {
            animation: project-mark-ring-again .45s var(--ease-in-out) .3s;
         }

      :root:not(.touch-input) .project-sheet:not(.is-playing) .button:hover .mark-block
         {
            animation: project-mark-block-again .45s var(--ease-in-out) .34s;
         }

      :root:not(.touch-input) .project-sheet:not(.is-playing) .button:hover .mark-step
         {
            animation: project-mark-step-again .45s var(--ease-in-out) .38s;
         }
   }

@keyframes project-mark-ring-again
   {
      40%
         {
            transform: scale(.82);
         }
   }

@keyframes project-mark-block-again
   {
      40%
         {
            transform: translateY(12px);
         }
   }

@keyframes project-mark-step-again
   {
      40%
         {
            transform: translateX(-12px);
         }
   }

/* SleekBoard's View Live mark is its logo broken in two: the halves sit
   apart, offset, each holding half the circle. On landing they slide in from
   either side. Hovering, once the fill has carried the mark across, they slide
   together into the whole logo, the square with its round hole: the top half
   right and up, the bottom half left and down, by the 19.8 and 6.46 units that
   line the two arcs up. Leaving, they part again, quicker. */
.sleek-mark :is(.sleek-top, .sleek-bottom)
   {
      transition: transform .3s var(--ease-in-out);
   }

.project-sheet.is-playing .sleek-mark .sleek-top
   {
      animation: sleek-top-in .5s var(--ease-out) 1s both;
   }

.project-sheet.is-playing .sleek-mark .sleek-bottom
   {
      animation: sleek-bottom-in .5s var(--ease-out) 1.08s both;
   }

@keyframes sleek-top-in
   {
      from
         {
            opacity: 0;
            transform: translateX(-28px);
         }
   }

@keyframes sleek-bottom-in
   {
      from
         {
            opacity: 0;
            transform: translateX(28px);
         }
   }

@media (hover: hover) and (pointer: fine)
   {
      :root:not(.touch-input) .project-sheet:not(.is-playing) .button:hover .sleek-mark :is(.sleek-top, .sleek-bottom)
         {
            transition: transform .5s var(--ease-out) .28s;
         }

      :root:not(.touch-input) .project-sheet:not(.is-playing) .button:hover .sleek-top
         {
            transform: translate(19.8px, -6.46px);
         }

      :root:not(.touch-input) .project-sheet:not(.is-playing) .button:hover .sleek-bottom
         {
            transform: translate(-19.8px, 6.46px);
         }
   }

@media (prefers-reduced-motion: reduce)
   {
      .sleek-mark :is(.sleek-top, .sleek-bottom)
         {
            animation: none !important;
            transition: none !important;
         }
   }

/* After its entrance the hero moves only by transition. Scrolling on past it
   plays the entrance back out, quicker, the last line to rise leaving first;
   scrolling back returns it at the exit's pace, as About's sections do.
   Script adds .is-leaving only when motion is allowed. */
.project-sheet:not(.is-playing) .project-sheet-hero:not(.is-leaving) .about-title-line
   {
      transition: --about-fill .5s var(--ease-out);
   }

.project-sheet:not(.is-playing) .project-sheet-hero:not(.is-leaving) .about-rule::after
   {
      transition: transform .4s var(--ease-out);
   }

.project-sheet:not(.is-playing) .project-sheet-hero:not(.is-leaving) .project-sheet-copy > .about-eyebrow
   {
      transition: opacity .4s var(--ease-out);
   }

.project-sheet-hero.is-leaving .about-title-line
   {
      --about-fill: 0;
      transition: --about-fill .28s var(--ease-out) .06s;
   }

.project-sheet-hero.is-leaving .about-rule::after
   {
      transform: scaleX(0);
      transform-origin: right;
      transition: transform .28s var(--ease-out) .06s;
   }

.project-sheet-hero.is-leaving .project-sheet-copy > .about-eyebrow
   {
      opacity: 0;
      transition: opacity .25s var(--ease-out) .06s;
   }

/* Touch keeps its native momentum and lands on each section; the wheel's
   turns are scripted. The hero rests below the bar's room. */
@media (pointer: coarse)
   {
      .project-paging
         {
            scroll-snap-type: y mandatory;
         }

      .project-paging .project-sheet-content > *
         {
            scroll-snap-align: start;
         }

      .project-paging .project-sheet-hero
         {
            scroll-margin-top: 5rem;
         }
   }

/* The handover. Every line of the hero leaves through its own top edge, as
   Masaigon's and Akaru's headings do: the links, the lead and the title,
   12ms apart, while the story dissolves beside them, before the page cuts; they
   come back down in the opposite order. Script splits the paragraphs into
   lines, sets each one's --out and --in, and adds .handover-motion only when
   motion is allowed. The clips reach past each line's sides and foot, so
   nothing is cut at rest; only the top edge is a mask. */
.handover-line
   {
      display: block;
      clip-path: inset(0 -1em -.3em -1em);
   }

.handover-out
   {
      display: block;
      white-space: nowrap;
   }

.handover-motion :is(.project-story .about-eyebrow, .about-title-line)
   {
      clip-path: inset(0 -1em -.3em -1em);
   }

.handover-motion .project-sheet-actions li
   {
      clip-path: inset(0 -4px -4px -4px);
   }

.handover-motion :is(.handover-out, .project-story .about-eyebrow > span, .about-title-line > *)
   {
      transition: translate .5s var(--ease-out) calc(var(--in, 0) * 12ms);
   }

.handover-motion.is-leaving :is(.handover-out, .project-story .about-eyebrow > span, .about-title-line > *)
   {
      translate: 0 -105%;
      transition: translate .28s var(--ease-in-out) calc(var(--out, 0) * 12ms);
   }

/* The links keep their press feedback alongside. */
.handover-motion .project-sheet-actions .button
   {
      transition: translate .5s var(--ease-out) calc(var(--in, 0) * 12ms), transform var(--press-out) var(--ease-out), opacity var(--press-out) var(--ease-out);
   }

.handover-motion.is-leaving .project-sheet-actions .button
   {
      translate: 0 -105%;
      transition: translate .28s var(--ease-in-out) calc(var(--out, 0) * 12ms), transform var(--press-out) var(--ease-out), opacity var(--press-out) var(--ease-out);
   }

/* The story dissolves instead of lifting: each line softens and fades up
   10px in reading order, 14ms apart, alongside the title side's lift. It
   re-forms in the same order, a beat after the title side starts back. */
.handover-motion .project-story :is(.about-eyebrow, .handover-line)
   {
      clip-path: none;
   }

.handover-motion .project-story :is(.handover-out, .about-eyebrow > span)
   {
      transition: opacity .5s var(--ease-out) calc(.12s + var(--out, 0) * 24ms), translate .5s var(--ease-out) calc(.12s + var(--out, 0) * 24ms), filter .5s var(--ease-out) calc(.12s + var(--out, 0) * 24ms);
   }

.handover-motion.is-leaving .project-story :is(.handover-out, .about-eyebrow > span)
   {
      opacity: 0;
      translate: 0 -10px;
      filter: blur(6px);
      transition: opacity .24s var(--ease-out) calc(var(--out, 0) * 14ms), translate .24s var(--ease-out) calc(var(--out, 0) * 14ms), filter .24s var(--ease-out) calc(var(--out, 0) * 14ms);
   }

/* The cue drops away first, the way it points, and comes back last. */
.handover-motion .project-scroll-cue
   {
      transition: color .2s ease, opacity .4s var(--ease-out) .3s, translate .4s var(--ease-out) .3s;
   }

.handover-motion.is-leaving .project-scroll-cue
   {
      opacity: 0;
      translate: 0 12px;
      pointer-events: none;
      transition: color .2s ease, opacity .2s var(--ease-out), translate .2s var(--ease-out);
   }

/* How it works rises in where the hero stood, through a soft edge, and its
   line draws on from the left as the hero's shrinks away to the right.
   Script adds .how-motion only when motion is allowed. */
/* The sheet waits below its place; the section must not scroll to it. */
.how-motion
   {
      overflow-y: hidden;
   }

.how-motion .arch-head .about-eyebrow
   {
      clip-path: inset(0 -1em -.3em -1em);
   }

.how-motion:not(.is-entered) .arch-sheet
   {
      transform: translateY(40px);
      --sheet-reveal: 0;
   }

.how-motion:not(.is-entered) .arch-head .about-eyebrow > span:first-child
   {
      translate: 0 105%;
   }

.how-motion:not(.is-entered) .arch-head .about-rule::after
   {
      transform: scaleX(0);
   }

.how-motion.is-entered:not(.is-leaving) .arch-sheet
   {
      transition: transform 1s var(--ease-out-cubic) .12s, --sheet-reveal 1s var(--ease-out-cubic) .12s;
   }

.how-motion.is-entered:not(.is-leaving) .arch-head .about-eyebrow > span:first-child
   {
      transition: translate .6s var(--ease-out) .2s;
   }

.how-motion.is-entered:not(.is-leaving) .arch-head .about-rule::after
   {
      transition: transform .7s var(--ease-out) .25s;
   }

/* It leaves on the hero's own exit curve and clears before the page cuts
   back to the hero. */
.how-motion.is-leaving .arch-sheet
   {
      transform: translateY(40px);
      --sheet-reveal: 0;
      transition: transform .32s var(--ease-in-out), --sheet-reveal .32s var(--ease-in-out);
   }

.how-motion.is-leaving .arch-head .about-eyebrow > span:first-child
   {
      translate: 0 105%;
      transition: translate .25s var(--ease-out);
   }

.how-motion.is-leaving .arch-head .about-rule::after
   {
      transform: scaleX(0);
      transition: transform .25s var(--ease-out);
   }

/* The first arrival draws the figure, as ChainGPT's mark draws itself: each
   box's outline in the order data flows (--flow, 70ms apart), then its fill,
   labels and routes, and last the selected stage's logos in colour. Every
   box is 176 by 88, so each outline is 528 long; script sets each route's
   --len. */
.how-motion.is-drawing .arch-region
   {
      animation: arch-fade .5s var(--ease-out) .45s both;
   }

.how-motion.is-drawing .arch-node-body
   {
      stroke-dasharray: 528;
      animation: arch-outline .6s var(--ease-out-quart) calc(.5s + var(--flow, 0) * 70ms) both, arch-fill .3s var(--ease-out) calc(.8s + var(--flow, 0) * 70ms) both;
   }

.how-motion.is-drawing .arch-node > :not(.arch-node-body)
   {
      animation: arch-fade .3s var(--ease-out) calc(.85s + var(--flow, 0) * 70ms) both;
   }

.how-motion.is-drawing .arch-edge:not([data-kind="control"]) .arch-route
   {
      stroke-dasharray: var(--len);
      animation: arch-route .45s cubic-bezier(.22, 1, .36, 1) calc(.75s + var(--flow, 0) * 70ms) both;
   }

/* A route's arrowhead draws even while its line is hidden, so each route
   stays out of sight until its line starts. */
.how-motion.is-drawing .arch-edge:not([data-kind="control"])
   {
      animation: arch-fade .12s linear calc(.75s + var(--flow, 0) * 70ms) both;
   }

.how-motion.is-drawing .arch-edge[data-kind="control"], .how-motion.is-drawing .arch-ports rect
   {
      animation: arch-fade .3s var(--ease-out) calc(.85s + var(--flow, 0) * 70ms) both;
   }

.how-motion.is-drawing .arch-node[data-selected="true"] .arch-icon-color
   {
      animation: arch-fade .25s var(--ease-out) 1.95s both;
   }

@keyframes arch-outline
   {
      from
         {
            stroke-dashoffset: 528;
         }
   }

@keyframes arch-fill
   {
      from
         {
            fill-opacity: 0;
         }
   }

@keyframes arch-route
   {
      from
         {
            stroke-dashoffset: var(--len);
         }
   }

@keyframes arch-fade
   {
      from
         {
            opacity: 0;
         }
   }

/* The inspector rises through its own top edge once the figure is in:
   after the drawing on the first arrival, straight after the sheet later. */
.how-motion
   {
      --inspect-at: .55s;
   }

.how-motion.is-drawing
   {
      --inspect-at: 2s;
   }

.how-motion.is-entered .arch-stage-type
   {
      --i: 0;
   }

.how-motion.is-entered .arch-stage-title
   {
      --i: 1;
   }

.how-motion.is-entered .arch-desc
   {
      --i: 2;
   }

.how-motion.is-entered :is(.arch-stage-type, .arch-stage-title, .arch-desc)
   {
      animation: arch-rise .5s var(--ease-out) calc(var(--inspect-at) + var(--i) * 60ms) both;
   }

.how-motion.is-entered :is(.arch-tech, .arch-stepper)
   {
      animation: arch-fade .4s var(--ease-out) calc(var(--inspect-at) + .2s) both;
   }

@keyframes arch-rise
   {
      from
         {
            translate: 0 100%;
            clip-path: inset(0 0 100% 0);
         }
   }

.craft-tools-set li:has([href="#icon-postman"])
   {
      --brand: #ff6c37;
   }

.craft-tools-set li:has([href="#icon-apachemaven"])
   {
      --brand: #e56a9b;
   }
