/* Site styles + overrides for index.html (was inline). */
      .profile-avatar {
        background-image: url("../images/01.webp");
        background-position: 0% 5%;
      }
      .project-item {
        opacity: 1;
      }
      .project-status {
        position: absolute;
        right: 0;
        bottom: 0;
        font-family: "JetBrains Mono", ui-monospace, monospace;
        font-size: 10px;
        letter-spacing: 0.04em;
        text-transform: uppercase;
        color: var(--grey3);
        white-space: nowrap;
      }

      /* Hover preview image that follows the cursor */
      .hover-preview {
        position: fixed;
        top: 0;
        left: 0;
        z-index: 50;
        pointer-events: none;
        will-change: transform;
      }
      .hover-preview__inner {
        width: 230px;
        height: 165px;
        border-radius: 0;
        overflow: hidden;
        background: transparent;
        box-shadow: none;
        opacity: 0;
        transform: translate(-50%, -50%) scale(0.92);
        /* Exit: quick and quiet (this rule)  a dismissed preview should get
           out of the way. Enter (below) keeps the softer overshoot. */
        transition:
          opacity 0.12s ease,
          transform 0.12s ease;
      }
      .hover-preview[data-show="true"] .hover-preview__inner {
        opacity: 1;
        transform: translate(-50%, -50%) scale(1);
        transition:
          opacity 0.18s ease,
          transform 0.28s cubic-bezier(0.175, 0.885, 0.32, 1.1);
      }
      .hover-preview__inner img {
        width: 100%;
        height: 100%;
        object-fit: contain;
        display: block;
      }
      @media (prefers-reduced-motion: reduce) {
        .hover-preview__inner {
          transition: opacity 0.12s ease;
          transform: translate(-50%, -50%);
        }
      }

      /* Contact links: dashed underline on hover */
      .contact-item .chip-link__text {
        border-bottom: 1px dashed var(--grey3);
        transition: border-bottom-color 0.18s ease;
      }

      /* Hide the scrollbar everywhere (scrolling still works) */
      html,
      body {
        scrollbar-width: none; /* Firefox */
        -ms-overflow-style: none; /* old Edge/IE */
        overflow-x: clip; /* the page-shell zoom scales the fixed top-gradient past the edge */
      }
      html::-webkit-scrollbar,
      body::-webkit-scrollbar {
        display: none; /* Chrome, Safari, Edge */
        width: 0;
        height: 0;
      }

      /* Accessibility: skip link (visible only on keyboard focus) */
      .skip-link {
        position: absolute;
        left: 8px;
        top: 8px;
        z-index: 100;
        padding: 8px 12px;
        border-radius: 6px;
        background: var(--grey1);
        color: var(--body-bg, #fff);
        font-size: 13px;
        line-height: 1;
        text-decoration: none;
        transform: translateY(-56px);
        transition: transform 0.15s ease-out;
      }
      .skip-link:focus {
        transform: translateY(0);
        outline: 2px solid var(--focus-color);
        outline-offset: 2px;
      }
      [tabindex="-1"]:focus {
        outline: none;
      }
      /* Keyboard focus: one consistent ring for every interactive element
         (folders, mode/theme toggles, chat buttons, back buttons, links,
         focusable scrollers). :where() keeps specificity at zero so the few
         bespoke focus styles (.chip-link, .show-more-btn, .skip-link) still
         win. Mouse and touch never match :focus-visible, so pointer users
         see nothing new. */
      :where(a, button, [tabindex="0"]):focus-visible {
        outline: 2px solid var(--focus-color);
        outline-offset: 2px;
      }

      /* Header role subtitle (replaces the misused <time>) */
      .profile-info .profile-role {
        margin: 0;
        color: var(--grey3);
        font-size: 15px;
        font-weight: 400;
        line-height: 1;
        letter-spacing: -0.00563rem;
      }

      /* Footer becomes a contentinfo landmark outside <main> */
      .site-footer {
        margin-top: 64px;
        animation: staggerIn 0.42s cubic-bezier(0.22, 1, 0.36, 1) both;
        animation-delay: 0.55s;
      }
      @media (prefers-reduced-motion: reduce) {
        .site-footer {
          animation: none;
        }
      }

      /* Products: show first 3, reveal the rest via "View all" */
      .project-item.is-extra-hidden {
        display: none;
      }
      /* Newly revealed items cascade in (30ms steps, JS sets --reveal-i) instead
         of popping. Collapse stays instant on purpose: enter smooth, exit fast. */
      .project-item.is-revealed {
        animation: staggerIn 0.26s cubic-bezier(0.23, 1, 0.32, 1) both;
        animation-delay: calc(min(var(--reveal-i, 0), 5) * 30ms);
      }
      @media (prefers-reduced-motion: reduce) {
        .project-item.is-revealed {
          animation: none;
        }
      }
      .show-more-btn {
        margin-top: 16px;
        display: inline-flex;
        align-items: center;
        gap: 4px;
        padding: 0;
        background: none;
        border: none;
        cursor: pointer;
        font-family: inherit;
        font-size: 14px;
        line-height: 1;
        letter-spacing: -0.00563rem;
        color: var(--grey3);
        transition:
          color 0.12s ease,
          transform 0.16s cubic-bezier(0.23, 1, 0.32, 1);
      }
      .show-more-btn:active {
        transform: scale(0.97);
      }
      .show-more-btn:hover {
        color: var(--grey1);
      }
      .show-more-btn:focus-visible {
        outline: 2px solid var(--focus-color);
        outline-offset: 3px;
        border-radius: 3px;
      }
      .show-more-btn .chev {
        transition: transform 0.2s ease;
      }
      .show-more-btn[aria-expanded="true"] .chev {
        transform: rotate(180deg);
      }
      .show-more-label {
        text-decoration: underline;
        text-underline-offset: 3px;
        text-decoration-thickness: 1px;
      }
      #opensource-list .project-body {
        gap: 12px;
      }
      .case-study-link {
        position: relative;
        z-index: 1;
        display: inline-flex;
        align-items: center;
        gap: 4px;
        margin-top: 8px;
        font-family: "Hanken Grotesk", sans-serif;
        font-size: 12px;
        font-weight: 500;
        width: fit-content;
        color: var(--accent);
        border-bottom: 1px dashed transparent;
        transition: border-bottom-color 0.18s ease;
      }
      .case-study-link:hover {
        border-bottom-color: var(--accent);
      }
      .case-study-link.is-soon {
        color: var(--grey3);
        font-weight: 400;
        pointer-events: none;
      }
      .os-links {
        position: relative;
        z-index: 1;
        display: flex;
        gap: 16px;
        margin-top: 4px;
        width: fit-content;
      }
      .project-description .chip-link {
        position: relative;
        z-index: 1;
      }

      /* Pixel clouds (static, top-right) */
      .cloud {
        position: absolute;
        top: 0;
        background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADYAAAAeCAYAAABnuu2GAAAA/UlEQVR4nO2YOw7DIAxAjcXSvVunHKOH6DWy5ixZuUYPkWNkyta9YyukpqoiIHwC2KhvTCzCk20CAPzhhcgx6HCfX6bn463L8j0TEjII9deLOeDzvoSgKCK0QU0L5BbE0lK/sbaSrS42REiVkhM1pExl6SKmZCUkkCq1jqHlnGNFLDoiNltHSIWiAhYdb7FtL9QQC5ETvjK1RGLlJLVyC+nL4OWestSKnp/rV4EcpXxAzlK9I2vJWyqqINds7dF+xloDoVEQGKOmxbr7+IrpAJ8jBBcQGkXuBcyPJ1CjO5/CMsapHJWjv6ylSF1OecxP5LzPYHHQpMJY8DaZJG+ptYIp9M3GwwAAAABJRU5ErkJggg==");
        background-repeat: no-repeat;
        background-size: 100% 100%;
        image-rendering: pixelated;
        z-index: 11;
        pointer-events: none;
      }
      .cloud-1 {
        top: 22px;
        left: calc(50% + 210px);
        width: 100px;
        height: 56px;
      }
      .cloud-2 {
        top: 66px;
        left: calc(50% + 168px);
        width: 64px;
        height: 36px;
      }
      @media (max-width: 900px) {
        .cloud-1,
        .cloud-2 {
          display: none;
        }
      }

      /* No content-visibility on the page sections, on purpose.
         It used to be `content-visibility: auto; contain-intrinsic-size: auto
         560px` on the plain post-lists and footers, to take off-screen layout
         off the load path. But one 560px placeholder can't fit sections that
         really measure 14px (footer) to 1094px (Let's talk): on a first visit
         the page changed length by ~1090px (desktop) / ~760px (phone) WHILE it
         was being scrolled. Each correction re-laid out the whole document
         from the root mid-scroll; with Lenis that is a visible freeze. A/B, full
         wheel scroll at 6x CPU, 4 runs: worst freeze 256ms -> 112ms, total
         time frozen 2.06s -> 1.02s, 15ms+ layouts 12 -> 3. The sections are
         small text lists, so rendering them up front is cheap. (`auto` only
         remembers a size after the first render, so it never helped the first
         pass, which is every recruiter's.) The photo-scroller tiles keep theirs
         (see .photo-scroller > *): that strip is horizontal and its height
         doesn't depend on them, so it can't change the page length. */

      /* Sparrow: flies in, perches, idles, flies off */
      .bird {
        position: fixed;
        left: 0;
        bottom: 8px;
        z-index: 40;
        pointer-events: none;
        transform: translateX(-70px);
        will-change: transform;
      }
      .bird-lift {
        transition: transform 0.55s cubic-bezier(0.22, 1, 0.36, 1);
        transform: translateY(0);
      }
      .bird-lift.is-up {
        transform: translateY(-30px);
      }
      /* Sprite frames advance by translating the full strip behind an
         overflow window (compositor-friendly). Animating background-position
         repainted the sprite on the main thread every frame. */
      .bird-sprite {
        width: 50px;
        height: 40px;
        overflow: hidden;
      }
      .bird-strip {
        display: block;
        width: 150px;
        height: 40px;
        background: url("../images/runner.png?v=4") 0 0 / 150px 40px no-repeat;
        image-rendering: pixelated;
        transform: translateX(-100px); /* resting frame */
      }
      .bird-sprite.is-flying {
        animation: birdBob 1.1s ease-in-out infinite;
      }
      .bird-sprite.is-flying .bird-strip {
        animation: birdFlap 0.28s steps(2) infinite;
      }
      @keyframes birdFlap {
        from {
          transform: translateX(0);
        }
        to {
          transform: translateX(-100px);
        }
      }
      @keyframes birdBob {
        0%,
        100% {
          transform: translateY(0);
        }
        50% {
          transform: translateY(-4px);
        }
      }
      /* The glide across the screen is a CSS animation (JS only starts/stops
         it), so a flying bird costs zero main-thread work per frame. JS sets a
         negative animation-delay to resume the loop from where the bird sat. */
      .bird.is-gliding {
        animation: birdGlide var(--glide-dur, 26s) linear infinite;
      }
      @keyframes birdGlide {
        from {
          transform: translateX(-70px);
        }
        to {
          transform: translateX(calc(100vw + 80px));
        }
      }
      @media (prefers-reduced-motion: reduce) {
        .bird {
          display: none;
        }
      }
      /* Visuals tiles: white-matted framed card look */
      .photo-scroller img {
        width: 184px;
        height: 248px;
        border: 5px solid var(--photo-frame, #fff);
        border-radius: 10px;
        background: var(--photo-frame, #fff);
        box-shadow:
          0 0 0 1px rgba(0, 0, 0, 0.07),
          0 2px 5px rgba(0, 0, 0, 0.04);
      }
      .video-scroller {
        align-items: center;
      }
      .video-scroller .video-tile {
        flex: 0 0 auto;
        display: block;
        line-height: 0;
      }
      .video-scroller video {
        width: 300px;
        height: 169px;
        object-fit: cover;
        border: 5px solid var(--photo-frame, #fff);
        border-radius: 10px;
        background: #000;
        box-shadow:
          0 0 0 1px rgba(0, 0, 0, 0.07),
          0 2px 5px rgba(0, 0, 0, 0.04);
        display: block;
      }
      /* "Seeing stars" effect while the bird is perched */
      .bird-stars {
        position: absolute;
        left: 34px;
        top: -3px;
        width: 0;
        height: 0;
        opacity: 0;
        pointer-events: none;
        transition: opacity 0.2s ease;
      }
      .bird.is-sitting .bird-stars {
        opacity: 1;
      }
      .star {
        position: absolute;
        left: -4px;
        top: -4px;
        width: 9px;
        height: 9px;
        background: #f6c945;
        clip-path: polygon(
          50% 0%,
          61% 35%,
          98% 35%,
          68% 57%,
          79% 91%,
          50% 70%,
          21% 91%,
          32% 57%,
          2% 35%,
          39% 35%
        );
        animation: birdOrbit 1.8s linear infinite;
        animation-name: none; /* no idle layers; the orbit is named while perched */
      }
      .bird.is-sitting .star {
        animation-name: birdOrbit;
      }
      .star:nth-child(2) {
        animation-delay: -0.6s;
      }
      .star:nth-child(3) {
        animation-delay: -1.2s;
      }
      @keyframes birdOrbit {
        0% {
          transform: translate(15px, 0);
        }
        12.5% {
          transform: translate(10.6px, 3.5px);
        }
        25% {
          transform: translate(0, 5px);
        }
        37.5% {
          transform: translate(-10.6px, 3.5px);
        }
        50% {
          transform: translate(-15px, 0);
        }
        62.5% {
          transform: translate(-10.6px, -3.5px);
        }
        75% {
          transform: translate(0, -5px);
        }
        87.5% {
          transform: translate(10.6px, -3.5px);
        }
        100% {
          transform: translate(15px, 0);
        }
      }
      /* Pixel trees (bottom-right ground decoration) */
      body {
        position: relative;
      }
      .tree {
        position: absolute;
        bottom: 0;
        background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADYAAABWCAYAAACEsWWHAAADGklEQVR4nO2avYoUQRDH/11MLIeBTLAcclwgRkYbiJFP4SMY+hA+hA9g5EMYyQUbGRrIIcshE4kPIJzM4sBs2x9VNTU7t8X8YFm2v7b/Xf1R/QGsrKzMScACbN++uC/F7z58DWclbFsRZCkwYCFB7fVFMm33/beJwIATisqJyTEWKRUXsJCFri7bo9+3+85UXMAJxtBYVCwoJiVQIy7AgHef3txzKjmIutpsjtPd3RXzacQFTLTQy9fPjiqaq2ROVC2fVlzABCvlKpmqaE8pfZynNuZ2FWGEE6xHGnJjsf03Xmt1IIvul6xYJX5uSCNqaVqG1VRdUUtq3HHiNBA3Ydw6tYrk4lPhybDM5MGlwQJwrBP7jLHn0l5fHNL0DZ6aIRuJteLChwqW1jENN5+//RfWi2gFvqaJxSzHR0qURlwjHlv7rurv5RiPG64TrKWRbjksRKV+W0OSxFpR3LK15W8T61ngWmv8pzXvXGKZlJjcOGsT4yvnO4oX6JSrlPXYFaJ6Ut6NdPcdJNaSeOc1YdJuJ91hE86cNmNJekhbEss8VIqU9uuHBEn6t8Q716xT/Ww4fKbSSDP0AiTTPYeUkCFMu/9ruAnHrlR1y1KxlsYtk/YAmqNwiRdR63babknSDL04icBSOZL/zNElzvonrWNcgVbiauwil6o4xmr7H46fdzjRumyzIoadsPWSQ3EA92yc67wOYXPsDLpCg5i5VKVBXpsAequkzjS41toljEG1hKVWsWYQwxHUVep1lk5wx7icyApbymo1OuaNC9tisbh4liu5Pn2cxdTeCa6RisLizBpxS4jqYU3t8R6tdp88ZqqouDF31le1GnGWomZ9NbBlXJ5bMEXQgCoT9/hAI9hClMmrAQuRVq9xTvIyR3soZPFAbNG3VHOIOfnF3/PbP4fvzZNHR+HvP97M1rAEpxCcQnAKwSkEpxCcQnAKwSkEpxCcQnAKwSkEpxCcQnAKwSkEpxCcQnAKwSkEpxCcQnAKwSkEpwTLwn79+GLyiPPx01eT60VwCsEpjWVh3c89Hgq0dAVWVlZwlvwFBVK4H3rP8BgAAAAASUVORK5CYII=");
        background-repeat: no-repeat;
        background-size: 100% 100%;
        image-rendering: pixelated;
        z-index: 11;
        pointer-events: none;
      }
      .tree-1 {
        left: calc(50% + 162px);
        width: 70px;
        height: 111px;
      }
      .tree-2 {
        left: calc(50% + 132px);
        width: 50px;
        height: 80px;
      }
      @media (max-width: 900px) {
        .tree-1,
        .tree-2 {
          display: none;
        }
      }
      /* A butterfly fluttering around the trees (both modes) */
      .garden > * {
        position: absolute;
        bottom: 0;
        z-index: 12;
        image-rendering: pixelated;
        pointer-events: none;
      }
      .garden svg { width: 100%; height: 100%; display: block; shape-rendering: crispEdges; }
      .butterfly {
        left: calc(50% + 158px);
        bottom: 44px;
        width: 19px;
        height: 16px;
        z-index: 12;
        will-change: transform;
        animation: bfFlight 11s ease-in-out infinite;
      }
      .bf-inner { display: block; width: 100%; height: 100%; position: relative; animation: bfBob 0.9s ease-in-out infinite; }
      /* Wings are separate stacked layers (same viewBox) so the flap is a
         plain HTML transform  it runs on the compositor. Animating the paths
         INSIDE one svg forced style+layout on the main thread every frame,
         which kept the whole page's render pipeline awake at 60fps. */
      .bf-layer { position: absolute; inset: 0; }
      .bf-wl, .bf-wr { animation: bfFlap 0.16s ease-in-out infinite alternate; will-change: transform; }
      @keyframes bfFlap { from { transform: scaleX(1); } to { transform: scaleX(0.32); } }
      @keyframes bfBob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-1.5px); } }
      @keyframes bfFlight {
        0%   { transform: translate(0, 0) rotate(-5deg); }
        14%  { transform: translate(16px, -20px) rotate(7deg); }
        28%  { transform: translate(38px, -8px) rotate(-4deg); }
        42%  { transform: translate(22px, -34px) rotate(9deg); }
        58%  { transform: translate(-4px, -22px) rotate(-7deg); }
        72%  { transform: translate(10px, -42px) rotate(5deg); }
        86%  { transform: translate(30px, -16px) rotate(-3deg); }
        100% { transform: translate(0, 0) rotate(-5deg); }
      }
      @media (max-width: 900px) {
        /* Keep the butterfly on mobile (trees + clouds drop away). Tuck it into
           the empty top-right corner; the margins leave room for the full
           flight path so it never clips off-screen. */
        .butterfly {
          left: auto;
          right: 50px;
          top: 50px;
          bottom: auto;
        }
      }
      @media (prefers-reduced-motion: reduce) {
        .butterfly, .bf-inner, .bf-wl, .bf-wr { animation: none; }
        .butterfly { bottom: 70px; }
      }

      .project-year .metric-label {
        top: 16px;
        color: var(--grey3);
        font-family: "Hanken Grotesk", sans-serif;
        font-size: 10px;
        font-weight: 400;
        letter-spacing: 0;
      }

      /* Work index mirrors the supplied editorial two-column reference. */
      .work-row {
        column-gap: 32px;
      }
      .work-row .work-identity {
        width: 96px;
        min-width: 96px;
      }
      .work-row .work-name {
        color: var(--accent);
        font-weight: 500;
      }
      .work-row .work-location {
        top: 19px;
        white-space: nowrap;
      }
      .work-row .project-title {
        font-size: 15px;
        line-height: 1.35;
      }
      .work-row .project-description {
        margin-top: 2px;
        font-size: 14px;
        line-height: 1.5;
      }
      .profile-info h1 {
        display: inline-flex;
        align-items: center;
        gap: 5px;
      }
      .profile-avatar {
        width: 106px;
        height: 106px;
        border-radius: 12px;
        border: 4px solid var(--photo-frame, #fff);
        box-shadow:
          0 0 0 1px rgba(0, 0, 0, 0.08),
          0 2px 7px rgba(0, 0, 0, 0.06);
      }
      /* Chat */
      .chat {
        display: flex;
        flex-direction: column;
        gap: 4px;
        margin-top: 16px;
      }
      .cb {
        margin: 0;
        font-size: 14px;
        line-height: 1.4;
        padding: 9px 14px;
        border-radius: 18px;
        width: fit-content;
        max-width: 80%;
      }
      .cb-them {
        align-self: flex-start;
        background: var(--chat-bubble, #f0f1f3);
        color: var(--grey1);
        border-bottom-left-radius: 5px;
      }
      .cb-you {
        align-self: flex-end;
        background: var(--accent);
        color: #fff;
        border-bottom-right-radius: 5px;
      }
      .chat-panel {
        align-self: flex-start;
        width: min(82%, 320px);
        display: flex;
        flex-direction: column;
        gap: 7px;
        margin: 2px 0;
      }
      .chat-email-panel {
        width: min(82%, 360px);
      }
      .chat-email-panel .chat-line {
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: 16px;
      }
      .chat-copy-icon {
        display: grid;
        place-items: center;
        flex: 0 0 28px;
        width: 28px;
        height: 28px;
        border: 0;
        color: var(--grey2);
        background: transparent;
      }
      .social-panel {
        flex-direction: row;
        width: auto;
        gap: 7px;
      }
      .chat-social {
        display: grid;
        place-items: center;
        width: 48px;
        height: 48px;
        border-radius: 8px;
        background: var(--chat-surface, #fff);
        box-shadow: inset 0 0 0 1px var(--surface-border, rgba(0, 0, 0, 0.1));
        color: var(--grey2);
        text-decoration: none;
        font-family: "Bricolage Grotesk", sans-serif;
        font-size: 20px;
        font-weight: 600;
      }
      .chat-line {
        background: var(--chat-bubble, #f0f1f3);
        color: var(--grey1);
        padding: 10px 14px;
        border-radius: 14px;
        font-size: 14px;
        word-break: break-all;
      }
      .chat-btn {
        display: flex;
        align-items: center;
        justify-content: center;
        gap: 7px;
        width: 100%;
        padding: 10px;
        border-radius: 12px;
        border: none;
        background: var(--chat-surface, #fff);
        box-shadow: inset 0 0 0 1px var(--surface-border, rgba(0, 0, 0, 0.1));
        color: var(--grey1);
        font-family: inherit;
        font-size: 14px;
        cursor: pointer;
        text-decoration: none;
        transition:
          background 0.15s ease,
          box-shadow 0.15s ease,
          transform 0.16s cubic-bezier(0.23, 1, 0.32, 1);
      }
      .chat-btn:hover {
        background: var(--chat-surface-hover, #fafbfc);
        box-shadow: inset 0 0 0 1px var(--surface-border-hover, rgba(0, 0, 0, 0.18));
      }
      .chat-btn:active {
        transform: scale(0.98);
      }
      .chat-btn svg {
        flex-shrink: 0;
      }
      .kbd {
        font-family: "JetBrains Mono", ui-monospace, monospace;
        font-size: 10px;
        padding: 1px 5px;
        border-radius: 5px;
        background: var(--chat-surface, #fff);
        color: var(--grey2);
        box-shadow:
          inset 0 0 0 1px var(--surface-border, rgba(0, 0, 0, 0.15)),
          0 1px 0 rgba(0, 0, 0, 0.08);
      }
      .copy-toast {
        position: fixed;
        bottom: 22px;
        left: 50%;
        transform: translateX(-50%) translateY(10px);
        background: var(--grey1);
        color: var(--body-bg, #fff);
        font-size: 13px;
        padding: 8px 15px;
        border-radius: 999px;
        opacity: 0;
        pointer-events: none;
        z-index: 1000;
        transition:
          opacity 0.2s ease-out,
          transform 0.2s cubic-bezier(0.22, 1, 0.36, 1);
      }
      .copy-toast.show {
        opacity: 1;
        transform: translateX(-50%) translateY(0);
      }
      /* === Premium type system (re-consolidated) + top nav === */
      :root {
        --accent: #3b5bff;
        --folder-surface: oklch(1 0 0);
        --folder-ink: oklch(0.18 0 0);
        --folder-muted: oklch(0.48 0 0);
        --folder-accent: oklch(0.53 0.24 269);
        color-scheme: light only;
      }
      .profile-info h1 {
        font-family: "Bricolage Grotesque", "Hanken Grotesk", sans-serif;
        font-weight: 600;
        letter-spacing: -0.03em;
      }
      .homepage .article > header {
        gap: 18px;
      }
      .homepage .article > header .profile-info h1 {
        font-size: 24px;
      }
      /* tiny uppercase-mono section labels (the editorial premium signal) */
      .post-list-title,
      .photo-scroller-label {
        font-family: "JetBrains Mono", ui-monospace, monospace;
        font-size: 11px;
        font-weight: 500;
        letter-spacing: 0.11em;
        text-transform: uppercase;
        color: var(--grey3);
      }
      .photo-scroller-caption {
        font-size: 13px;
        color: var(--grey3);
        margin: -8px 0 16px;
      }
      /* hero metrics: mono + cobalt */
      .project-year {
        font-family: "JetBrains Mono", ui-monospace, monospace;
        font-size: 13px;
      }
      .project-year > span:first-child {
        color: var(--accent);
        font-weight: 500;
        letter-spacing: -0.04em;
      }
      .project-year .metric-label {
        top: 16px;
        color: var(--grey3);
        font-family: "Hanken Grotesk", sans-serif;
        font-size: 10px;
        font-weight: 400;
        letter-spacing: 0;
      }
      #clock {
        font-family: "JetBrains Mono", ui-monospace, monospace;
        letter-spacing: -0.02em;
      }
      .chip-link:hover .chip-link__text {
        border-bottom-color: var(--accent);
      }
      .project-title-link:hover .project-title-text {
        border-bottom-color: var(--accent);
      }
      .show-more-btn:hover {
        color: var(--accent);
      }
      .homepage > article.article {
        gap: 13px;
      }
      /* Paragraph spacing after: 60% of the paragraph's own text size. */
      .homepage .article > p:not(.cb):not(.profile-role) {
        margin-bottom: 0.6em;
      }
      .homepage .article > p:not(.cb):not(.profile-role):last-of-type {
        margin-bottom: 0;
      }
      .cb-them + .cb-you,
      .cb-you + .cb-them,
      .cb-you + .chat-panel,
      .chat-panel + .cb-you,
      .chat-panel + .cb-them {
        margin-top: 13px;
      }
      .contact-list {
        display: grid;
        grid-template-columns: repeat(4, max-content);
        gap: 15px 34px;
        margin-top: 16px;
      }
      .contact-item {
        display: block;
      }
      .photo-scroller {
        padding-top: 10px;
        padding-bottom: 10px;
      }
      @media (max-width: 900px) {
        .cloud-1 {
          display: block;
          left: auto;
          right: 8px;
          top: 14px;
          width: 72px;
          height: 40px;
        }
        .cloud-2 {
          display: block;
          left: auto;
          right: 64px;
          top: 48px;
          width: 46px;
          height: 26px;
        }
        .tree-1 {
          display: block;
          left: auto;
          right: 8px;
          width: 54px;
          height: 86px;
        }
        .tree-2 {
          display: block;
          left: auto;
          right: 48px;
          width: 38px;
          height: 60px;
        }
      }

      /* Feature badges (inline, glass) */
      .badge {
        --c: #888888;
        position: relative;
        z-index: 1;
        display: inline-flex;
        align-items: center;
        gap: 3px;
        padding: 1px 7px 1px 8px;
        border-radius: 100px;
        font-family: "Hanken Grotesk", sans-serif;
        font-size: 10.5px;
        font-weight: 600;
        line-height: 1.5;
        letter-spacing: 0;
        white-space: nowrap;
        text-decoration: none;
        vertical-align: 1px;
        /* Darkened text so small badge labels clear WCAG AA on the light fill,
           while the border/background keep the bright category hue. */
        color: color-mix(in srgb, var(--c) 55%, #111);
        border: 1px solid var(--c);
        border-color: color-mix(in srgb, var(--c) 36%, transparent);
        background: rgba(255, 255, 255, 0.5);
        background:
          linear-gradient(
            180deg,
            rgba(255, 255, 255, 0.6),
            rgba(255, 255, 255, 0.12)
          ),
          color-mix(in srgb, var(--c) 14%, rgba(255, 255, 255, 0.5));
        /* no backdrop-filter: each one is a composited layer that re-blurs
           its backdrop every scroll frame, and over a flat page bg the blur
           is invisible  measured as pure GPU cost (layer audit). */
        box-shadow:
          inset 0 1px 0 rgba(255, 255, 255, 0.85),
          inset 0 -2px 3px color-mix(in srgb, var(--c) 9%, transparent),
          0 1px 3px color-mix(in srgb, var(--c) 16%, transparent);
        transition:
          transform 0.16s ease,
          box-shadow 0.16s ease,
          background 0.16s ease;
      }
      .badge:hover {
        transform: translateY(-1px);
        box-shadow:
          inset 0 1px 0 rgba(255, 255, 255, 0.95),
          inset 0 -2px 3px color-mix(in srgb, var(--c) 15%, transparent),
          0 4px 8px color-mix(in srgb, var(--c) 20%, transparent);
        background:
          linear-gradient(
            180deg,
            rgba(255, 255, 255, 0.8),
            rgba(255, 255, 255, 0.2)
          ),
          color-mix(in srgb, var(--c) 18%, rgba(255, 255, 255, 0.6));
      }
      /* Press feedback  after :hover so it wins while both apply. */
      .badge:active {
        transform: scale(0.96);
      }
      .badge-arrow {
        flex-shrink: 0;
        opacity: 0.85;
        transition: transform 0.16s ease;
      }
      .badge:hover .badge-arrow {
        transform: translate(1px, -1px);
      }
      .badge--pink {
        --c: #ec4899;
      }
      .badge--red {
        --c: #f43f5e;
      }
      .badge--blue {
        --c: #3b82f6;
      }
      .badge--orange {
        --c: #f97316;
      }
      .badge--green {
        --c: #10b981;
      }
      /* GitHub activity */
      .gh-activity {
        margin-top: 40px;
        position: relative;
      }
      /* Two pandas in the contribution wall (Minimal only):
         left one munches sugarcane, the shy one ducks in on hover. */
      .gh-peek {
        position: absolute;
        overflow: hidden;
        z-index: 2;
        pointer-events: none;
      }
      .gh-peek-eat { left: -15px; top: 56px; width: 18px; height: 24px; }
      .gh-peek-shy { left: 188px; top: 14px; width: 24px; height: 24px; }
      .gh-panda {
        display: block;
        width: 24px;
        height: 20px;
        image-rendering: pixelated;
        will-change: transform;
      }
      .gh-panda svg, .gh-cane svg { width: 100%; height: 100%; display: block; shape-rendering: crispEdges; }
      /* eating panda: sits still holding its cane; hides behind the wall on hover */
      .gh-cane {
        position: absolute;
        left: 5px;
        top: 1px;
        width: 7px;
        height: 23px;
        z-index: 3;
        image-rendering: pixelated;
        transform: rotate(6deg);
      }
      .gh-eat-inner {
        position: absolute;
        inset: 0;
        transition: transform 0.45s cubic-bezier(0.5, 0, 0.2, 1);
      }
      .gh-activity:hover .gh-eat-inner { transform: translateX(30px); }
      /* shy panda: peeked by default, ducks inside when the chart is hovered */
      .gh-shy-inner {
        display: block;
        transform: translateY(24%);
        transition: transform 0.45s cubic-bezier(0.5, 0, 0.2, 1);
      }
      .gh-activity:hover .gh-shy-inner { transform: translateY(125%); }
      @media (prefers-reduced-motion: reduce) {
        .gh-eat-inner, .gh-shy-inner { transition: none; }
      }

      .gh-head {
        display: flex;
        align-items: baseline;
        justify-content: space-between;
        gap: 12px;
        margin-bottom: 16px;
      }
      .gh-link {
        font-family: "Hanken Grotesk", sans-serif;
        font-size: 13px;
        color: var(--grey2);
        text-decoration: none;
        display: inline-flex;
        align-items: center;
        gap: 4px;
        transition: color 0.18s ease;
      }
      .gh-link:hover {
        color: var(--accent);
      }
      .gh-link svg {
        transition: transform 0.18s ease;
      }
      .gh-link:hover svg {
        transform: translate(1px, -1px);
      }
      .gh-graph {
        display: grid;
        grid-template-rows: repeat(7, 8px);
        grid-auto-flow: column;
        grid-auto-columns: 8px;
        gap: 2px;
        overflow-x: auto;
        scrollbar-width: none;
        min-height: 68px;
      }
      .gh-graph::-webkit-scrollbar {
        display: none;
      }
      .gh-cell {
        border-radius: 2px;
        background: var(--gh-empty, #eceef3);
      }
      .gh-pad {
        background: transparent;
      }
      /* One accent drives the whole scale: each level is --accent laid over the
         empty-cell colour at 30/52/76/100%. It follows the theme on its own —
         the old hand-picked hexes were never overridden for dark, so dark mode
         was showing the light ramp. The hex lines are fallbacks for browsers
         without color-mix(). */
      .gh-l0 {
        background: var(--gh-empty, #eceef3);
      }
      .gh-l1 {
        background: #c6d2ff;
        background: color-mix(in srgb, var(--accent) 30%, var(--gh-empty, #eceef3));
      }
      .gh-l2 {
        background: #93a8ff;
        background: color-mix(in srgb, var(--accent) 52%, var(--gh-empty, #eceef3));
      }
      .gh-l3 {
        background: #6079ff;
        background: color-mix(in srgb, var(--accent) 76%, var(--gh-empty, #eceef3));
      }
      .gh-l4 {
        background: var(--accent);
      }
      .gh-foot {
        display: flex;
        align-items: center;
        gap: 3px;
        margin-top: 12px;
        font-family: "Hanken Grotesk", sans-serif;
        font-size: 10.5px;
        color: var(--grey3);
      }
      .gh-foot-label {
        margin: 0 3px;
      }
      .gh-key {
        width: 8px;
        height: 8px;
        border-radius: 2px;
        display: inline-block;
      }
      .gh-total {
        margin-right: auto;
        font-variant-numeric: tabular-nums;
      }

      /* Column-by-column reveal the first time the grid scrolls into view.
         A curtain in the page colour sits over the grid and shrinks off to
         the right a column every 12ms. It animates transform, so it runs on
         the compositor and keeps stepping even while the main thread is busy
         (a clip-path version froze mid-reveal after a mode switch). The page
         behind the grid is a flat --body-bg, so the curtain is invisible.
         Pandas sit above it: .gh-body makes no stacking context. */
      .gh-body::before {
        content: "";
        position: absolute;
        z-index: 1;
        left: 0;
        right: 0;
        top: 0;
        height: 68px; /* the grid: 7 rows of 8px + 6 gaps of 2px */
        background: var(--body-bg);
        pointer-events: none;
        transform-origin: 100% 50%;
        transform: scaleX(0);
      }
      .gh-body.gh-wait::before {
        transform: scaleX(1);
      }
      .gh-body.gh-sweep::before {
        animation: gh-sweep calc(var(--gh-cols, 50) * 12ms) steps(var(--gh-cols, 50), end) both;
      }
      @keyframes gh-sweep {
        from { transform: scaleX(1); }
        to { transform: scaleX(0); }
      }

      /* Day tooltip. Lives on <body> so it can clamp to the viewport edge. */
      .gh-tip {
        position: fixed;
        left: 0;
        top: 0;
        z-index: 1000;
        pointer-events: none;
        white-space: nowrap;
        padding: 4px 8px;
        border-radius: 8px;
        background: var(--grey1);
        color: var(--body-bg);
        font: 500 11px/1.4 "Hanken Grotesk", sans-serif;
        box-shadow: 0 4px 14px rgba(0, 0, 0, 0.14);
        /* display:none while hidden, not just opacity:0. Chrome gives every
           position:fixed element its own GPU layer, so an invisible tooltip
           would sit on the GPU for the whole visit (and force the repo row it
           overlapped onto one too). allow-discrete + @starting-style keep the
           fade in and out; browsers without them just show and hide it. */
        display: none;
        opacity: 0;
        scale: 0.94;
        transform-origin: 50% 100%;
        transition:
          opacity 0.14s cubic-bezier(0.22, 1, 0.36, 1),
          scale 0.14s cubic-bezier(0.22, 1, 0.36, 1),
          display 0.14s allow-discrete;
      }
      .gh-tip.is-on {
        display: block;
        opacity: 1;
        scale: 1;
      }
      @starting-style {
        .gh-tip.is-on {
          opacity: 0;
          scale: 0.94;
        }
      }

      /* "Most active in" sheet. Collapsed it is a 46px bar under the legend;
         open, it grows up over the grid. The bar's space is reserved up front
         (padding on .gh-body) so the fetch landing never shifts the page. */
      /* The sheet is tinted from --accent, so it reads as part of the cobalt
         grid rather than a neutral box, and follows the theme on its own. */
      .gh-activity {
        --gh-sheet: #eef1ff;
        --gh-sheet: color-mix(in srgb, var(--accent) 8%, var(--body-bg, #fff));
      }
      .gh-body {
        position: relative;
        padding-bottom: 58px;
      }
      .gh-activity.gh-no-repos .gh-body {
        padding-bottom: 0;
      }
      .gh-repos {
        position: absolute;
        inset: -8px -10px 0;
        z-index: 4;
        display: flex;
        flex-direction: column;
        overflow: hidden;
        border-radius: 14px;
        background: var(--gh-sheet);
        /* clip-path, not height: it animates without re-running layout, and
           the clipped-away part stops taking hits, so the grid under a closed
           sheet still gets its tooltips */
        clip-path: inset(calc(100% - 46px) 0 0 0 round 14px);
        transition: clip-path 0.5s cubic-bezier(0.22, 1, 0.36, 1);
      }
      .gh-repos.is-open {
        clip-path: inset(0 0 0 0 round 14px);
      }
      .gh-repos[hidden] {
        display: none;
      }
      .gh-repo-bar {
        margin-top: auto;
        flex: 0 0 46px;
        display: flex;
        align-items: center;
        gap: 10px;
        padding: 0 8px 0 14px;
      }
      .gh-repo-label {
        flex: 1;
        min-width: 0;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
        font-family: "Hanken Grotesk", sans-serif;
        font-size: 13px;
        color: var(--grey2);
      }
      .gh-stack {
        display: flex;
      }
      .gh-stack .gh-av + .gh-av {
        margin-left: -8px;
      }
      .gh-repos.is-open .gh-stack {
        visibility: hidden;
      }
      .gh-av {
        flex: 0 0 24px;
        width: 24px;
        height: 24px;
        display: grid;
        place-items: center;
        overflow: hidden;
        border-radius: 50%;
        background: var(--accent);
        /* body-bg, not white: 5.2:1 on light cobalt, 6.3:1 on the dark one */
        color: var(--body-bg);
        box-shadow: 0 0 0 2px var(--gh-sheet);
        font: 600 11px/1 "Hanken Grotesk", sans-serif;
        text-transform: uppercase;
      }
      .gh-av img {
        width: 100%;
        height: 100%;
        object-fit: cover;
      }
      .gh-repo-toggle {
        flex: 0 0 30px;
        width: 30px;
        height: 30px;
        display: grid;
        place-items: center;
        padding: 0;
        border: 0;
        border-radius: 50%;
        background: var(--body-bg);
        color: var(--accent);
        cursor: pointer;
        transition:
          background-color 0.18s ease,
          scale 0.16s cubic-bezier(0.34, 1.1, 0.64, 1);
      }
      .gh-repo-toggle:hover {
        background: color-mix(in srgb, var(--accent) 12%, var(--body-bg));
      }
      .gh-repo-toggle:active {
        scale: 0.94;
      }
      .gh-repo-toggle:focus-visible {
        outline: 2px solid var(--accent);
        outline-offset: 2px;
      }
      .gh-repo-toggle svg {
        width: 26px;
        height: 26px;
        transition: rotate 0.55s cubic-bezier(0.34, 1.3, 0.64, 1);
      }
      .gh-repos.is-open .gh-repo-toggle svg {
        rotate: 180deg;
      }
      .gh-repo-list {
        /* after the toggle in the DOM (so Tab goes toggle -> rows), but
           drawn above the bar */
        order: -1;
        list-style: none;
        margin: 0;
        padding: 6px 4px 0;
        /* Closed rows don't paint at all, not just sit at opacity 0: faded
           content still counts for GPU overlap tests, and the rows over the
           reveal's curtain were being promoted to layers. The delay lets the
           close fade finish first. */
        visibility: hidden;
        transition: visibility 0s linear 0.2s;
      }
      .gh-repos.is-open .gh-repo-list {
        visibility: visible;
        transition-delay: 0s;
      }
      .gh-repo {
        display: flex;
        align-items: center;
        gap: 12px;
        padding: 5px 10px;
        border-radius: 10px;
        color: var(--grey1);
        text-decoration: none;
        font-family: "Hanken Grotesk", sans-serif;
        font-size: 13px;
        transition: background-color 0.18s ease;
      }
      .gh-repo:hover {
        background: color-mix(in srgb, var(--accent) 10%, transparent);
      }
      .gh-repo:focus-visible {
        outline: 2px solid var(--accent);
        outline-offset: -2px;
      }
      /* Row avatars are the stack avatars' landing spots (see the FLIP in
         script.js), so they switch instantly; only the text fades in. */
      .gh-repo .gh-av {
        visibility: hidden;
      }
      .gh-repos.is-open .gh-repo .gh-av {
        visibility: visible;
      }
      .gh-repo-name {
        flex: 1;
        min-width: 0;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
      }
      .gh-repo-count {
        color: var(--grey2);
        font-size: 12px;
        font-variant-numeric: tabular-nums;
      }
      .gh-repo-name,
      .gh-repo-count {
        opacity: 0;
        translate: 0 16px;
        transition:
          opacity 0.18s ease,
          translate 0.18s ease;
      }
      .gh-repos.is-open .gh-repo-name,
      .gh-repos.is-open .gh-repo-count {
        opacity: 1;
        translate: 0 0;
        transition:
          opacity 0.4s cubic-bezier(0.22, 1, 0.36, 1),
          translate 0.55s cubic-bezier(0.34, 1.3, 0.64, 1);
        transition-delay: calc(80ms + var(--i, 0) * 45ms);
      }
      @media (prefers-reduced-motion: reduce) {
        .gh-repos,
        .gh-repo-toggle svg,
        .gh-repo-name,
        .gh-repo-count,
        .gh-tip {
          transition: none !important;
        }
      }

      /* ============ MODE TOGGLE ============ */
      .page-shell {
        position: relative;
        z-index: 0;
        transition: opacity 0.25s ease;
      }
      .page-shell.is-switching {
        opacity: 0;
      }
      .mode-toggle {
        position: absolute;
        top: 22px;
        left: 16px;
        z-index: 30;
        display: inline-flex;
        align-items: center;
        padding: 2.5px;
        border-radius: 999px;
        background: var(--toggle-track, #f1f1f3);
        box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.06);
        width: fit-content;
      }
      .mode-btn {
        position: relative;
        z-index: 1;
        border: none;
        background: none;
        cursor: pointer;
        font-family: "Hanken Grotesk", sans-serif;
        font-size: 11.5px;
        font-weight: 600;
        padding: 4px 14px;
        border-radius: 999px;
        color: var(--grey3);
        transition: color 0.25s ease;
      }
      .mode-btn.is-active {
        color: var(--grey1);
      }
      .mode-thumb {
        position: absolute;
        top: 3px;
        left: 3px;
        height: calc(100% - 6px);
        width: calc(50% - 3px);
        border-radius: 999px;
        background: var(--toggle-thumb, #fff);
        box-shadow: 0 1px 4px rgba(0, 0, 0, 0.12);
        transition: transform 0.3s cubic-bezier(0.34, 1.1, 0.64, 1);
      }
      body.creative-mode .mode-thumb {
        transform: translateX(100%);
      }

      /* ============ MODE SWITCHING ============ */
      .creative-only {
        display: none;
      }
      body.creative-mode .article,
      body.creative-mode .post-list,
      body.creative-mode .photo-scroller-section,
      body.creative-mode .gh-activity,
      body.creative-mode .race-track,
      body.creative-mode .site-footer {
        display: none;
      }
      .creative-chat {
        display: none;
      }
      body.creative-mode .post-list.creative-chat {
        display: flex;
        margin-top: 88px;
      }
      body.creative-mode .photo-scroller-section.creative-shared {
        display: block;
        margin-top: 88px;
      }
      body.creative-mode .creative-only {
        display: block;
        /* Fade + rise the creative view in on switch, so it doesn't pop. */
        animation: fvIn 0.4s cubic-bezier(0.22, 1, 0.36, 1) both;
      }
      @media (prefers-reduced-motion: reduce) {
        body.creative-mode .creative-only {
          animation: none;
        }
      }
      body.creative-mode .page-shell {
        max-width: 540px;
        /* Width is intentionally not animated: layout sizing can't run on the
           GPU and a scale() would distort text. The content swap is instant,
           so the page snaps cleanly with it. */
      }

      /* ============ CREATIVE HERO ============ */
      .creative-hero {
        margin-top: -5px;
      }
      .ch-top {
        display: flex;
        align-items: flex-start;
        justify-content: space-between;
        gap: 20px;
        flex-wrap: wrap;
      }
      .ch-role {
        font-family: "JetBrains Mono", ui-monospace, monospace;
        font-size: 13px;
        line-height: 1.5;
        color: var(--grey2);
      }
      .ch-name {
        display: flex;
        flex-wrap: wrap;
        gap: 0 0.3em;
        margin: 40px 0 0;
        font-family: "Bricolage Grotesque", sans-serif;
        font-weight: 700;
        font-size: clamp(40px, 9vw, 84px);
        line-height: 0.95;
        letter-spacing: -0.035em;
        color: var(--accent);
      }
      .ch-tag {
        margin-top: 16px;
        font-family: "Bricolage Grotesque", sans-serif;
        font-size: clamp(16px, 2.4vw, 22px);
        font-weight: 500;
        color: var(--grey1);
      }

      /* ============ FOLDERS ============ */
      /* Folder cards, exact bricx-style: the white surface is a FOLDER-TAB
         silhouette - a raised tab (holding project-favicon chips) joined to
         the body by a concave fillet - with the category screenshots fanned
         out of the top-right opening behind it. Surface colour is one var
         (--fcard) so the tab, fillet and body always match; it reads as a
         card on both themes without new tokens. Favicon chips mirror bricx
         tool chips: rounded squares, faint fill, hairline border. */
      .folders {
        display: grid;
        grid-template-columns: repeat(2, 1fr);
        gap: 8px 20px;
        margin-top: 28px;
        padding-top: 8px;
      }
      body.creative-mode .folders {
        margin-top: 58px;
        gap: 54px 24px;
      }
      .folder-group-title {
        grid-column: 1 / -1;
        margin-bottom: -30px;
        font-family: "JetBrains Mono", ui-monospace, monospace;
        font-size: 11px;
        font-weight: 500;
        letter-spacing: 0.11em;
        text-transform: uppercase;
        color: var(--grey3);
      }
      .folder-group-title--clients {
        margin-top: 14px;
      }
      .work-folder {
        padding-top: 98px;
      }
      .work-folder .folder-tabzone {
        width: 56%;
        height: 58px;
        padding: 13px 14px 8px;
        align-items: flex-start;
        flex-direction: column;
        gap: 1px;
      }
      .work-folder-name {
        color: var(--folder-accent);
        font-family: "JetBrains Mono", ui-monospace, monospace;
        font-size: 14px;
        font-weight: 500;
      }
      .work-folder-location {
        color: var(--folder-muted);
        font-size: 11px;
      }
      .work-folder .folder-label {
        min-height: 142px;
        padding: 18px 16px 20px;
      }
      .work-folder .folder-name {
        font-size: 17px;
        line-height: 1.3;
      }
      .work-folder .folder-desc {
        display: -webkit-box;
        overflow: hidden;
        -webkit-line-clamp: 3;
        -webkit-box-orient: vertical;
      }
      .work-detail {
        display: grid;
        gap: 12px;
      }
      .work-detail img {
        width: 100%;
        aspect-ratio: 16 / 9;
        object-fit: cover;
        border-radius: 8px;
      }
      .work-detail h3 {
        font-family: "Bricolage Grotesk", sans-serif;
        font-size: 20px;
        color: var(--grey1);
      }
      .work-detail p {
        color: var(--grey2);
        line-height: 1.55;
      }

      .creative-chat .chat > * {
        opacity: 0;
        transform: translateY(12px) scale(0.98);
      }
      .creative-chat.is-revealed .chat > * {
        animation: chatBubbleIn 0.42s cubic-bezier(0.22, 1, 0.36, 1) forwards;
        animation-delay: calc(var(--chat-i, 0) * 0.16s);
      }
      @keyframes chatBubbleIn {
        to { opacity: 1; transform: none; }
      }
      .folder {
        --fcard: var(--folder-surface);
        position: relative;
        display: flex;
        flex-direction: column;
        text-align: left;
        /* full button reset (there must be exactly ONE .folder block that
           touches padding - a duplicate reset once collapsed the stage) */
        -webkit-appearance: none;
        appearance: none;
        border: none;
        background: none;
        font: inherit;
        cursor: pointer;
        padding: 94px 0 0; /* stage: what shows of the sheets above the mouth */
        text-decoration: none;
        /* drop-shadow follows the tab silhouette (box-shadow would draw a
           rectangle straight through the notch) */
        filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.05))
          drop-shadow(0 10px 22px rgba(0, 0, 0, 0.07));
        transition: transform 0.28s cubic-bezier(0.34, 1.2, 0.64, 1);
      }
      .folder:hover {
        transform: translateY(-4px);
      }
      .folder-sheets {
        position: absolute;
        top: 0;
        left: 13%;
        right: 2px;
        height: 94px;
        z-index: 0;
      }
      .fsheet {
        position: absolute;
        bottom: 6px;
        width: 84%;
        aspect-ratio: 16/11;
        border-radius: 12px;
        border: 3px solid #fff;
        background: #fff center 28% / cover no-repeat;
        /* per-sheet depth-graded shadow set on -1/-2/-3 below */
        transform-origin: bottom center;
        transition: transform 0.42s cubic-bezier(0.34, 1.3, 0.64, 1);
      }
      /* Deep tuck: bottoms sit far down behind the pocket so only the papers'
         tops peek above the folder mouth - the rest lives inside. */
      .fsheet-1 { background-image: var(--img1); left: 1%; bottom: -94px; transform: rotate(-5deg) scale(0.93); box-shadow: 0 10px 22px rgba(0,0,0,0.12); z-index: 1; }
      .fsheet-2 { background-image: var(--img2); left: 14%; bottom: -102px; transform: rotate(4deg) scale(0.965); box-shadow: 0 7px 16px rgba(0,0,0,0.13); z-index: 2; }
      .fsheet-3 { background-image: var(--img3); left: 7%; bottom: -110px; transform: rotate(-1deg); box-shadow: 0 4px 12px rgba(0,0,0,0.16); z-index: 3; }
      .folder:hover .fsheet-1 { transform: rotate(-7deg) scale(0.93) translateY(-11px); }
      .folder:hover .fsheet-2 { transform: rotate(6deg) scale(0.965) translateY(-15px); }
      .folder:hover .fsheet-3 { transform: rotate(-1deg) translateY(-20px); }
      /* The raised tab: chips live here, like bricx notch chips. It hugs the
         chips (fit-content, doesn't stretch to card width) so the curve
         starts right after them instead of after a wide empty bar. */
      .folder-tabzone {
        position: relative;
        z-index: 2;
        align-self: flex-start;
        display: flex;
        align-items: flex-end; /* chips sit low in the tab, near the body seam */
        gap: 7px;
        width: fit-content;
        height: 66px;
        padding: 0 15px 9px 11px; /* 9px bottom lifts them off the seam; the
                                     taller tab leaves real breathing room above */
        background: var(--fcard);
        border-radius: 18px 18px 0 0;
        box-shadow: inset 0 1px 0 color-mix(in srgb, #fff 55%, transparent);
      }
      /* Concave fillet: the signature folder-tab curve. A generous 26px
         quarter-circle carved from a box at the tab's bottom-right so the
         raised tab sweeps smoothly down into the body. The 1px feather
         (25px->26px) keeps the arc crisp but anti-aliased, no jaggies. */
      .folder-tabzone::after {
        content: "";
        position: absolute;
        left: 100%;
        bottom: 0;
        width: 26px;
        height: 26px;
        background: radial-gradient(
          circle 26px at 100% 0%,
          transparent 0 25px,
          var(--fcard) 26px
        );
      }
      .fchip {
        width: 46px;
        height: 46px;
        display: grid;
        place-items: center;
        border-radius: 13px;
        background: color-mix(in srgb, var(--grey1) 5%, var(--body-bg));
        border: 1px solid var(--border);
      }
      .fchip img {
        width: 25px;
        height: 25px;
        border-radius: 6px;
        display: block;
      }
      /* The card body: name + count, continuing the tab surface */
      .folder-label {
        position: relative;
        z-index: 1;
        display: flex;
        flex-direction: column;
        gap: 2px;
        background: var(--fcard);
        border-radius: 0 16px 16px 16px;
        padding: 14px 16px 20px;
        box-shadow: inset 0 1px 0 color-mix(in srgb, #fff 55%, transparent);
      }
      .folder-name {
        font-family: "Bricolage Grotesque", sans-serif;
        font-weight: 600;
        font-size: 19px;
        color: var(--folder-ink);
      }
      /* Visual order is name -> desc -> count, while the DOM keeps the old
         name/sub sequence (flex order does the swap - no markup churn for
         anything that reads .folder-sub). */
      .folder-desc {
        order: 2;
        margin-top: 3px;
        font-size: 13px;
        line-height: 1.5;
        letter-spacing: -0.01em;
        color: var(--folder-muted);
      }
      .folder-sub {
        order: 3;
        margin-top: 7px;
        font-size: 11.5px;
        color: var(--grey3);
      }
      @media (prefers-reduced-motion: reduce) {
        .folder,
        .folder:hover,
        .folder:hover .fsheet-1,
        .folder:hover .fsheet-2,
        .folder:hover .fsheet-3 {
          transform: none;
          transition: none;
        }
      }

      /* ============ CREATIVE FOOTER ============ */
      .creative-footer {
        margin-top: 88px;
      }
      .cf-status {
        display: inline-flex;
        align-items: center;
        gap: 8px;
        font-family: "Hanken Grotesk", sans-serif;
        font-size: 13px;
        color: var(--grey2);
      }
      .cf-dot {
        position: relative;
        width: 8px;
        height: 8px;
        border-radius: 50%;
        background: #22c55e;
      }
      .cf-dot::after {
        content: "";
        position: absolute;
        inset: 0;
        border-radius: 50%;
        background: rgba(34, 197, 94, 0.45);
        animation: nowPulse 2s ease-out infinite;
      }
      /* Ring expands via transform/opacity only (GPU)  no box-shadow paint. */
      @keyframes nowPulse {
        0% {
          transform: scale(1);
          opacity: 0.6;
        }
        70%,
        100% {
          transform: scale(2.6);
          opacity: 0;
        }
      }
      .cf-name {
        margin: 12px 0 18px;
        font-family: "Bricolage Grotesque", sans-serif;
        font-weight: 700;
        font-size: clamp(34px, 7vw, 72px);
        letter-spacing: -0.03em;
        line-height: 1;
        color: var(--grey1);
      }
      .cf-links {
        display: flex;
        flex-wrap: wrap;
        gap: 20px;
      }
      .cf-links a {
        font-family: "Hanken Grotesk", sans-serif;
        font-size: 15px;
        font-weight: 600;
        color: var(--grey2);
        text-decoration: none;
        border-bottom: 1px solid transparent;
        transition:
          color 0.18s ease,
          border-color 0.18s ease;
      }
      .cf-links a:hover {
        color: var(--accent);
        border-bottom-color: var(--accent);
      }
      @media (prefers-reduced-motion: reduce) {
        .cf-dot,
        .cf-dot::after,
        .mode-thumb,
        .folder {
          animation: none;
          transition: none;
        }
      }

      .fv-videos {
        margin-left: 0;
        margin-right: 0;
        padding-left: 0;
        padding-right: 0;
        margin-top: 4px;
      }
      /* Achievement screenshots: landscape tiles inside the photo scroller */
      .shots-scroller img {
        width: 300px;
        height: 169px;
        object-fit: cover;
        object-position: center top;
      }
      .ch-blurb {
        margin-top: 14px;
        max-width: 60ch;
        font-family: "Hanken Grotesk", sans-serif;
        font-size: 15px;
        line-height: 1.5;
        color: var(--grey2);
      }
      /* Inline links in the intro copy */
      .article p a {
        color: var(--accent);
        text-decoration: underline;
        text-underline-offset: 2px;
      }
      .article p a:hover {
        color: var(--grey1);
      }
      /* Figma / Framer / Lovable icon links (Creative only) */
      .build-links {
        display: flex;
        gap: 18px;
        margin-top: 18px;
      }
      .build-links a {
        display: inline-flex;
        color: var(--grey1);
        transition: color 0.2s ease, transform 0.2s ease;
      }
      .build-links a:hover {
        color: var(--accent);
        transform: translateY(-2px);
      }
      /* open-in-place views */
      .folders[hidden] {
        display: none;
      }
      .folder-views {
        margin-top: 8px;
      }
      .folder-view[hidden] {
        display: none;
      }
      .folder-view {
        margin-top: 64px;
        animation: fvIn 0.32s cubic-bezier(0.22, 1, 0.36, 1);
      }
      .fv-card--nolink {
        cursor: default;
      }
      .fv-card--nolink:hover {
        transform: none;
        border-color: var(--surface-border, #e7e7ec);
        box-shadow: none;
      }
      @keyframes fvIn {
        from {
          opacity: 0;
          transform: translateY(10px);
        }
        to {
          opacity: 1;
          transform: none;
        }
      }
      .fv-back {
        -webkit-appearance: none;
        appearance: none;
        border: none;
        background: none;
        cursor: pointer;
        padding: 0;
        font-family: "Hanken Grotesk", sans-serif;
        font-size: 13px;
        font-weight: 600;
        color: var(--grey3);
        display: inline-flex;
        align-items: center;
        gap: 5px;
        transition: color 0.18s ease;
      }
      .fv-back:hover {
        color: var(--accent);
      }
      .fv-head {
        margin: 22px 0 24px;
      }
      .fv-title {
        font-family: "Bricolage Grotesque", sans-serif;
        font-weight: 700;
        font-size: 26px;
        letter-spacing: -0.02em;
        color: var(--grey1);
        outline: none;
      }
      .fv-desc {
        margin-top: 7px;
        font-size: 14px;
        color: var(--grey2);
        max-width: 56ch;
      }
      .fv-grid {
        display: flex;
        flex-direction: column;
        gap: 10px;
      }
      /* A card paired with its own case-study link (kept grouped in the grid). */
      .fv-card-group {
        display: flex;
        flex-direction: column;
        gap: 7px;
      }
      .fv-case-study {
        margin-top: 0;
        padding-left: 4px;
      }
      /* Sibling links that belong to one archive card (e.g. the four pages of
         the desi series) sit under it, aligned with the case-study link. */
      .fv-card-group .os-links {
        margin-top: 0;
        padding-left: 4px;
        flex-wrap: wrap;
        gap: 14px;
      }
      .fv-card {
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: 12px;
        padding: 13px 15px;
        border: 1px solid var(--surface-border, rgba(0, 0, 0, 0.08));
        border-radius: 12px;
        background: var(--chat-surface, #fafafa);
        text-decoration: none;
        transition:
          transform 0.16s ease,
          border-color 0.16s ease,
          background 0.16s ease,
          box-shadow 0.16s ease;
      }
      .fv-card:hover {
        transform: translateY(-2px);
        border-color: color-mix(in srgb, var(--accent) 42%, transparent);
        background: var(--chat-surface-hover, #fff);
        box-shadow: 0 6px 16px rgba(0, 0, 0, 0.06);
      }
      .fv-card:active {
        transform: scale(0.985);
      }
      .fv-card-text {
        display: flex;
        flex-direction: column;
        gap: 4px;
        min-width: 0;
        flex: 1;
      }
      .fv-card-name {
        font-family: "Bricolage Grotesque", sans-serif;
        font-weight: 600;
        font-size: 14.5px;
        color: var(--grey1);
      }
      .fv-card-desc {
        font-size: 13px;
        line-height: 1.4;
        color: var(--grey2);
      }
      .fv-card-meta {
        margin-top: 3px;
        font-family: "JetBrains Mono", ui-monospace, monospace;
        font-size: 10.5px;
        letter-spacing: 0.02em;
        color: var(--grey3);
      }
      .fv-card-arrow {
        color: var(--grey3);
        flex-shrink: 0;
        transition:
          color 0.16s ease,
          transform 0.16s ease;
      }
      .fv-card:hover .fv-card-arrow {
        color: var(--accent);
        transform: translate(1px, -1px);
      }

      /* (Stack tech-badge styles removed with the Stack section.) */

      /* Pixel birds' nest, a hero easter egg. The babies are the same
         sparrow sprite (runner.png) shrunk down, so they read as its chicks. */
      .birds-nest {
        position: relative;
        display: block; /* own its 84x46 box + reserve vertical space so the
                           nest sits BELOW the blurb instead of overlapping it
                           (it used to rely on the removed .ch-skills block) */
        width: 84px;
        height: 46px;
        margin: 46px 0 0 0;
      }
      .birds-nest > * {
        position: absolute;
        image-rendering: pixelated;
      }
      .bn-nest-back { left: 6px; bottom: 6px; width: 72px; z-index: 0; }
      .bn-nest-back svg {
        width: 100%;
        height: auto;
        display: block;
        shape-rendering: crispEdges;
      }
      /* Chicks = runner.png sparrow (3 frames, shown 72x19) shrunk to 24x19.
         Frames advance via a translated strip (compositor) instead of
         background-position (main-thread repaint every frame). */
      .bn-baby {
        width: 18px;
        height: 14px;
        z-index: 2;
        overflow: hidden;
      }
      .bn-strip {
        display: block;
        width: 54px;
        height: 14px;
        background: url("../images/runner.png?v=4") 0 0 / 54px 14px no-repeat;
        image-rendering: pixelated;
        transform: translateX(-36px); /* calm resting frame */
      }
      /* left chick: leans forward-down, pecking the worm (eating from below) */
      .bn-baby-1 {
        left: 14px;
        bottom: 19px;
        transform-origin: 68% 90%;
        animation: bnPeck 2.5s ease-in-out infinite;
      }
      /* right chick: flipped, lifting off with wings going (trying to fly) */
      .bn-baby-2 {
        left: 41px;
        bottom: 20px;
        transform-origin: 50% 100%;
        animation: bnLift 2.6s ease-in-out infinite;
      }
      .bn-baby-2 .bn-strip {
        animation: bnFlap 0.26s steps(1) infinite;
      }
      .bn-worm {
        position: absolute;
        left: 30px;
        bottom: 11px;
        width: 7px;
        height: 9px;
        z-index: 4;
        image-rendering: pixelated;
        transform-origin: 50% 100%;
        animation: bnWiggle 2.5s ease-in-out infinite;
      }
      .bn-worm svg { width: 100%; height: 100%; display: block; shape-rendering: crispEdges; }
      @keyframes bnPeck {
        0%, 58%, 100% { transform: rotate(20deg); }
        72% { transform: rotate(40deg); }
        85% { transform: rotate(29deg); }
      }
      @keyframes bnLift {
        0%, 100% { transform: scaleX(-1) translateY(0) rotate(0deg); }
        50% { transform: scaleX(-1) translateY(-6px) rotate(-7deg); }
      }
      @keyframes bnFlap {
        0% { transform: translateX(0); }
        50% { transform: translateX(-18px); }
        100% { transform: translateX(0); }
      }
      @keyframes bnWiggle {
        0%, 58%, 100% { transform: rotate(0deg); }
        70% { transform: rotate(-20deg) translateY(1px); }
        80% { transform: rotate(14deg); }
        90% { transform: rotate(-6deg); }
      }
      @media (prefers-reduced-motion: reduce) {
        .bn-baby-1, .bn-baby-2, .bn-baby-2 .bn-strip, .bn-worm { animation: none; }
      }
      @media (max-width: 560px) {
        .birds-nest { transform: scale(0.85); transform-origin: left bottom; }
      }

      /* Gold mine / Receipts */
      .gold-mine {
        margin-top: 80px;
      }
      /* Nested inside the Achievements folder the numbers follow the folder
         description directly, so they don't need a section-sized gap above. */
      .folder-view .gold-mine {
        margin-top: 4px;
      }
      .gm-head {
        margin-bottom: 20px;
      }
      .gm-title {
        font-family: "Bricolage Grotesque", sans-serif;
        font-weight: 700;
        font-size: 26px;
        letter-spacing: -0.02em;
        color: var(--grey1);
      }
      .gm-sub {
        margin-top: 3px;
        font-family: "Hanken Grotesk", sans-serif;
        font-size: 13.5px;
        color: var(--grey3);
      }
      .gold-mine .chat {
        max-width: 460px;
      }
      /* Receipts: hard numbers in a tile grid. Each tile carries its own --i
         so the existing .is-revealed observer staggers them like the bubbles
         it replaced. */
      .gm-grid {
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
        gap: 10px;
        list-style: none;
        margin: 0;
        padding: 0;
      }
      /* An odd tile count would strand the last tile alone on its row;
         let it span the grid so it reads as the closing stat. */
      .gm-stat:last-child:nth-child(odd) {
        grid-column: 1 / -1;
      }
      .gm-stat {
        display: flex;
        flex-direction: column;
        gap: 5px;
        padding: 14px 15px;
        border: 1px solid var(--surface-border, rgba(0, 0, 0, 0.08));
        border-radius: 12px;
        background: var(--chat-surface, #fafafa);
      }
      .gm-num {
        font-family: "Bricolage Grotesque", sans-serif;
        font-weight: 800;
        font-size: 24px;
        line-height: 1;
        letter-spacing: -0.03em;
        color: var(--grey1);
        font-variant-numeric: tabular-nums;
      }
      .gm-cap {
        font-family: "Hanken Grotesk", sans-serif;
        font-size: 12.5px;
        line-height: 1.4;
        color: var(--grey2);
      }
      .gm-note {
        margin-top: 16px;
        max-width: 62ch;
        font-family: "Hanken Grotesk", sans-serif;
        font-size: 13px;
        line-height: 1.55;
        color: var(--grey3);
      }
      .gold-mine .cb,
      .gold-mine .gm-stat,
      .gold-mine .gm-note {
        opacity: 0;
        transform: translateY(10px);
      }
      .gold-mine.is-revealed .cb,
      .gold-mine.is-revealed .gm-stat,
      .gold-mine.is-revealed .gm-note {
        animation: gmBubble 0.45s cubic-bezier(0.22, 1, 0.36, 1) forwards;
        animation-delay: calc(var(--i) * 0.08s);
      }
      @keyframes gmBubble {
        to {
          opacity: 1;
          transform: none;
        }
      }
      @media (prefers-reduced-motion: reduce) {
        .gold-mine .cb,
        .gold-mine .gm-stat,
        .gold-mine .gm-note {
          opacity: 1;
          transform: none;
        }
        .gold-mine.is-revealed .cb,
        .gold-mine.is-revealed .gm-stat,
        .gold-mine.is-revealed .gm-note {
          animation: none;
        }
      }

      /* === Motion polish (Emil Kowalski audit) === */
      /* Flat buttons didn't transition transform  add it so :active scales
         smoothly. (folder/fv-card already transition transform natively, so
         they're intentionally left out to keep their own curves/timing.) */
      .chat-btn,
      .show-more-btn,
      .mode-btn,
      .fv-back,
      .gh-link,
      .case-study-link {
        transition-property:
          color, background, background-color, border-color, box-shadow,
          transform;
        transition-duration: 0.16s;
        transition-timing-function: cubic-bezier(0.34, 1.1, 0.64, 1);
      }
      /* Press feedback  the interface should feel like it hears the user. */
      .chat-btn:active,
      .show-more-btn:active,
      .mode-btn:active,
      .fv-back:active,
      .gh-link:active,
      .case-study-link:active {
        transform: scale(0.97);
      }
      /* Compress the existing hover-lift on press instead of fighting it. */
      .fv-card:active {
        transform: translateY(-1px) scale(0.985);
      }
      .folder:active {
        transform: translateY(-4px) scale(0.99);
      }

      /* Don't leave hover-lifts stuck on touch devices (where :hover sticks
         after a tap). Only fine-pointer devices keep the lift. */
      @media (hover: none) {
        .folder:hover,
        .fv-card:hover,
        .folder:hover .fsheet-1,
        .folder:hover .fsheet-2,
        .folder:hover .fsheet-3 {
          transform: none;
        }
      }

      /* signature reveal: name letters + tagline animate in on Creative */
      .ch-name > span {
        overflow: hidden;
        padding-bottom: 0.14em;
        margin-bottom: -0.14em;
      }
      .rv-char {
        display: inline-block;
        transform: translateY(115%);
        /* No will-change here: the rvUp animation promotes each letter while
           it runs; permanent hints kept dozens of layers alive forever and
           taxed every compositor commit. */
      }
      body.creative-mode .ch-name .rv-char {
        animation: rvUp 0.85s cubic-bezier(0.16, 1, 0.3, 1) forwards;
        animation-delay: calc(var(--i) * 0.035s);
      }
      body.creative-mode .ch-tag {
        opacity: 0;
        animation: rvFade 0.6s ease 0.5s forwards;
      }
      body.creative-mode .ch-blurb {
        opacity: 0;
        animation: rvFade 0.6s ease 0.62s forwards;
      }
      body.creative-mode .build-links {
        opacity: 0;
        animation: rvFade 0.6s ease 0.7s forwards;
      }
      @keyframes rvUp {
        to {
          transform: translateY(0);
        }
      }
      @keyframes rvFade {
        from {
          opacity: 0;
          transform: translateY(8px);
        }
        to {
          opacity: 1;
          transform: none;
        }
      }
      @media (prefers-reduced-motion: reduce) {
        body.creative-mode .ch-name .rv-char,
        body.creative-mode .ch-tag,
        body.creative-mode .ch-blurb,
        body.creative-mode .build-links {
          animation: none;
          opacity: 1;
          transform: none;
        }
      }

      /* Folders cascade in on Creative */
      body.creative-mode .folders .folder {
        opacity: 0;
        animation: folderIn 0.55s cubic-bezier(0.22, 1, 0.36, 1) forwards;
        animation-delay: calc(0.35s + var(--i, 0) * 0.07s);
      }
      @keyframes folderIn {
        from {
          opacity: 0;
          transform: translateY(18px);
        }
        to {
          opacity: 1;
          transform: none;
        }
      }

      @media (prefers-reduced-motion: reduce) {
        body.creative-mode .folders .folder {
          animation: none;
          opacity: 1;
        }
      }

      /* favicon tile on each card */
      .fv-card-icon {
        flex-shrink: 0;
        width: 38px;
        height: 38px;
        display: grid;
        place-items: center;
        border-radius: 10px;
        background: #fff;
        border: 1px solid #ececf0;
        overflow: hidden;
      }
      .fv-card-icon img,
      .fv-card-icon svg {
        width: 22px;
        height: 22px;
        object-fit: contain;
        display: block;
      }

      /* Some brand marks are white on transparent (Annotations), so they
         disappear on the default white tile. Give those a dark ground. */
      .fv-card-icon--dark {
        background: #17171a;
        border-color: transparent;
      }

      .fv-card-icon--mono {
        --h: 220; /* unique hue per tile (set inline); drives the gradient */
        background:
          radial-gradient(
            125% 125% at 22% 18%,
            hsl(calc(var(--h) - 20) 96% 74%) 0%,
            hsl(var(--h) 86% 58%) 46%,
            hsl(calc(var(--h) + 42) 82% 47%) 100%
          );
        border-color: transparent;
        box-shadow:
          inset 0 1px 0 rgba(255, 255, 255, 0.4),
          0 2px 6px hsl(var(--h) 70% 45% / 0.32);
      }
      /* Mobile: one folder per row. At two-up (~160px each) the rotated
         sheets overflow their stage into the neighbor column. Capped and
         centered so a full-width folder reads as a card, not a billboard. */
      @media (max-width: 600px) {
        .folders {
          grid-template-columns: 1fr;
          gap: 18px;
        }
        .folders .folder {
          width: 100%;
          max-width: 360px;
          margin-inline: auto;
        }
        .work-row {
          column-gap: 18px;
        }
        .work-row .work-identity {
          width: 92px;
          min-width: 92px;
        }
      }

      @media (prefers-reduced-motion: reduce) {
        .creative-chat .chat > *,
        .creative-chat.is-revealed .chat > * {
          opacity: 1;
          transform: none;
          animation: none;
        }
      }

      /* Section mascots: a dog perched over Client works, a frog over Vibe coding */
      .has-mascot { position: relative; }
      .section-mascot {
        position: absolute;
        left: 0;
        bottom: calc(100% - 3px);
        width: 32px;
        z-index: 5;
        pointer-events: none;
        image-rendering: pixelated;
      }
      .section-mascot svg { width: 100%; height: auto; display: block; shape-rendering: crispEdges; }
      /* dog: shakes its head now and then, tail always wagging */
      .dog-head { transform-box: fill-box; transform-origin: 50% 92%; animation: dogShake 5s ease-in-out infinite; }
      .dog-tail { transform-box: fill-box; transform-origin: 12% 88%; animation: dogWag 0.45s ease-in-out infinite alternate; }
      @keyframes dogShake {
        0%, 68%, 100% { transform: rotate(0deg); }
        74% { transform: rotate(-10deg); }
        80% { transform: rotate(10deg); }
        86% { transform: rotate(-7deg); }
        92% { transform: rotate(4deg); }
      }
      @keyframes dogWag { from { transform: rotate(-12deg); } to { transform: rotate(16deg); } }
      /* frog: blinks now and then, throat gently puffs */
      .frog-eye { transform-box: fill-box; transform-origin: 50% 55%; animation: frogBlink 4.4s ease-in-out infinite; }
      .frog-throat { transform-box: fill-box; transform-origin: 50% 0%; animation: frogPuff 2.6s ease-in-out infinite; }
      @keyframes frogBlink {
        0%, 91%, 100% { transform: scaleY(1); }
        95% { transform: scaleY(0.1); }
      }
      @keyframes frogPuff {
        0%, 100% { transform: scaleY(1); }
        50% { transform: scaleY(1.5); }
      }
      @media (prefers-reduced-motion: reduce) {
        .dog-head, .dog-tail, .frog-eye, .frog-throat { animation: none; }
      }
      /* The dog/frog/worm animate SVG internals, which only run on the main
         thread (style + layout every frame). Freeze them while the page is
         actively scrolling, and whenever they're off-screen (JS toggles the
         classes), so they never tax scrolling or idle frames. */
      html.is-scrolling .dog-head,
      html.is-scrolling .dog-tail,
      html.is-scrolling .frog-eye,
      html.is-scrolling .frog-throat,
      html.is-scrolling .bn-worm,
      .anim-paused,
      .anim-paused * {
        animation-play-state: paused;
      }

      /* Scale the whole layout up on large desktops so the 540px column isn't
         lost in whitespace. zoom keeps proportions + line-length intact (vs just
         widening, which would make text lines too long to read comfortably). */
      @media (min-width: 1600px) { .page-shell, .cloud, .tree, .theme-toggle { zoom: 1.15; } }
      @media (min-width: 1920px) { .page-shell, .cloud, .tree, .theme-toggle { zoom: 1.3; } }
      @media (min-width: 2560px) { .page-shell, .cloud, .tree, .theme-toggle { zoom: 1.55; } }

      /* Lenis smooth scroll  while active, it owns scrolling, so turn off the
         native smooth-scroll to avoid double-easing. (Reduced-motion users get
         no Lenis, so native behaviour is untouched for them.) */
      html.lenis,
      html.lenis body {
        height: auto;
      }
      html.lenis {
        scroll-behavior: auto !important;
      }
      .lenis.lenis-smooth [data-lenis-prevent] {
        overscroll-behavior: contain;
      }
      .lenis.lenis-stopped {
        overflow: clip;
      }
      .lenis.lenis-smooth iframe {
        pointer-events: none;
      }

      /* === RACE TRACK  pixel tortoise & hare on a top-down oval (canvas) === */
      .race-track {
        position: relative;
        height: 148px;
        margin: 40px 0;
        border-radius: 18px;
        overflow: hidden;
      }
      .race-canvas {
        width: 100%;
        height: 100%;
        display: block;
        image-rendering: pixelated;
      }
      @media (max-width: 600px) {
        .race-track { height: 116px; margin: 30px 0; }
      }
      /* Dark badges: pastel text (hue mixed toward WHITE, not grey  grey
         muddies it), a quiet 16% tinted fill, and no gloss gradient or inner
         white line (light-mode skeuomorphs that read as haze on dark). */
      [data-theme="dark"] .badge {
        color: color-mix(in srgb, var(--c) 42%, #fff);
        border-color: color-mix(in srgb, var(--c) 38%, transparent);
        background: color-mix(in srgb, var(--c) 16%, #17171c);
        box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
      }
      [data-theme="dark"] .badge:hover {
        background: color-mix(in srgb, var(--c) 24%, #17171c);
        box-shadow: 0 3px 8px rgba(0, 0, 0, 0.5);
      }
      /* Dark folders: the real fix is the SURFACE, not the sheen. In light
         mode the card works via white surface + real shadows; in dark, black
         shadows vanish and the 2.5% surface mix is invisible against the page,
         so the card had no elevation cues at all (which is why any sheen value
         looked wrong  it was the only "card" signal left). Dark elevation =
         lighter surface: step the card up to a real surface tone and deepen
         the drop-shadows so the sheet stack seats. Sheen stays quiet (8%). */
      [data-theme="dark"] .folder {
        --fcard: var(--folder-surface);
        filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.45))
          drop-shadow(0 10px 22px rgba(0, 0, 0, 0.5));
      }
      /* No top-sheen in dark: the lifted surface carries the card now, and
         any inset highlight reads as a separator line across the label. */
      [data-theme="dark"] .folder-tabzone,
      [data-theme="dark"] .folder-label {
        box-shadow: none;
      }
      /* Mode toggle, dark: inactive label steps up to grey2 so it reads as a
         tappable second option, not a disabled ghost. Active stays grey1. */
      [data-theme="dark"] .mode-btn:not(.is-active) {
        color: var(--grey2);
      }
      /* Dark text hierarchy: bio copy steps down from heading-bright so the
         page reads title -> body -> description -> meta instead of one wall
         of near-white. (Light mode keeps grey1  near-black body is normal.)
         :not(.cb) keeps chat bubbles out  they set their own text colors
         (white on the accent bubbles) and must not be dimmed. */
      [data-theme="dark"] .homepage .article p:not(.cb):not(.profile-role) {
        color: #b7b7bf;
      }
      @media (prefers-color-scheme: dark) {
        :root:not([data-theme="light"]) .badge {
          color: color-mix(in srgb, var(--c) 42%, #fff);
          border-color: color-mix(in srgb, var(--c) 38%, transparent);
          background: color-mix(in srgb, var(--c) 16%, #17171c);
          box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
        }
        :root:not([data-theme="light"]) .badge:hover {
          background: color-mix(in srgb, var(--c) 24%, #17171c);
          box-shadow: 0 3px 8px rgba(0, 0, 0, 0.5);
        }
        :root:not([data-theme="light"]) .folder {
          --fcard: var(--folder-surface);
          filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.45))
            drop-shadow(0 10px 22px rgba(0, 0, 0, 0.5));
        }
        :root:not([data-theme="light"]) .folder-tabzone,
        :root:not([data-theme="light"]) .folder-label {
          box-shadow: none;
        }
        :root:not([data-theme="light"]) .mode-btn:not(.is-active) {
          color: var(--grey2);
        }
        :root:not([data-theme="light"]) .homepage .article p:not(.cb):not(.profile-role) {
          color: #b7b7bf;
        }
      }

      /* === DARK MODE === */
      @media (prefers-color-scheme: dark) {
        :root:not([data-theme="light"]) {
          --body-bg: #0e0e12;
          --body-color: #eaeaea;
          --grey1: #eaeaea;
          --grey2: #94949d;
          --grey3: #787880;
          --wash1: #2a2a32;
          --wash2: #1c1c22;
          --border: #2a2a32;
          --border-rule: #2a2a32;
          --selection-color: #f0f0f0;
          --selection-bg: #2a2a3a;
          --focus-color: rgba(255, 255, 255, 0.5);
          --color-gray-background: #0e0e12;
          --color-gray-1200: #eaeaea;
          --color-gray-1000: #94949d;
          --color-gray-900: #787880;
          --color-gray-400: #2a2a32;
          --color-text-paragraph: #94949d;
          --accent: #6b8aff;
          --transparentBorder: rgba(255, 255, 255, 0.06);
          --toggle-track: #23232c;
          --toggle-thumb: #3a3a48;
          --chat-bubble: #2a2a32;
          --chat-surface: #23232c;
          --chat-surface-hover: #2e2e3a;
          --surface-border: rgba(255, 255, 255, 0.12);
          --surface-border-hover: rgba(255, 255, 255, 0.22);
          --photo-frame: #2b2b33;
          --sk-badge-shadow: inset 0 2px 5px rgba(0, 0, 0, 0.35);
          --gh-empty: #1e1e26;
          --folder-surface: #1f1f22;
          --folder-ink: #eaeaea;
          --folder-muted: #94949d;
          color-scheme: dark;
        }
      }
      [data-theme="dark"] {
        --body-bg: #0e0e12;
        --body-color: #eaeaea;
        --grey1: #eaeaea;
        --grey2: #94949d;
        --grey3: #787880;
        --wash1: #2a2a32;
        --wash2: #1c1c22;
        --border: #2a2a32;
        --border-rule: #2a2a32;
        --selection-color: #f0f0f0;
        --selection-bg: #2a2a3a;
        --focus-color: rgba(255, 255, 255, 0.5);
        --color-gray-background: #0e0e12;
        --color-gray-1200: #eaeaea;
        --color-gray-1000: #94949d;
        --color-gray-900: #787880;
        --color-gray-400: #2a2a32;
        --color-text-paragraph: #94949d;
        --accent: #6b8aff;
        --transparentBorder: rgba(255, 255, 255, 0.06);
        --toggle-track: #23232c;
        --toggle-thumb: #3a3a48;
        --chat-bubble: #2a2a32;
        --chat-surface: #23232c;
        --chat-surface-hover: #2e2e3a;
        --surface-border: rgba(255, 255, 255, 0.12);
        --surface-border-hover: rgba(255, 255, 255, 0.22);
        --photo-frame: #2b2b33;
        --sk-badge-shadow: inset 0 2px 5px rgba(0, 0, 0, 0.35);
        --gh-empty: #1e1e26;
        --folder-surface: #1f1f22;
        --folder-ink: #eaeaea;
        --folder-muted: #94949d;
        color-scheme: dark;
      }

      /* Theme toggle button */
      .theme-toggle {
        position: absolute;
        top: 2px;
        left: calc(50% + 226px);
        z-index: 10;
        width: 46px;
        height: 46px;
        padding: 0;
        border: none;
        background: none;
        cursor: pointer;
        display: grid;
        place-items: center;
        filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.12));
        transition: transform 0.2s cubic-bezier(0.34, 1.1, 0.64, 1);
      }
      .theme-toggle:hover { transform: scale(1.12); }
      .theme-toggle:active { transform: scale(0.94); }
      /* Sun and moon stack in the same grid cell and crossfade with a small
         rotation  the sun's rays make the turn readable, so the swap feels
         like one celestial body giving way to the other instead of a display
         snap. Opacity leads (fast) while the transform settles a beat later. */
      .theme-toggle svg {
        width: 42px;
        height: 42px;
        display: block;
        grid-area: 1 / 1;
        transition:
          opacity 180ms ease,
          transform 320ms cubic-bezier(0.23, 1, 0.32, 1);
      }
      .theme-toggle .icon-moon { opacity: 0; transform: scale(0.6) rotate(-60deg); }
      [data-theme="dark"] .theme-toggle .icon-sun { opacity: 0; transform: scale(0.6) rotate(60deg); }
      [data-theme="dark"] .theme-toggle .icon-moon { opacity: 1; transform: none; }
      @media (prefers-color-scheme: dark) {
        :root:not([data-theme="light"]) .theme-toggle .icon-sun { opacity: 0; transform: scale(0.6) rotate(60deg); }
        :root:not([data-theme="light"]) .theme-toggle .icon-moon { opacity: 1; transform: none; }
      }
      @media (prefers-reduced-motion: reduce) {
        /* Keep the crossfade (it aids comprehension), drop the rotation. */
        .theme-toggle svg { transition: opacity 150ms ease; }
        .theme-toggle .icon-moon,
        [data-theme="dark"] .theme-toggle .icon-sun { transform: none; }
      }
      @media (max-width: 900px) {
        .theme-toggle { left: auto; right: 30px; top: 70px; }
      }
      /* Theme switch: circular reveal ("drain"). script.js covers the screen
         instantly with this disc in the OLD theme's colour, applies the new
         theme underneath (the full re-render happens at the start, hidden),
         then shrinks the disc into the sun/moon on transform:scale
         (compositor -> smooth). Nothing re-renders after the animation ends,
         which is what one GPU session kept presenting stale frames on.
         Deliberately NO will-change / contain: sticky promoted layers are
         what blanked the page before. pointer-events:none keeps the switch
         clickable mid-sweep. */
      .theme-reveal {
        position: fixed;
        border-radius: 50%;
        transform: scale(0);
        transform-origin: center;
        pointer-events: none;
        z-index: 2147483000;
      }

      /* === Smooth cursor (hpbrn-style): outlined arrow that leans into its
         direction of travel, swapping to a pointing hand over interactives.
         script.js drives position/rotation with a spring on transform only;
         CSS owns the arrow<->hand swap and theming. Colours ride existing
         tokens (--body-bg / --grey1), so dark mode needs no extra rules.
         html.has-smooth-cursor is added by JS only on fine-pointer,
         no-reduced-motion setups; coarse pointers also hard-hidden below. */
      .has-smooth-cursor,
      .has-smooth-cursor * {
        cursor: none !important;
      }
      .smooth-cursor {
        position: fixed;
        top: 0;
        left: 0;
        z-index: 9999;
        pointer-events: none;
        opacity: 0;
        will-change: transform;
        /* origin = the arrow's TIP, so rotation pivots on the point */
        transform-origin: 12.5px 2.5px;
        transition: opacity 0.25s ease;
      }
      .smooth-cursor.is-visible {
        opacity: 1;
      }
      .smooth-cursor svg {
        display: block;
        position: absolute;
        top: 0;
        left: 0;
        transform-origin: 12.5px 2.5px;
        transition:
          opacity 0.16s ease,
          transform 0.22s cubic-bezier(0.22, 1, 0.36, 1);
      }
      .cursor-hand {
        opacity: 0;
        transform: scale(0.72) rotate(-12deg);
      }
      .smooth-cursor.is-hand .cursor-arrow {
        opacity: 0;
        transform: scale(0.72) rotate(12deg);
      }
      .smooth-cursor.is-hand .cursor-hand {
        opacity: 1;
        transform: scale(1);
      }
      @media (pointer: coarse) {
        .smooth-cursor {
          display: none;
        }
      }

      /* Dark mode: top fade gradient */
      [data-theme="dark"] .page-shell::before {
        background: linear-gradient(
          180deg,
          #0e0e12,
          hsla(240, 10%, 6%, 0.738) 19%,
          hsla(240, 10%, 6%, 0.541) 34%,
          hsla(240, 10%, 6%, 0.382) 47%,
          hsla(240, 10%, 6%, 0.278) 56.5%,
          hsla(240, 10%, 6%, 0.194) 65%,
          hsla(240, 10%, 6%, 0.126) 73%,
          hsla(240, 10%, 6%, 0.075) 80.2%,
          hsla(240, 10%, 6%, 0.042) 86.1%,
          hsla(240, 10%, 6%, 0.021) 91%,
          hsla(240, 10%, 6%, 0.008) 95.2%,
          hsla(240, 10%, 6%, 0.002) 98.2%,
          hsla(240, 10%, 6%, 0)
        );
      }
      @media (prefers-color-scheme: dark) {
        :root:not([data-theme="light"]) .page-shell::before {
          background: linear-gradient(
            180deg,
            #0e0e12,
            hsla(240, 10%, 6%, 0.738) 19%,
            hsla(240, 10%, 6%, 0.541) 34%,
            hsla(240, 10%, 6%, 0.382) 47%,
            hsla(240, 10%, 6%, 0.278) 56.5%,
            hsla(240, 10%, 6%, 0.194) 65%,
            hsla(240, 10%, 6%, 0.126) 73%,
            hsla(240, 10%, 6%, 0.075) 80.2%,
            hsla(240, 10%, 6%, 0.042) 86.1%,
            hsla(240, 10%, 6%, 0.021) 91%,
            hsla(240, 10%, 6%, 0.008) 95.2%,
            hsla(240, 10%, 6%, 0.002) 98.2%,
            hsla(240, 10%, 6%, 0)
          );
        }
      }

      /* === Live bio keywords: words that behave like small UIs ===
         Three, deliberately: Figma (selection frame), GSAP (flowing squiggle),
         SEO (receipts card with a chart drawing itself). Everything CSS-only,
         themed via existing tokens, gated off touch, reduced-motion aware. */
      .kw {
        position: relative;
        color: var(--grey1);
        font-weight: 500;
        cursor: default;
        /* Own stacking context: the AEO cluster tucks its logos behind the
           word with z-index:-1, which without this escaped the word and
           slid behind the whole paragraph. Isolating keeps the -1 inside. */
        isolation: isolate;
      }
      /* And a hovered keyword (popup open) must stack above neighboring
         lines and later .kw spans in the same paragraph. */
      .kw:hover {
        z-index: 70;
      }
      .kw-figma,
      .kw-gsap,
      .kw-react,
      .kw-seo,
      .kw-aeo,
      .kw-ds,
      .kw-music {
        border-bottom: 1px dashed var(--grey3);
      }

      /* Figma: hover draws the canonical selection frame + corner handles */
      .kw-figma::before,
      .kw-figma::after {
        content: "";
        position: absolute;
        opacity: 0;
        pointer-events: none;
        transition: opacity 0.12s ease;
      }
      .kw-figma::before {
        inset: -1px -4px;
        border: 1.2px solid #0d99ff;
      }
      .kw-figma::after {
        inset: -4px -7px;
        background:
          linear-gradient(#fff, #fff) left 1px top 1px / 4px 4px,
          linear-gradient(#fff, #fff) right 1px top 1px / 4px 4px,
          linear-gradient(#fff, #fff) left 1px bottom 1px / 4px 4px,
          linear-gradient(#fff, #fff) right 1px bottom 1px / 4px 4px,
          linear-gradient(#0d99ff, #0d99ff) left top / 6px 6px,
          linear-gradient(#0d99ff, #0d99ff) right top / 6px 6px,
          linear-gradient(#0d99ff, #0d99ff) left bottom / 6px 6px,
          linear-gradient(#0d99ff, #0d99ff) right bottom / 6px 6px;
        background-repeat: no-repeat;
      }
      .kw-figma:hover {
        border-bottom-color: transparent;
      }
      .kw-figma:hover::before,
      .kw-figma:hover::after {
        opacity: 1;
      }

      /* GSAP: an always-on accent squiggle that flows on hover. The svg is
         2x the word width inside a clipping span; the hover loop shifts the
         WHOLE svg by -50% (4 full wave periods, so the loop is seamless).
         Animating the svg element (not the inner path) keeps the animation
         on the compositor  inner-SVG transforms can repaint per frame. */
      .kw-gsap:hover {
        border-bottom-color: transparent; /* the squiggle takes the underline's place */
      }
      .kw-waveclip {
        position: absolute;
        left: 0;
        right: 0;
        bottom: -6px;
        height: 6px;
        overflow: hidden;
        pointer-events: none;
        opacity: 0;
        transition: opacity 0.15s ease;
      }
      .kw-gsap:hover .kw-waveclip {
        opacity: 1;
      }
      .kw-wave {
        display: block;
        width: 200%;
        height: 100%;
      }
      .kw-wave path {
        fill: none;
        stroke: var(--accent);
        stroke-width: 1.7;
        stroke-linecap: round;
      }
      .kw-gsap:hover .kw-wave {
        animation: kwFlow 0.8s linear infinite;
      }
      @keyframes kwFlow {
        to {
          transform: translateX(-50%);
        }
      }

      /* Hover cards: compact Gustavo-style  small thumb left, text right.
         .kw-pop is the floating shell; --seo and --music are the layouts. */
      .kw-pop {
        position: absolute;
        left: 50%;
        bottom: calc(100% + 9px);
        z-index: 60;
        display: flex;
        white-space: nowrap;
        background: var(--chat-surface, #fff);
        border: 1px solid var(--surface-border, rgba(0, 0, 0, 0.08));
        border-radius: 12px;
        box-shadow: 0 12px 32px rgba(0, 0, 0, 0.14);
        opacity: 0;
        pointer-events: none;
        transform: translateX(-50%) translateY(6px) scale(0.96);
        transform-origin: bottom center;
        transition:
          opacity 0.16s ease,
          transform 0.22s cubic-bezier(0.34, 1.3, 0.64, 1);
      }
      .kw:hover .kw-pop {
        opacity: 1;
        transform: translateX(-50%) translateY(0) scale(1);
      }
      .kw-pop--seo {
        align-items: center;
        gap: 9px;
        padding: 8px 13px 8px 8px;
      }
      .kw-pop-thumb {
        flex: none;
        width: 44px;
        height: 32px;
        border-radius: 7px;
        display: grid;
        place-items: center;
        background: color-mix(in srgb, #22c55e 10%, transparent);
      }
      .kw-pop-text {
        display: flex;
        flex-direction: column;
        gap: 1px;
      }
      .kw-chart {
        width: 38px;
        height: 26px;
      }
      .kw-chart-line {
        fill: none;
        stroke: #22c55e;
        stroke-width: 2;
        stroke-linecap: round;
        stroke-linejoin: round;
        stroke-dasharray: 55;
        stroke-dashoffset: 55;
      }
      .kw-seo:hover .kw-chart-line {
        stroke-dashoffset: 0;
        transition: stroke-dashoffset 0.55s ease-out 0.08s;
      }
      .kw-chart-dot {
        fill: #22c55e;
        opacity: 0;
        transition: opacity 0.2s ease 0.55s;
      }
      .kw-seo:hover .kw-chart-dot {
        opacity: 1;
      }
      /* soft gradient area under the line, fading in after the line draws */
      .kw-chart-area {
        opacity: 0;
        transition: opacity 0.3s ease 0.4s;
      }
      .kw-seo:hover .kw-chart-area {
        opacity: 1;
      }
      /* sonar ping on the peak  runs only while hovered */
      .kw-chart-ring {
        fill: none;
        stroke: #22c55e;
        stroke-width: 1;
        opacity: 0;
        transform-box: fill-box;
        transform-origin: center;
      }
      .kw-seo:hover .kw-chart-ring {
        animation: kwPing 1.5s ease-out 0.6s infinite;
      }
      @keyframes kwPing {
        0% {
          opacity: 0.7;
          transform: scale(1);
        }
        70%,
        100% {
          opacity: 0;
          transform: scale(2.4);
        }
      }
      .kw-pop-line {
        font-size: 12px;
        font-weight: 600;
        line-height: 1.35;
        color: var(--grey1);
      }
      .kw-pop-sub {
        font-size: 11px;
        line-height: 1.35;
        color: var(--grey3);
      }

      /* Music: same card anatomy as the last-played pattern  thumb + track,
         muted footer strip. The vinyl spins only while hovered. */
      .kw-pop--music {
        flex-direction: column;
        overflow: hidden;
      }
      /* artwork sits flush against the card's edge  the card's own corner
         radius rounds it on that side (reference look), everything tight */
      .kw-mrow {
        display: flex;
        align-items: center;
        gap: 10px;
        padding: 0 14px 0 0;
      }
      .kw-mthumb {
        position: relative;
        flex: none;
        width: 46px;
        height: 46px;
        display: block;
      }
      .kw-mart {
        display: block;
        width: 100%;
        height: 100%;
        object-fit: cover;
      }
      /* the little disc ring over the art, like a record label */
      .kw-mthumb::after {
        content: "";
        position: absolute;
        inset: 0;
        margin: auto;
        width: 15px;
        height: 15px;
        border-radius: 50%;
        border: 2px solid rgba(255, 255, 255, 0.9);
        background: radial-gradient(
          circle,
          rgba(255, 255, 255, 0.55) 0 3px,
          rgba(255, 255, 255, 0.12) 3.5px
        );
        box-shadow: 0 0 6px rgba(0, 0, 0, 0.25);
      }
      /* tiny equalizer beside the track name  plays only while hovered */
      .kw-eq {
        display: inline-flex;
        align-items: flex-end;
        gap: 2px;
        height: 9px;
        margin-left: 6px;
      }
      .kw-eq span {
        width: 2px;
        height: 100%;
        border-radius: 1px;
        background: var(--accent);
        transform: scaleY(0.3);
        transform-origin: bottom;
        animation: kwEq 0.9s ease-in-out infinite;
        animation-name: none;
      }
      .kw-eq span:nth-child(2) {
        animation-delay: -0.3s;
      }
      .kw-eq span:nth-child(3) {
        animation-delay: -0.6s;
      }
      .kw-music:hover .kw-eq span {
        animation-name: kwEq;
      }
      @keyframes kwEq {
        0%,
        100% {
          transform: scaleY(0.3);
        }
        50% {
          transform: scaleY(1);
        }
      }

      /* === Role subtitle: blur-fade cycle between two titles. Both lines
         stack in one grid cell (width = the longer one, no layout shift);
         each dissolves out through a blur while the other reforms. The
         animated values sit on long plateaus, so between swaps the browser
         has nothing to repaint  no continuous cost. === */
      .profile-role.role-swap {
        display: inline-grid;
      }
      /* screen-reader-only text (the animated words are aria-hidden) */
      .sr-only {
        position: absolute;
        width: 1px;
        height: 1px;
        padding: 0;
        margin: -1px;
        overflow: hidden;
        clip: rect(0, 0, 0, 0);
        white-space: nowrap;
        border: 0;
      }
      .role-line {
        grid-area: 1 / 1;
        white-space: nowrap;
      }
      /* word-by-word: each word runs the same 8s cycle, offset ~0.14s from
         the previous, so titles dissolve and reform as a cascade */
      .role-line .rw {
        display: inline-block;
        opacity: 0;
        /* Opacity-only on purpose: the original blur dissolve re-rasterized
           five text layers at every transition, which produced a periodic
           scroll hitch in the hero (confirmed by A/B feel-test). The word-
           by-word cascade carries the effect; opacity keeps it compositor-
           cheap on every device. */
        animation: roleSwapFade 8s ease-in-out infinite;
      }
      .role-line-a .rw:nth-child(2) {
        animation-delay: 0.14s;
      }
      .role-line-a .rw:nth-child(3) {
        animation-delay: 0.28s;
      }
      .role-line-b .rw {
        animation-delay: -4s;
      }
      .role-line-b .rw:nth-child(2) {
        animation-delay: -3.86s;
      }
      .role-line-b .rw:nth-child(3) {
        animation-delay: -3.72s;
      }
      @keyframes roleSwapFade {
        0% {
          opacity: 0;
        }
        6%,
        44% {
          opacity: 1;
        }
        50%,
        100% {
          opacity: 0;
        }
      }

      /* Touch: no hover = no frame/popup; keep the quiet resting styles only */
      @media (hover: none) {
        .kw-figma::before,
        .kw-figma::after,
        .kw-pop,
        .kw-coder,
        .kw-cafe {
          display: none;
        }
      }
      @media (prefers-reduced-motion: reduce) {
        .kw-gsap:hover .kw-wave,
        .kw-eq span,
        .kw-seo:hover .kw-chart-ring,
        .kw-coder-head,
        .kw-coder-hand,
        .kw-cafe-steam,
        .kw-cafe-mug,
        .role-line .rw {
          animation: none;
        }
        /* static subtitle: first role only, sharp and visible */
        .role-line-a .rw {
          opacity: 1;
          filter: none;
        }
        .role-line-b {
          display: none;
        }
        .kw-chart-line {
          stroke-dasharray: none;
          stroke-dashoffset: 0;
        }
        .kw-pop {
          transition: opacity 0.12s ease;
          transform: translateX(-50%);
        }
        .kw-seo:hover .kw-pop {
          transform: translateX(-50%);
        }
      }

      /* AEO: answer-engine card  Claude / ChatGPT / Perplexity logos, the
         whole card is a link to a live "who is Atishay Tuli" AI search.
         Unlike the other cards this one is clickable, so pointer-events
         re-enable while its keyword is hovered (the card is a child of the
         keyword, so moving the cursor onto it keeps it open). */
      /* No card at all: just the logo cluster, rising from BEHIND the word
         (z-index -1 puts it under the text plane while it emerges). */
      .kw-pop--aeo {
        background: none;
        border: none;
        box-shadow: none;
        border-radius: 0;
        padding: 0;
        z-index: -1;
        bottom: calc(100% + 3px);
        text-decoration: none;
        /* center over the WORD without left:50% (unreliable against inline
           fragment boxes): pin to both edges and auto-center the cluster */
        left: 0;
        right: 0;
        margin-inline: auto;
        width: fit-content;
        transform: translateY(14px) scale(0.9);
      }
      .kw-aeo:hover .kw-pop--aeo {
        /* generic .kw:hover rule reintroduces translateX(-50%); cancel it */
        transform: translateY(0) scale(1);
      }
      /* SEO / AEO: the WORDS are the links (cards are display-only sugar) */
      a.kw {
        cursor: pointer;
        text-decoration: none;
      }
      a.kw:focus-visible {
        outline: 2px solid var(--focus-color);
        outline-offset: 3px;
        border-radius: 3px;
      }
      .kw-pop--seo {
        text-decoration: none;
      }
      .kw-pop--seo:hover .kw-pop-sub {
        color: var(--accent);
      }
      /* Designing Solutions: coffee break at the office  a pixel mug rises
         from behind the phrase, steam drifts only while hovered. Same
         anatomy as the React coder. */
      .kw-ds {
        white-space: nowrap;
      }
      .kw-cafe {
        position: absolute;
        left: 0;
        right: 0;
        margin-inline: auto;
        width: 30px;
        bottom: calc(100% + 1px);
        z-index: -1;
        pointer-events: none;
        opacity: 0;
        transform: translateY(12px);
        transition:
          opacity 0.16s ease,
          transform 0.22s cubic-bezier(0.34, 1.3, 0.64, 1);
      }
      .kw-cafe svg {
        display: block;
        width: 100%;
        height: auto;
        shape-rendering: crispEdges;
      }
      /* :is(:hover, .kw-demo) = real hover OR the scripted teaser, one state */
      .kw-ds:is(:hover, .kw-demo) .kw-cafe {
        opacity: 1;
        transform: translateY(0);
      }
      .kw-cafe-steam {
        opacity: 0;
        animation: kwSteam 1.6s linear infinite;
        animation-name: none; /* no idle layer; named on hover */
      }
      .kw-cafe-s2 {
        animation-delay: 0.5s;
      }
      .kw-cafe-s3 {
        animation-delay: 1s;
      }
      .kw-ds:is(:hover, .kw-demo) .kw-cafe-steam {
        animation-name: kwSteam;
      }
      @keyframes kwSteam {
        0% {
          opacity: 0;
          transform: translateY(2px);
        }
        35% {
          opacity: 0.9;
        }
        100% {
          opacity: 0;
          transform: translateY(-3px);
        }
      }
      .kw-cafe-mug {
        animation: kwCoderBob 2.2s steps(1) infinite;
        animation-name: none;
        transform-box: fill-box;
      }
      .kw-ds:is(:hover, .kw-demo) .kw-cafe-mug {
        animation-name: kwCoderBob;
      }

      /* React: a pixel coder at their desk (house mascot style) rises from
         behind the word  head bobs, hand taps, code lines type onto the
         screen. All animations run only while hovered. */
      .kw-coder {
        position: absolute;
        left: 0;
        right: 0;
        margin-inline: auto;
        width: 30px;
        bottom: calc(100% + 1px);
        z-index: -1;
        pointer-events: none;
        opacity: 0;
        transform: translateY(12px);
        transition:
          opacity 0.16s ease,
          transform 0.22s cubic-bezier(0.34, 1.3, 0.64, 1);
      }
      .kw-coder svg {
        display: block;
        width: 100%;
        height: auto;
        shape-rendering: crispEdges;
      }
      .kw-react:is(:hover, .kw-demo) .kw-coder {
        opacity: 1;
        transform: translateY(0);
      }
      /* Idle sprites carry NO animation (animation-name: none), not a paused
         one: Chrome promotes any element with an active animation to its own
         composited layer even when paused and invisible, and every layer is
         re-committed on every scroll frame. The name is set on hover only;
         durations/delays live in the shorthand so they survive the swap. */
      .kw-coder-head {
        animation: kwCoderBob 1.4s steps(1) infinite;
        animation-name: none;
      }
      .kw-coder-hand {
        animation: kwCoderTap 0.24s steps(1) infinite;
        animation-name: none;
      }
      /* right hand taps on the off-beat */
      .kw-coder-hand-r {
        animation-delay: 0.12s;
      }
      .kw-react:is(:hover, .kw-demo) .kw-coder-head {
        animation-name: kwCoderBob;
      }
      .kw-react:is(:hover, .kw-demo) .kw-coder-hand {
        animation-name: kwCoderTap;
      }
      @keyframes kwCoderBob {
        0%,
        100% {
          transform: translateY(0);
        }
        50% {
          transform: translateY(1px);
        }
      }
      @keyframes kwCoderTap {
        0%,
        100% {
          transform: translateY(0);
        }
        50% {
          transform: translateY(1px);
        }
      }
      /* three raw logos, unframed: overlapped -2px, edge ones fanned outward */
      .kw-ai-logos {
        display: flex;
        align-items: center;
        flex: none;
      }
      .kw-ai-logos img {
        display: block;
        width: 22px;
        height: 22px;
      }
      .kw-ai-logos img + img {
        margin-left: -2px;
      }
      .kw-ai-logos img:first-child {
        transform: rotate(-10deg) translateY(1px);
      }
      .kw-ai-logos img:nth-child(2) {
        position: relative;
        z-index: 1;
      }
      .kw-ai-logos img:last-child {
        transform: rotate(10deg) translateY(1px);
      }

      /* Achievement shots: caption under each screenshot */
      .shots-scroller figure.win-shot {
        margin: 0;
        flex: none;
      }
      .win-cap {
        display: block;
        margin-top: 7px;
        max-width: 300px;
        font-size: 11.5px;
        line-height: 1.45;
        color: var(--grey3);
        white-space: normal;
      }

      /* Logo-less icon tiles: the gradient drifts slowly. A conic layer twice
         the tile size rotates underneath (pure transform  compositor only);
         first and last stops match, so the loop is seamless. */
      .fv-card-icon--mono {
        position: relative;
        overflow: hidden;
      }
      .fv-card-icon--mono::before {
        content: "";
        position: absolute;
        inset: -45%;
        /* gentle hue walk with small lightness steps + a blur on the layer
           itself  the blur rasterizes once, then only rotates, so the
           softness costs nothing per frame */
        background: conic-gradient(
          from 0deg,
          hsl(calc(var(--h) - 18) 88% 66%),
          hsl(calc(var(--h) - 6) 86% 61%),
          hsl(var(--h) 84% 58%),
          hsl(calc(var(--h) + 16) 82% 55%),
          hsl(calc(var(--h) + 32) 80% 58%),
          hsl(calc(var(--h) + 16) 84% 62%),
          hsl(calc(var(--h) - 18) 88% 66%)
        );
        filter: blur(7px);
        animation: monoDrift 10s linear infinite;
      }
      @keyframes monoDrift {
        to {
          transform: rotate(1turn);
        }
      }
      @media (prefers-reduced-motion: reduce) {
        .fv-card-icon--mono::before {
          animation: none;
        }
      }

      /* Horizontal strips: each tile renders only when it nears the strip's
         visible window. Without this the whole 9,000px-wide strip rasterized
         at once as the section scrolled into view (measured: a 105ms GPU
         stall  the "stuck" moment). `auto` sizes are placeholders only
         until a tile paints once; the page's vertical layout is unaffected
         because the strip's height comes from its visible tiles. */
      .photo-scroller > * {
        content-visibility: auto;
        contain-intrinsic-size: auto 300px auto 250px;
      }

/* Monogram avatar placeholder (no photo). */
.avatar-monogram {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  font-family: "Bricolage Grotesque", "Hanken Grotesk", sans-serif;
  font-weight: 800;
  font-size: 30px;
  letter-spacing: 0.02em;
  color: var(--color-gray-100);
}
@media (max-width: 768px) {
  .avatar-monogram { font-size: 26px; }
}
.profile-avatar {
  background-image: url("/__l5e/assets-v1/4a609add-9409-4311-bc7d-a51d5267c17a/vineet-profile.png") !important;
  background-size: cover;
  background-position: center 20%;
}
.profile-avatar .avatar-monogram { display: none; }

/* Restored portfolio sections without misleading external profile links. */
.gh-link--pending {
  cursor: default;
  color: var(--grey1);
}
.gh-preview .gh-body {
  min-height: 94px;
}
.gh-preview .gh-graph {
  overflow: hidden;
}

/* Shared sticky controls and reference-style contact footer. */
:root:not([data-theme="dark"]) { --body-bg: #EEEDED; --color-gray-background: var(--body-bg); }
@media (prefers-color-scheme: dark) { :root:not([data-theme]) { --body-bg: #0e0e12; } }
:root[data-theme="light"] { --body-bg: #EEEDED; --color-gray-background: var(--body-bg); }
.page-shell::before { display: none; }
.sticky-controls {
  position: fixed;
  inset: 0 0 auto;
  z-index: 60;
  background: transparent;
}
.sticky-controls-inner {
  max-width: 600px;
  margin: auto;
  padding: 16px;
  min-height: 90px;
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.sticky-controls .mode-toggle { position: relative; top: auto; left: auto; z-index: 1; }
.header-weather { position: relative; width: 96px; height: 58px; transition: width 250ms ease, height 250ms ease; }
.header-weather .theme-toggle { position: absolute; top: 0; left: 4px; right: auto; width: 46px; height: 46px; zoom: 1; z-index: 1; }
.header-weather .cloud-1 { display: block; top: 16px; left: 22px; width: 72px; height: 40px; z-index: 2; zoom: 1; transition: width 250ms ease, height 250ms ease, top 250ms ease, left 250ms ease; }
.sticky-controls.is-scrolled .header-weather { width: 52px; height: 44px; }
.sticky-controls.is-scrolled .theme-toggle { width: 38px; height: 38px; left: 0; }
.sticky-controls.is-scrolled .theme-toggle svg { width: 32px; height: 32px; }
.sticky-controls.is-scrolled .cloud-1 { width: 38px; height: 22px; top: 16px; left: 12px; }
.creative-footer { margin-top: 80px; padding: 48px 0 32px; }
.cf-name { font-size: 52px; line-height: 1.04; letter-spacing: 0; margin: 0 0 40px; font-weight: 750; }
.cf-product-line { display: block; }
.cf-word { color: var(--accent); display: inline-block; transition: opacity 180ms ease, transform 180ms ease; }
.cf-word.is-changing { opacity: 0; transform: translateY(6px); }
.cf-links { gap: 18px 28px; }
.cf-links a { color: var(--grey1); font-weight: 400; font-size: 17px; border-bottom: 1px dashed var(--grey3); padding-bottom: 4px; }
@media (max-width: 600px) {
  .sticky-controls-inner { min-height: 76px; padding: 12px 20px; }
  .header-weather { width: 72px; height: 48px; }
  .header-weather .cloud-1 { width: 52px; height: 29px; }
  .cf-name { font-size: 36px; }
  .cf-links { gap: 18px 24px; }
  .creative-footer { margin-top: 48px; padding-bottom: 12px; }
}
@media (max-width: 380px) { .cf-name { font-size: 30px; } }
@media (prefers-reduced-motion: reduce) {
  .header-weather, .header-weather .cloud-1, .cf-word { transition: none; }
}

/* Profile photo: click to view full size */
.profile-avatar-btn {
  display: block;
  padding: 0;
  border: 0;
  background: none;
  cursor: zoom-in;
}
.profile-avatar-btn .profile-avatar { display: block; }
.profile-photo-lightbox {
  position: fixed;
  inset: 0;
  z-index: 120;
  display: none;
  place-items: center;
  padding: 24px;
  background: rgba(14, 14, 18, 0.82);
  cursor: zoom-out;
}
.profile-photo-lightbox.is-open { display: grid; }
.profile-photo-lightbox img {
  max-width: min(90vw, 720px);
  max-height: 88vh;
  width: auto;
  height: auto;
  border-radius: 12px;
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.45);
}
