/* Shared styles for the Home, Feed, and case-study previews. */
@font-face {
        font-family: "Raveo";
        src: url("./assets/fonts/raveo/RaveoVF.woff2") format("woff2");
        font-style: normal;
        font-weight: 100 900;
        font-display: swap;
      }

      @font-face {
        font-family: "Departure Mono";
        src: url("./assets/fonts/departure-mono/DepartureMono-Regular.woff2") format("woff2");
        font-style: normal;
        font-weight: 400;
        font-display: swap;
      }

      @font-face {
        font-family: "LT Superior Serif";
        src: url("./assets/fonts/superior-serif/LTSuperiorSerif-Regular.otf") format("opentype");
        font-style: normal;
        font-weight: 400;
        font-display: swap;
      }

      :root {
        --surface-primary: #fff;
        --paper: var(--surface-primary);
        --ink: #111;
        --quiet: #68655f;
        --rule: #d9d8d3;
        /* The identity band's divider: neutral like the grey type beside it,
           and lighter than --rule so the hairline doesn't cut across the top. */
        --rule-header: #e6e6e6;
        --surface: #f1f1ed;
        --text-primary: #1a1a1a;
        --text-tertiary: #cfcfcf;
        --surface-secondary: #f7f7f7;
        --highlight: #ffdb00;
        /* The one UI curve: a strong ease-out, per DESIGN.md's motion rules. */
        --ease-out: cubic-bezier(0.23, 1, 0.32, 1);
        --tab-inline-padding: 8px;
        --gutter: clamp(1.25rem, 3.4vw, 3.75rem);
        --work-indent: clamp(var(--gutter), 17vw, 20rem);
        --claim-indent: clamp(0rem, 20vw, 19rem);
        /* Band sits short of the claim title's line: enough to read as an
           indent, not enough to line up with it. */
        --band-indent: clamp(var(--gutter), 7.7vw, 9rem);
        /* Card corner radius, reused as the title's indent so the text starts
           where the plate's straight edge does. */
        --card-radius: 1rem;
        /* How far the case-study lede's platforms and title sit in from the
           page edges. */
        --lede-indent: clamp(1.5rem, 7vw, 6.25rem);
        --section-space: clamp(4.5rem, 7vw, 7rem);
        --display: "Raveo", Arial, Helvetica, sans-serif;
        --sans: "Raveo", Arial, Helvetica, sans-serif;
        --mono: "Departure Mono", monospace;
        --type-meta: clamp(0.8125rem, calc(0.78rem + 0.16vw), 0.875rem);
        --type-ui: clamp(0.875rem, calc(0.82rem + 0.25vw), 1rem);
        --type-body: clamp(1rem, calc(0.94rem + 0.3vw), 1.125rem);
        --type-lead: clamp(1.125rem, calc(1.02rem + 0.5vw), 1.375rem);
        --type-identity: clamp(1.125rem, calc(1rem + 0.5vw), 1.375rem);
        --type-statement: clamp(2rem, calc(1.4rem + 2.5vw), 3.5rem);
        --type-project: clamp(2.25rem, calc(1.5rem + 3vw), 3.5rem);
        --type-display: clamp(3rem, calc(2rem + 4vw), 5.25rem);
      }

      * {
        box-sizing: border-box;
      }

      .study-heading {
        display: flex;
        align-items: baseline;
        justify-content: space-between;
        gap: 1.5rem;
        padding: clamp(2rem, 5vw, 5rem) var(--gutter) clamp(2rem, 4vw, 4rem);
      }

      .study-heading h1 {
        margin: 0;
        font: 400 var(--type-display)/1.04 var(--display);
        letter-spacing: -0.04em;
      }

      .study-heading p {
        margin: 0;
        font-size: var(--type-body);
        color: var(--quiet);
      }

      .study-gallery {
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 4px;
        padding-inline: 4px;
      }

      @media (min-width: 651px) {
        body[data-page="case-study"] {
          --study-width: min(calc(100vw - var(--gutter) * 2), calc((100svh - 5rem) * 2644 / 1924));
        }

        .study-heading,
        .study-gallery {
          width: var(--study-width);
          margin-inline: auto;
          padding-inline: 0;
        }
      }

      .study-image {
        display: block;
        min-width: 0;
        overflow: hidden;
        border-radius: 16px;
        cursor: pointer;
      }

      .study-image--wide {
        grid-column: 1 / -1;
      }

      .study-image img {
        display: block;
        width: 100%;
        height: auto;
      }

      .study-image:focus-visible {
        outline-offset: -5px;
      }

      html:has(body[data-page="case-study"]) {
        scrollbar-gutter: stable;
      }

      html:has(body[data-page="home"]) {
        scrollbar-gutter: stable;
      }

      html:has(.image-lightbox[open]) {
        overflow: hidden;
      }

      .image-lightbox {
        position: fixed;
        inset: 0;
        width: 100%;
        max-width: none;
        height: 100%;
        max-height: none;
        margin: 0;
        padding: 32px;
        overflow: hidden;
        border: 0;
        background: var(--lightbox-dim);
        cursor: pointer;
        --lightbox-dim: rgb(17 17 17 / 45%);
      }

      .image-lightbox[open] {
        display: grid;
        place-items: center;
      }

      /* The dim is the dialog's own background, which already covers the
         viewport, so site.js can fade it with the visual. A ::backdrop only goes
         when the dialog closes, which snapped it off after the visual landed. */
      .image-lightbox::backdrop {
        background: transparent;
      }

      .image-lightbox > img,
      .video-lightbox > video {
        display: block;
        width: auto;
        height: auto;
        max-width: calc(100vw - 64px);
        max-height: calc(100svh - 64px);
        border-radius: 16px;
        transform-origin: top left;
      }

      /* On the study page the expanded visual stops short of the viewport. These
         are lifted windows, and a window blown up to fill the screen reads as
         empty space around its content rather than as a bigger screenshot. The
         percentage tracks the dialog's own padding, so the phone rule holds. */
      body[data-page="study"] .image-lightbox > img,
      body[data-page="study"] .video-lightbox > video {
        max-width: min(1200px, 100%);
      }

      /* Home's recordings keep their window's rounded corners in the frame, a
         little wider than the lightbox's own radius at full size. */
      body[data-page="home"] .image-lightbox > img[src$=".avif"] {
        border-radius: 24px;
      }

      /* The visual a lightbox just closed back to keeps focus without its ring
         (site.js clears the flag when focus moves on). Specific enough to beat
         the per-component rings, the deepest of which is three levels. */
      :is(a, button)[data-focus-returned][data-focus-returned]:focus-visible {
        outline: none;
      }

      .image-lightbox-close {
        position: absolute;
        top: 10px;
        right: 16px;
        z-index: 1;
        border: 0;
        padding: 5px 8px;
        border-radius: 4px;
        background: var(--paper);
        color: var(--ink);
        font: 400 14px/1.2 var(--sans);
        cursor: pointer;
      }

      @media (max-width: 650px) {
        .study-heading {
          align-items: flex-start;
          flex-direction: column;
          gap: 0.75rem;
        }

        .study-gallery { grid-template-columns: minmax(0, 1fr); }
        .study-image { border-radius: 10px; }
        .image-lightbox { padding: 16px; }
        .image-lightbox > img,
        .video-lightbox > video {
          max-width: calc(100vw - 32px);
          max-height: calc(100svh - 80px);
          border-radius: 10px;
        }

        body[data-page="home"] .image-lightbox > img[src$=".avif"] {
          border-radius: 10px;
        }
      }

      html {
        scroll-behavior: smooth;
      }

      /* Page changes crossfade, and a CraftNote card grows into the hero of the
         study it opens: the card plate and the hero share a
         view-transition-name, set inline on both pages. The header is named so
         it holds still instead of fading with the page. Browsers without
         cross-document view transitions just load the page. */
      @view-transition {
        navigation: auto;
      }

      .site-header {
        view-transition-name: site-header;
      }

      ::view-transition-group(*) {
        animation-duration: 300ms;
        animation-timing-function: var(--ease-out);
      }

      ::view-transition-old(root),
      ::view-transition-new(root) {
        animation-duration: 200ms;
      }

      body {
        margin: 0;
        background: var(--paper);
        color: var(--ink);
        font-family: var(--sans);
        font-size: var(--type-body);
        line-height: 1.5;
        -webkit-font-smoothing: antialiased;
      }

      /* Links never underline. Text links show hover by fading to 30% ink. */
      a {
        color: inherit;
        text-decoration: none;
      }

      a:focus-visible {
        outline: 2px solid var(--ink);
        outline-offset: 5px;
      }

      .site-header {
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: 1.5rem;
        padding: 1rem var(--gutter) 1.5rem;
        border-bottom: 1px solid var(--rule-header);
      }

      .identity-email {
        font-size: var(--type-identity);
        letter-spacing: -0.02em;
        line-height: 1.2;
        color: var(--ink);
        text-decoration: none;
        transition: color 160ms ease;
      }

      .identity-email:hover,
      .identity-email:focus-visible {
        color: rgb(17 17 17 / 0.3);
      }

      .site-nav-header {
        position: sticky;
        top: 0;
        z-index: 10;
        padding: 1rem calc(var(--gutter) - var(--tab-inline-padding));
        background: var(--surface-primary);
      }

      .identity {
        display: flex;
        flex-direction: column;
        align-items: flex-start;
        gap: 0.1rem;
      }

      .identity strong,
      .identity .local-time {
        font-size: var(--type-identity);
        letter-spacing: -0.02em;
        line-height: 1.2;
      }

      .identity strong {
        font-weight: 520;
      }

      .identity-home {
        color: inherit;
        text-decoration: none;
        transition: color 160ms ease;
      }

      .identity-home:hover,
      .identity-home:focus-visible {
        color: rgb(17 17 17 / 0.3);
      }

      .site-nav {
        font-size: var(--type-ui);
        font-weight: 520;
        line-height: 1.4;
      }

      .project-meta,
      .footer-meta {
        font-size: var(--type-meta);
        line-height: 1.4;
      }

      .project-meta,
      .footer-meta {
        color: var(--quiet);
      }

      .identity .local-time {
        color: var(--text-tertiary);
      }

      .project-meta {
        font-family: var(--mono);
      }

      .site-nav {
        display: flex;
        flex-wrap: wrap;
        gap: 6px;
      }

      .nav-tab {
        display: inline-flex;
        align-items: center;
        min-height: 2rem;
        padding: 4px var(--tab-inline-padding);
        border-radius: 4px;
        font-size: 14px;
        color: var(--text-tertiary);
        background: transparent;
        text-decoration: none;
        transition: color 160ms ease, background-color 160ms ease;
      }

      /* Pointer devices only: on a phone the fill would stay on the tapped tab. */
      @media (hover: hover) and (pointer: fine) {
        .nav-tab:hover {
          color: var(--text-primary);
          background: var(--surface-secondary);
        }
      }

      .nav-tab[aria-current="page"],
      .nav-tab[aria-current="page"]:hover {
        color: var(--text-primary);
        background: transparent;
        text-decoration: none;
      }

      .nav-tab:focus-visible {
        color: var(--text-primary);
      }

      .hero {
        display: grid;
        grid-template-columns: minmax(0, 0.62fr) minmax(0, 1fr);
        align-items: end;
        padding: clamp(4.5rem, 7vw, 7.5rem) var(--gutter) clamp(6.5rem, 12.5vw, 11.25rem);
      }

      .hero h1 {
        grid-column: 2;
        margin: 0;
        font-family: var(--display);
        font-size: clamp(3.5rem, 6.5vw, 6.25rem);
        font-weight: 500;
        letter-spacing: -0.04em;
        line-height: 1.03;
        transform: translateX(2rem);
      }

      /* Home's statement has set lines ("…and / the systems that help / them
         grow."), and its size follows its own column rather than the window:
         the longest line (8.7em) fills most of the column after the 2rem
         shift, so the title ends the same way inside the email's edge on a
         laptop and a large monitor. 61.7cqi is the 1fr column of the
         0.62fr/1fr grid. It caps at 120px, from about 2000px wide. */
      @media (min-width: 851px) {
        body[data-page="home"] .hero {
          container-type: inline-size;
        }

        body[data-page="home"] .hero h1 {
          font-size: clamp(2.5rem, calc((61.7cqi - 2rem) / 9.2), 7.5rem);
        }
      }

      .hero-quiet,
      .claim-quiet {
        color: var(--text-tertiary);
      }

      /* Highlighter fill on the closing word: guglieri.com/work's yellow, but
         a 16px corner rather than a pill. */
      .hero-mark {
        /* Equal 0.14em gap around the ink: above the x-height (0.53em) and
           below the g's descender (0.21em). Raveo's box is 0.97em up and
           0.25em down; a 0.62 line-height takes 0.3em off each side, so the
           top lands at 0.67em, and the bottom padding brings the bottom to
           0.35em below the baseline. */
        display: inline-block;
        line-height: 0.62;
        padding: 0 0.12em 0.4em;
        border-radius: 16px;
        background: var(--highlight);
        color: inherit;
      }

      /* On Home the title arrives once per session, as on guglieri.com/work:
         each word fades in on its own, opacity only, on hand-set uneven delays
         (--delay in the HTML) so it reads like speech rather than a counter.
         280ms on a strong ease-out stands in for that site's spring. Then the
         highlighter wipes "grow" in left to right, the word and its fill
         together, so the ink appears as the stroke passes; clip-path keeps the
         16px corners round, where scaleX would stretch them. The head script
         in index.html sets data-mark-seen on later visits. */
      @media (prefers-reduced-motion: no-preference) {
        body[data-page="home"] .hero-word {
          animation: word-fade 280ms cubic-bezier(0.2, 0.8, 0.2, 1) var(--delay) both;
        }

        body[data-page="home"] .hero-mark {
          /* Ease-in-out, not the UI curve: a highlighter stroke moves at a
             steady pace, where the strong ease-out laid most of it down at once. */
          animation: mark-sweep 450ms cubic-bezier(0.65, 0, 0.35, 1) 750ms both;
        }
      }

      /* Reduced motion: the words are there at once; the mark fades in. */
      @media (prefers-reduced-motion: reduce) {
        body[data-page="home"] .hero-mark {
          animation: mark-fade 150ms ease-out 250ms both;
        }
      }

      html[data-mark-seen] body[data-page="home"] :is(.hero-word, .hero-mark) {
        animation: none;
      }

      @keyframes word-fade {
        from { opacity: 0; }
      }

      @keyframes mark-sweep {
        from { clip-path: inset(0 100% 0 0 round 16px); }
        to { clip-path: inset(0 0 0 0 round 16px); }
      }

      @keyframes mark-fade {
        from { opacity: 0; }
      }

      /* 404: the hero statement alone, tall enough that the footer sits low. The
         code rides above the title on the same column and offset. */
      .hero.hero--notfound {
        min-height: calc(100svh - 12rem);
        grid-template-rows: 1fr auto;
      }

      .notfound-code {
        grid-column: 2;
        align-self: end;
        margin: 0 0 1.5rem;
        transform: translateX(2rem);
        font-size: var(--type-lead);
        color: var(--quiet);
      }

      /* For a word with an ascender and no descender ("work"): the same 0.14em
         gap, measured from the ascender (0.73em) and the baseline instead of
         the x-height and the g. The box then fits inside the line, so a mark
         mid-sentence doesn't push the next line down. */
      .hero-mark.hero-mark--ascender {
        padding: 0.2em 0.12em 0.09em;
      }

      /* The highlighted word is the way home. */
      a.hero-mark {
        text-decoration: none;
        transition: background-color 0.15s ease;
      }

      a.hero-mark:hover {
        background: #f2cb00;
      }

      a.hero-mark:focus-visible {
        outline: 2px solid var(--ink);
        outline-offset: 4px;
      }

      /* Indented, but short of the band's line. Title runs the full measure;
         the supporting copy drops into the second column, as the reference does. */
      .claim {
        display: grid;
        grid-template-columns: minmax(0, 0.75fr) minmax(0, 1fr);
        padding: clamp(6rem, 12vw, 10rem) var(--gutter) 0;
      }

      /* After the opening band the break sits the same 180px (at 1440) from the
         band's credit as its copy does from the cards below it. */
      .opener + .claim {
        padding-top: clamp(6.5rem, 12.5vw, 11.25rem);
      }

      /* Indent is its own value so the title can move without dragging the copy. */
      .claim-role {
        grid-column: 1 / -1;
        margin: 0 0 0 var(--claim-indent);
        font-family: var(--display);
        font-size: clamp(2.75rem, 5vw, 4.75rem);
        font-weight: 500;
        letter-spacing: -0.03em;
        line-height: 1.1;
      }

      /* Keeps the role on its own line. */
      .claim-line {
        display: block;
      }

      .claim-detail {
        grid-column: 2;
        max-width: 46ch;
        margin: clamp(3.5rem, 6vw, 6rem) 0 0;
        font-size: var(--type-lead);
        font-weight: 400;
        line-height: 1.5;
        color: var(--quiet);
      }

      .claim-detail p {
        margin: 0;
      }

      .claim-detail p + p {
        margin-top: clamp(1.25rem, 2vw, 1.75rem);
      }

      .claim-detail strong {
        font-weight: 520;
        color: var(--ink);
      }

      /* Wider gap above than a normal section break: the claim has to finish
         before the evidence starts. Bottom padding keeps the last row off the
         footer rule below. */
      .contribution {
        padding: clamp(6.5rem, 12.5vw, 11.25rem) var(--gutter) var(--section-space);
      }

      /* Case cards follow guglieri.com/work, measured at 1440: three square
         plates 4px apart, a 16px radius, and a small bold caption. The page
         keeps its own gutter so the row lines up with the claim and footer. */
      .case-grid {
        display: grid;
        grid-template-columns: repeat(3, minmax(0, 1fr));
        align-items: start;
        gap: clamp(2rem, 3.5vw, 3.5rem) 4px;
      }

      /* Single column: the result line is indented with a margin, not a second
         track. A grid-column on it would spawn an implicit column and shrink
         the plate. */
      .case-card {
        display: grid;
        grid-template-columns: minmax(0, 1fr);
        align-content: start;
        gap: 0;
        color: inherit;
        text-decoration: none;
      }

      .case-card-plate {
        display: block;
        overflow: hidden;
        aspect-ratio: 1 / 1;
        margin-bottom: 0.75rem;
        border-radius: var(--card-radius);
        background: var(--surface);
        transition: transform 160ms var(--ease-out);
      }

      /* Press feedback. A phone gets no hover zoom, so without this a tap shows
         nothing until the next page paints. Gentler than 0.97: it's a big plate. */
      .case-card:active .case-card-plate {
        transform: scale(0.985);
      }

      .case-card-plate img,
      .case-card-plate video {
        display: block;
        width: 100%;
        height: 100%;
        object-fit: cover;
        object-position: center;
      }

      /* Images get no transition: site.js moves them on hover with a spring.
         pointer-events: otherwise the clip swallows the click meant for the
         card's link. */
      .case-card-plate video {
        pointer-events: none;
        transition: transform 200ms cubic-bezier(0.23, 1, 0.32, 1);
      }

      /* A video plate still zooms flat. Pointer devices only: a tap would fire
         the zoom as the page leaves. */
      @media (hover: hover) and (pointer: fine) {
        .case-card:hover .case-card-plate video {
          transform: scale(1.02);
        }
      }

      /* Backdrop plus window, the same two layers as the study hero and the same
         62% share, so the card reads as a miniature of the page it opens.
         Doubled class: the plate's img sizing sits earlier but at equal weight. */
      .case-card-plate.case-card-plate--composed {
        position: relative;
        display: grid;
        place-items: center;
      }

      /* The painting is its own layer so hover can move it apart from the
         window (site.js). The window sits above it. */
      .case-card-art {
        position: absolute;
        inset: 0;
        background: url("./assets/craftnote-desktop/desktop-art-hero.webp?v=2") center / cover no-repeat;
      }

      /* Much wider share than the hero's 62%: the plate is square and the
         window is not, so anything less leaves it a strip in the middle. */
      .case-card-plate.case-card-plate--composed img {
        width: 92%;
        height: auto;
        object-fit: contain;
        position: relative;
        filter: drop-shadow(0 0.5rem 1rem rgb(17 17 17 / 0.28));
      }

      /* Two cards in a row: each plate is its study's hero in miniature, the
         same 16:9 backdrop and foreground at the same share, so the card and
         the page it opens are one image. */
      .case-grid.case-grid--two {
        grid-template-columns: repeat(2, minmax(0, 1fr));
      }

      .case-grid--two .case-card-plate {
        aspect-ratio: 16 / 9;
      }

      .case-grid--two .case-card-plate.case-card-plate--composed img {
        width: 62%;
      }

      .case-card-plate--mobile .case-card-art {
        background-image: url("./assets/craftnote-mobile/mobile-art-hero.webp?v=2");
      }

      /* The paintings (about 1.9 MB together) wait until the cards near the
         viewport, where site.js sets data-art-ready, so they don't compete with
         the opener's recording. The head script sets data-card-art on returns
         from a study, where the morphing card needs its painting at once. */
      :root:not([data-card-art]) .case-card-plate--composed:not([data-art-ready]) .case-card-art {
        background-image: none;
      }

      .case-grid--two .case-card-plate.case-card-plate--mobile img {
        width: 52.8%;
        height: auto;
      }

      .case-card-plate--pending {
        border: 1px dashed var(--rule);
        background: var(--surface);
      }

      .case-card-heading {
        display: block;
        padding-left: 0.5rem;
      }

      /* Block, or the body's taller line box pushes it off the plate. */
      .case-card-title {
        display: block;
        font-family: var(--sans);
        font-size: 0.75rem;
        font-weight: 700;
        line-height: 1.2;
        color: var(--ink);
      }

      .case-card:focus-visible {
        outline: 2px solid var(--ink);
        outline-offset: 6px;
      }

      .case {
        padding-bottom: clamp(4rem, 7vw, 7rem);
      }

      .case-heading {
        display: grid;
        grid-template-columns: 2.5rem minmax(0, 1fr);
        align-items: baseline;
        column-gap: 0.5rem;
        margin-bottom: clamp(1.25rem, 2vw, 2rem);
      }

      .case-heading .project-meta {
        color: var(--quiet);
      }

      .case-heading h3 {
        margin: 0;
        font-family: var(--display);
        font-size: clamp(1.5rem, 2.4vw, 2.25rem);
        font-weight: 400;
        letter-spacing: -0.02em;
        line-height: 1.1;
      }

      .case-result {
        grid-column: 2;
        max-width: 46ch;
        margin: 0.5rem 0 0;
        font-size: var(--type-body);
        line-height: 1.5;
        color: var(--quiet);
      }

      .case-visual {
        margin-left: 3rem;
      }

      .case-visual span {
        display: block;
        border: 1px dashed var(--rule);
        border-radius: 0.5rem;
        background: var(--surface);
      }

      /* 01: quantity is the argument. */
      .case-visual--many {
        display: grid;
        grid-template-columns: repeat(10, minmax(0, 1fr));
        gap: 0.5rem;
      }

      .case-visual--many span {
        aspect-ratio: 9 / 16;
      }

      /* 02: a sequence, read left to right. */
      .case-visual--sequence {
        display: grid;
        grid-template-columns: repeat(4, minmax(0, 1fr));
        gap: clamp(1.5rem, 3vw, 3rem);
      }

      .case-visual--sequence span {
        aspect-ratio: 9 / 16;
      }

      /* 03: the moment, then the prompt. */
      .case-visual--pairs {
        display: grid;
        grid-template-columns: repeat(3, minmax(0, 1fr));
        gap: clamp(1.5rem, 3vw, 3rem);
      }

      .case-visual--pairs > div {
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 0.5rem;
      }

      .case-visual--pairs span {
        aspect-ratio: 9 / 16;
      }

      /* Opening band: one painting under the whole band, with the windows lifted
         off their own backdrops sitting on it. The painting is the scroller's
         background, so it holds still while the windows pan across it, and an
         expanded window leaves the painting bare behind it.
         Doubled class so it outranks the .flagship-visual padding shorthand below. */
      .flagship-visual.flagship-visual--opener {
        --band-window: clamp(14rem, 29vw, 28rem);
        gap: clamp(1.5rem, 3vw, 3rem);
        /* Full bleed: the painting runs edge to edge with square corners, and
           the first window starts at the page gutter. */
        margin-inline: 0;
        padding: clamp(3rem, 7vw, 7rem) clamp(1.5rem, 3vw, 3rem);
        padding-left: var(--gutter);
        scroll-padding-left: var(--gutter);
        border-radius: 0;
        background: url("./assets/home/home-band-art.webp") center / cover no-repeat;
      }

      .flagship-visual.flagship-visual--opener img {
        height: var(--band-window);
        border-radius: 0;
        filter: drop-shadow(0 0.75rem 1.5rem rgb(17 17 17 / 0.22));
      }

      /* The recordings are rectangular crops with the window's own rounded
         corners still in the frame (no alpha), so the image clips to them and
         a box-shadow stands in for the drop-shadow. */
      .flagship-visual--opener .band-recording img {
        border-radius: 1.5% / 2.4%;
        filter: none;
        box-shadow: 0 0.75rem 1.5rem rgb(17 17 17 / 0.16);
      }

      .flagship-visual--opener .band-recording--browser img {
        border-radius: 1.66% / 2.83%;
      }

      /* The painting's credit sits under the band, not on it: right-aligned
         and held in from the right edge by the gutter. */
      .band-credit {
        margin: 0;
        padding: 0.75rem var(--gutter) 0;
        color: var(--quiet);
        font-family: var(--sans);
        font-size: var(--type-meta);
        line-height: 1.4;
        text-align: right;
      }

      /* Work entry: title left, copy and chips right, then a full-width drag band. */
      .flagship + .flagship {
        padding-top: clamp(5rem, 8vw, 8rem);
      }

      .role-intro {
        display: grid;
        grid-template-columns: minmax(0, 1fr) minmax(0, 1.65fr);
        align-items: start;
        gap: 0 clamp(2rem, 6vw, 7rem);
        margin-left: var(--work-indent);
        padding: 0 var(--gutter) clamp(2.5rem, 4vw, 4rem) 0;
      }

      .role-intro h2 {
        margin: 0;
        font-family: var(--display);
        font-size: clamp(2rem, 3.2vw, 3.25rem);
        font-weight: 520;
        letter-spacing: -0.025em;
        line-height: 1.1;
      }

      .role-intro h2 a {
        text-decoration-thickness: 1px;
      }

      .role-intro p {
        max-width: 46ch;
        margin: 0;
        font-size: var(--type-lead);
        line-height: 1.5;
      }

      .role-chips {
        display: flex;
        flex-wrap: wrap;
        gap: 0.625rem;
        margin: clamp(1.5rem, 2.5vw, 2.25rem) 0 0;
        padding: 0;
        list-style: none;
      }

      .role-chips li {
        padding: 0.5rem 1.125rem;
        border: 1px solid var(--rule);
        border-radius: 999px;
        font-size: var(--type-ui);
        line-height: 1.4;
      }

      .flagship-visual {
        display: flex;
        gap: clamp(1rem, 2vw, 2rem);
        overflow-x: auto;
        padding: 0 var(--gutter) 1rem var(--work-indent);
        scroll-padding-left: var(--work-indent);
        overscroll-behavior-x: contain;
        scrollbar-width: none;
      }

      .flagship-visual {
        user-select: none;
      }

      .flagship-visual[data-dragging] {
        cursor: grabbing;
      }

      .flagship-visual img,
      .flagship-visual video {
        -webkit-user-drag: none;
      }

      .flagship-visual::-webkit-scrollbar {
        display: none;
      }

      .flagship-visual > a,
      .flagship-visual > .gallery-video-trigger {
        display: block;
        flex: 0 0 auto;
      }

      /* Sizes to its own recording, so a band can mix clips of different ratios. */
      .gallery-video-trigger {
        width: auto;
        height: auto;
        padding: 0;
        border: 0;
        background: transparent;
        line-height: 0;
        cursor: pointer;
      }

      .gallery-video-trigger video {
        pointer-events: none;
      }
      .flagship-visual img,
      .flagship-visual video {
        display: block;
        width: auto;
        height: clamp(20rem, 43vw, 42rem);
        border-radius: 1rem;
      }

      .flagship-visual [data-expand-image],
      .flagship-visual [data-expand-image] img {
        cursor: pointer;
      }

      /* Press feedback for band windows. Not on :active: a mouse press here also
         starts a drag. Instead site.js holds data-opening while the full image
         decodes, since that wait is when a click would otherwise look ignored. */
      .flagship-visual [data-expand-image] img {
        transition: transform 160ms var(--ease-out);
      }

      .flagship-visual [data-expand-image][data-opening] img {
        transform: scale(0.985);
      }

      @media (prefers-reduced-motion: reduce) {
        .flagship-visual [data-expand-image] img {
          transition: none;
        }

        .flagship-visual [data-expand-image][data-opening] img {
          transform: none;
        }
      }

      .flagship-visual [data-expand-image]:focus-visible,
      .gallery-video-trigger:focus-visible {
        outline: 2px solid var(--ink);
        outline-offset: 4px;
      }

      .flagship-wide-video {
        position: relative;
        width: min(100%, calc((100svh - 8rem) * 2116 / 1440));
        margin-top: clamp(2rem, 4vw, 4rem);
        margin-inline: auto;
      }

      .flagship-wide-video video {
        display: block;
        width: 100%;
        max-width: 100%;
        height: auto;
        max-height: calc(100svh - 8rem);
        margin-inline: auto;
      }

      .flagship-wide-video canvas {
        position: absolute;
        inset: 0;
        width: 100%;
        height: 100%;
        pointer-events: none;
      }

      .quality-player-controls:not([hidden]) {
        position: absolute;
        inset: auto 0 0;
        display: flex;
        align-items: center;
        gap: 1rem;
        padding: 0.75rem 1rem;
        background: rgb(17 17 17 / 85%);
        color: white;
        opacity: 0;
        transition: opacity 150ms ease;
      }

      .flagship-wide-video:hover .quality-player-controls,
      .flagship-wide-video:focus-within .quality-player-controls,
      .flagship-wide-video[data-paused="true"] .quality-player-controls {
        opacity: 1;
      }

      .quality-player-controls button {
        border: 0;
        padding: 0.25rem;
        background: transparent;
        color: inherit;
        font: inherit;
        font-size: 14px;
        cursor: pointer;
      }

      .quality-player-controls input {
        flex: 1;
        min-width: 0;
        accent-color: white;
      }

      .flagship-wide-video:fullscreen {
        display: grid;
        place-items: center;
        width: 100%;
        height: 100%;
        margin: 0;
        background: #111;
      }

      .flagship-wide-video:fullscreen video {
        width: auto;
        max-height: 100%;
      }

      .flagship-wide-video:fullscreen canvas {
        object-fit: contain;
      }

      .chapters {
        padding-top: clamp(2rem, 4vw, 4rem);
      }

      .chapter {
        padding: 0 var(--gutter) clamp(4rem, 7vw, 7rem);
      }

      .chapter-heading {
        display: grid;
        grid-template-columns: minmax(0, 1fr) minmax(20rem, 0.8fr);
        align-items: end;
        column-gap: clamp(2rem, 7vw, 8rem);
        padding-bottom: clamp(1.5rem, 3vw, 3rem);
      }

      .chapter-heading .project-meta {
        grid-column: 1 / -1;
        margin-bottom: 0.6rem;
      }

      .chapter-heading h3 {
        margin: 0;
        font-family: var(--display);
        font-size: clamp(2.75rem, 5.2vw, 5.5rem);
        font-weight: 400;
        letter-spacing: -0.025em;
        line-height: 1.04;
      }

      .chapter-heading p {
        max-width: 34rem;
        margin: 0;
        font-size: var(--type-lead);
        line-height: 1.45;
      }

      .chapter-visual {
        min-height: clamp(24rem, 58vw, 52rem);
        background: var(--surface);
      }

      .chapter-visual--screens {
        display: flex;
        align-items: center;
        justify-content: center;
        gap: clamp(1.5rem, 5vw, 5rem);
        overflow: hidden;
        padding: clamp(2rem, 5vw, 5rem);
      }

      .chapter-visual--screens img {
        display: block;
        width: auto;
        max-width: 42%;
        max-height: clamp(24rem, 50vw, 46rem);
        object-fit: contain;
        box-shadow: 0 1rem 3rem rgb(17 17 17 / 0.07);
      }

      .chapter-visual--pending {
        display: grid;
        place-items: center;
        color: var(--quiet);
        font-size: var(--type-meta);
      }

      .previous-work {
        padding: clamp(3rem, 7vw, 7rem) var(--gutter) 0;
        border-top: 1px solid var(--rule);
      }

      .previous-work h2 {
        margin: 0;
        font-size: var(--type-statement);
        font-weight: 400;
        letter-spacing: -0.025em;
      }

      .projects {
        overflow: clip;
      }

      .project {
        display: flex;
        flex-direction: column;
        background: var(--paper);
        color: var(--ink);
      }

      .project-header {
        display: grid;
        grid-template-columns: minmax(0, 1.2fr) minmax(20rem, 0.8fr);
        gap: clamp(3rem, 7vw, 8rem);
        align-items: end;
        min-height: clamp(12rem, 16vw, 16rem);
        padding: clamp(2rem, 3vw, 3.5rem) clamp(var(--gutter), 14vw, 16rem) clamp(3rem, 5vw, 5rem);
      }

      .project h2 {
        margin: 0;
        font-family: var(--display);
        font-size: clamp(3.5rem, 5.2vw, 6rem);
        font-weight: 400;
        letter-spacing: -0.02em;
        line-height: 1.04;
      }

      .project h2 a {
        text-decoration: none;
      }

      .project-date {
        margin: 0.65rem 0 0;
        font-size: clamp(1.2rem, 1.8vw, 1.75rem);
        line-height: 1.25;
      }

      .project-summary {
        max-width: 36rem;
        margin: 0;
        font-size: var(--type-lead);
        line-height: 1.5;
      }

      .project-visual {
        display: block;
        overflow: hidden;
        background: var(--surface);
      }

      .project-visual img {
        display: block;
        width: 100%;
        height: auto;
      }

      .project + .project {
        margin-top: clamp(2.5rem, 5vw, 5rem);
      }

      .about {
        display: grid;
        grid-template-columns: minmax(10rem, 0.62fr) minmax(0, 2fr);
        gap: 2rem;
        padding: var(--section-space) var(--gutter);
        border-top: 1px solid var(--rule);
      }

      .about p {
        max-width: 30ch;
        margin: 0;
        font-family: var(--display);
        font-size: var(--type-statement);
        font-weight: 400;
        letter-spacing: -0.025em;
        line-height: 1.08;
      }

      .about a {
        display: inline-block;
        margin-top: 1.5rem;
        font-size: var(--type-lead);
      }

      /* Follows guglieri.com/work: a quiet two-line invitation with the address
         dropped back underneath it, and the profile links bold in a second
         column. Both sides share the same 32px Raveo setting, so weight is the
         only thing separating them. */
      .site-footer {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: clamp(2rem, 4vw, 3.5rem) 2rem;
        padding: clamp(3.5rem, 6.5vw, 6rem) var(--gutter);
        border-top: 1px solid var(--rule);
      }

      /* The study page ends on a full-bleed band, so the footer rule would
         otherwise butt straight against the Integrations artwork. Hold it off
         by roughly the footer's own top padding. */
      body[data-page="study"] .site-footer {
        margin-top: clamp(3.5rem, 7vw, 6.5rem);
      }

      .footer-reach,
      .footer-links {
        margin: 0;
        padding: 0;
        list-style: none;
        font-size: clamp(1.5rem, 2.25vw, 2rem);
        line-height: 1.2;
        letter-spacing: -0.02em;
      }

      .footer-reach p {
        margin: 0;
        font-weight: 400;
      }

      /* The address sits at the reference's 30% so the invitation reads first;
         hovering brings it back to full ink. */
      .footer-reach a {
        color: rgb(17 17 17 / 0.3);
        font-weight: 400;
        text-decoration: none;
        transition: color 160ms ease;
      }

      .footer-reach a:hover,
      .footer-reach a:focus-visible {
        color: var(--ink);
      }

      /* The links mirror the address: full ink at rest, fading to the same 30%
         on hover instead of taking an underline. */
      .footer-links a {
        color: var(--ink);
        font-weight: 800;
        text-decoration: none;
        transition: color 160ms ease;
      }

      .footer-links a:hover,
      .footer-links a:focus-visible {
        color: rgb(17 17 17 / 0.3);
      }


      @media (max-width: 850px) {
        :root {
          --work-indent: var(--gutter);
          --band-indent: var(--gutter);
        }

        /* Mobile keeps the identity band to the name and email alone. With the
           clock's second line gone, the band's padding evens out so the single
           line sits centred. */
        .local-time {
          display: none;
        }

        .site-header {
          padding-block: 1rem;
        }

        .hero,
        .about {
          grid-template-columns: 1fr;
        }

        .hero {
          padding-top: 2rem;
          padding-bottom: 2rem;
        }

        .hero h1 {
          grid-column: 1;
          font-size: clamp(2.25rem, 9vw, 3rem);
          transform: none;
        }

        /* The set break after "and" is for the wide column; a phone wraps
           the statement on its own. */
        .hero-break {
          display: none;
        }

        /* On a phone the statement leads: top of the screen, space below. */
        .hero.hero--notfound {
          min-height: calc(100svh - 10rem);
          grid-template-rows: auto auto 1fr;
          padding-top: 4rem;
        }

        .notfound-code {
          grid-column: 1;
          transform: none;
        }

        /* No alignment play at phone width: the claim loses its indent and
           second column. Blocks centre; the text inside stays left-aligned. */
        .claim {
          grid-template-columns: 1fr;
        }

        .claim-role {
          margin-left: 0;
        }

        .claim-detail {
          grid-column: 1;
          margin-inline: auto;
        }

        .project-header {
          grid-template-columns: 1fr;
          gap: 2rem;
          padding-inline: var(--gutter);
        }

        .chapter-heading {
          grid-template-columns: 1fr;
          gap: 1rem;
        }

        .role-intro {
          grid-template-columns: 1fr;
          row-gap: 1.25rem;
        }

        .flagship-visual img,
        .flagship-visual video {
          height: 60vw;
        }

        .flagship-visual.flagship-visual--opener {
          --band-window: 42vw;
        }

        .chapter-heading .project-meta {
          margin-bottom: 0;
        }

        .chapter-visual--screens {
          min-height: 24rem;
          padding: 2rem 1rem;
          gap: 1rem;
        }

        .chapter-visual--screens img {
          max-width: 46%;
          max-height: 32rem;
        }

      }

      @media (max-width: 520px) {
        .site-footer {
          grid-template-columns: 1fr;
        }
      }

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

        .nav-tab {
          transition: none;
        }
      }

/* Feed layout; uses the same tokens and shared shell. */
      .feed-intro {
        display: flex;
        align-items: baseline;
        justify-content: space-between;
        gap: 2rem;
        padding: clamp(2.5rem, 4vw, 4rem) var(--gutter) clamp(1.75rem, 3vw, 3rem);
        border-bottom: 1px solid var(--rule);
      }

      .feed-intro h1 {
        margin: 0;
        font-size: var(--type-display);
        font-weight: 400;
        letter-spacing: -0.035em;
        line-height: 1;
      }

      .feed {
        display: grid;
        grid-template-columns: repeat(12, minmax(0, 1fr));
        grid-auto-rows: clamp(3rem, 4.6vw, 4.5rem);
        gap: clamp(0.75rem, 1.2vw, 1.25rem);
        padding: clamp(0.75rem, 1.2vw, 1.25rem) var(--gutter) clamp(5rem, 8vw, 8rem);
      }

      figure {
        min-width: 0;
        min-height: 0;
        margin: 0;
      }

      .tile-link {
        display: grid;
        grid-template-rows: minmax(0, 1fr) auto;
        height: 100%;
        text-decoration: none;
      }

      .tile-frame {
        display: grid;
        width: 100%;
        overflow: hidden;
        place-items: center;
        background: var(--surface);
        min-height: 0;
        transition: transform 160ms var(--ease-out);
      }

      .tile-link:active .tile-frame {
        transform: scale(0.985);
      }

      .tile-frame img {
        display: block;
        width: 100%;
        height: 100%;
        object-fit: contain;
        transition: transform 180ms cubic-bezier(0.23, 1, 0.32, 1);
      }

      @media (hover: hover) and (pointer: fine) {
        .tile-link:hover .tile-frame img { transform: scale(1.01); }
      }

      figcaption {
        display: flex;
        justify-content: space-between;
        gap: 1rem;
        padding: 0.65rem 0 0.25rem;
        font-size: var(--type-ui);
        line-height: 1.4;
      }

      .tile-1 { grid-column: 1 / 9; grid-row: 1 / 7; }
      .tile-2 { grid-column: 9 / 13; grid-row: 1 / 10; }
      .tile-3 { grid-column: 1 / 5; grid-row: 7 / 10; }
      .tile-4 { grid-column: 5 / 9; grid-row: 7 / 10; }
      .tile-5 { grid-column: 1 / 5; grid-row: 10 / 19; }
      .tile-6 { grid-column: 5 / 13; grid-row: 10 / 16; }
      .tile-7 { grid-column: 5 / 9; grid-row: 16 / 19; }
      .tile-8 { grid-column: 9 / 13; grid-row: 16 / 19; }
      .tile-9 { grid-column: 1 / 13; grid-row: 19 / 25; }


      .feed-count,
      .tile-type {
        font-size: var(--type-meta);
        color: var(--quiet);
      }

      @media (max-width: 850px) {
        .feed-intro {
          padding-top: 2.5rem;
        }

        .feed {
          grid-auto-rows: auto;
        }

        .tile-1,
        .tile-2,
        .tile-3,
        .tile-4,
        .tile-5,
        .tile-6,
        .tile-7,
        .tile-8,
        .tile-9 {
          grid-column: 1 / -1;
          grid-row: auto;
        }

        .tile-frame img {
          height: auto;
        }
      }

      @media (prefers-reduced-motion: reduce) {
        .tile-frame img {
          transition: none;
        }

        .tile-link:hover .tile-frame img {
          transform: none;
        }
      }

/* Case-study page. Follows the guglieri.com/work/composer rhythm measured at
   1440: title row, full-bleed hero, offset intro with credits, then visual
   groups introduced by a small two-line label. Visuals sit near the viewport
   edge and 4px apart, so the page reads as a showcase, not an article.
   Scoped to the page so the dormant .study-heading / .study-gallery rules
   above stay out of the way. */
      html:has(body[data-page="study"]) {
        scrollbar-gutter: stable;
      }

      body[data-page="study"] {
        --study-gutter: clamp(1rem, 1.2vw, 1.25rem);
        /* Shared by the lede and the copy sections so the title starts on the
           same x as the copy below it. */
        --study-columns: minmax(0, 0.87fr) minmax(0, 1fr);
      }

      /* Platforms left, title on the copy column so the two share a left edge. */
      .study-lede {
        display: grid;
        grid-template-columns: var(--study-columns);
        column-gap: 1.5rem;
        align-items: baseline;
        padding: clamp(3rem, 5vw, 5.5rem) var(--study-gutter) clamp(2rem, 3.5vw, 3.5rem);
      }

      .study-platforms {
        display: flex;
        align-items: baseline;
        gap: 0.75rem;
        margin-left: var(--lede-indent);
        color: var(--text-tertiary);
      }

      .study-platforms svg {
        display: block;
        width: clamp(1.125rem, 1.5vw, 1.375rem);
        height: auto;
        fill: currentColor;
      }

      /* Weight matches the reference's Raveo setting (wght 800); it carries the
         title at display size where 500 reads thin. */
      .study-lede h1 {
        grid-column: 2;
        margin: 0;
        font-family: var(--display);
        font-size: clamp(2.5rem, 4.6vw, 4.25rem);
        font-weight: 800;
        letter-spacing: -0.04em;
        line-height: 1.06;
        text-align: left;
      }

      .study-year {
        grid-column: 3;
        font-size: var(--type-lead);
        line-height: 1.2;
        color: var(--text-tertiary);
      }

      .study-hero {
        padding-inline: var(--study-gutter);
      }

      /* Backdrop and window are separate layers rather than one baked image, so
         the backdrop stays a one-line change. Window sits at 62% of the frame,
         the proportion the reference uses. */
      .study-plate.study-plate--hero {
        display: grid;
        place-items: center;
        aspect-ratio: 16 / 9;
        background: url("./assets/craftnote-desktop/desktop-art-hero.webp?v=2") center / cover no-repeat;
      }

      /* The window PNG carries its own rounded corners, so the shadow has to
         follow the alpha; box-shadow would trace the square border box.
         Doubled class: the .study-plate img width sits later in the file. */
      .study-plate.study-plate--hero img {
        width: 62%;
        height: auto;
        filter: drop-shadow(0 1.5rem 2.5rem rgb(17 17 17 / 0.3));
      }

      /* Three phones at 60% of the plate's height, with room around them. A
         percentage height doesn't resolve in the plate's grid, so it is set as
         the width that gives it: 60% x 9/16 x the strip's 4151/2652. */
      .study-plate.study-plate--hero.study-plate--hero-phones img {
        width: 52.8%;
        height: auto;
      }

      /* Left column stays empty; the copy takes the right, as the reference does. */
      .study-intro {
        display: grid;
        grid-template-columns: var(--study-columns);
        column-gap: 1.5rem;
        padding: clamp(4rem, 7vw, 7rem) var(--study-gutter) clamp(2rem, 3.2vw, 3.25rem);
      }

      .study-intro > div {
        grid-column: 2;
        /* Runs the column, stopping short of the visuals' right edge. */
        max-width: calc(100% - 2rem);
      }

      .study-intro p {
        margin: 0;
        font-family: var(--display);
        font-size: clamp(1.25rem, 1.9vw, 1.75rem);
        font-weight: 400;
        letter-spacing: -0.02em;
        line-height: 1.3;
      }

      .study-intro p + p {
        margin-top: clamp(1rem, 1.6vw, 1.5rem);
      }

      /* Same offset column as the opening copy, used as a break between groups. */
      .study-intro--break {
        padding-block: clamp(4.5rem, 8vw, 8rem);
      }

      /* Set as a standfirst: rule above, tighter measure, and the lead sentence
         carrying the weight. Editorial without reaching for a serif. */
      /* A left break may run on into the right column rather than stopping at
         the narrow left one. The cap is in rem, not ch: ch here resolves at
         the box's body size, not the copy's, and 44ch held the copy to about
         27 characters a line. */
      .study-intro--break > div {
        grid-column: 1 / -1;
        margin-left: var(--lede-indent);
        border-top: 1px solid var(--rule);
        padding-top: clamp(1.5rem, 2.4vw, 2.25rem);
        max-width: 39rem;
      }

      /* Two steps only: everything is quiet except what the bold picks out. */
      .study-intro--break p {
        font-size: clamp(1.5rem, 2.4vw, 2.125rem);
        letter-spacing: -0.03em;
        line-height: 1.2;
        color: var(--quiet);
      }

      /* Steps back from the lead without going as pale as the hero's quiet grey,
         which is set for display sizes. */
      .break-quiet,
      .study-intro--break strong.break-quiet {
        color: var(--quiet);
      }

      /* Chapter title: opens a major section of the study. One step under the
         page title in the same bold Raveo, on the labels' left edge, with a
         rule inset equally from both sides closing it off. */
      /* Margin, not padding, carries the indent so the rule starts where the
         title does, and the same inset on the right keeps it centred. */
      .study-chapter {
        margin: 0 calc(var(--study-gutter) + var(--lede-indent));
        padding: clamp(5rem, 9vw, 8.5rem) 0 clamp(1rem, 1.6vw, 1.5rem);
        border-bottom: 1px solid var(--rule);
      }

      .study-chapter h2 {
        margin: 0;
        font-family: var(--display);
        font-size: clamp(2rem, 3.4vw, 3rem);
        font-weight: 800;
        letter-spacing: -0.04em;
        line-height: 1.06;
        color: var(--ink);
      }

      /* The chapter's rule stands in for the first break's own, and the
         break sits closer since the title has already opened the section. */
      .study-chapter + .study-intro--break {
        padding-top: clamp(2rem, 3.5vw, 3.5rem);
      }

      .study-chapter + .study-intro--break > div {
        border-top: 0;
        padding-top: 0;
      }

      .study-chapter + .study-intro--break .break-step {
        top: 0;
      }

      /* A break that continues the one before it: the pair reads as one beat,
         so the second drops most of the section's top padding. */
      .study-intro--break + .study-intro--break {
        padding-top: clamp(1.5rem, 2.5vw, 2.5rem);
      }

      .study-intro--break:has(+ .study-intro--break) {
        padding-bottom: 0;
      }

      /* No rule of its own: it continues the break above, not a new one. */
      .study-intro--break + .study-intro--break > div {
        border-top: 0;
      }

      /* Step number for a paired break, hung in the margin left of the copy on
         its first line, at the copy's own size and quiet tone. */
      .study-intro--break > div {
        position: relative;
      }

      .break-step {
        position: absolute;
        top: clamp(1.5rem, 2.4vw, 2.25rem);
        right: 100%;
        margin-right: clamp(1rem, 2vw, 1.75rem);
        font-size: clamp(1.5rem, 2.4vw, 2.125rem);
        letter-spacing: -0.03em;
        line-height: 1.2;
        color: var(--quiet);
        font-variant-numeric: tabular-nums;
      }

      /* Breaks sit on the left column by default; this one returns to the copy
         column, on the same left edge as the opening copy. */
      .study-intro--break.study-intro--right > div {
        grid-column: 2;
        margin-left: 0;
      }


      /* The break already carries the space; the label below shouldn't add more. */
      .study-intro--break + .study-note {
        margin-top: 0;
        padding-top: 0;
      }

      .study-intro--break strong {
        font-weight: 800;
        color: var(--ink);
      }

      /* Doubled up so it outranks the .study-intro p display size above. */
      .study-intro .study-credits {
        margin: clamp(2rem, 3vw, 2.75rem) 0 0;
        font-family: var(--sans);
        font-size: 0.875rem;
        font-weight: 400;
        letter-spacing: normal;
        line-height: 1.6;
        color: var(--quiet);
      }

      .study-credits span {
        display: block;
      }

      /* Two lines: what the group is, then what it shows. Tied to the visual
         below it, spaced away from the one above. */
      .study-note {
        padding: clamp(3.5rem, 6vw, 5.5rem) var(--study-gutter) 4px
          calc(var(--study-gutter) + var(--lede-indent));
      }

      .study-note strong {
        display: block;
        font-size: var(--type-meta);
        font-weight: 800;
        line-height: 1.4;
        color: var(--ink);
      }

      .study-note span {
        display: block;
        font-size: var(--type-meta);
        line-height: 1.4;
        color: var(--quiet);
      }

      .study-row {
        display: grid;
        grid-template-columns: repeat(var(--row-columns, 1), minmax(0, 1fr));
        /* Visuals of unequal height hang from a common top edge; stretching
           would show the plate's own background under the shorter ones. */
        align-items: start;
        gap: 4px;
        padding: 0 var(--study-gutter) 4px;
      }

      .study-row--two { --row-columns: 2; }

      /* Two visuals of different proportions sharing one frame, so the row keeps
         a flat bottom edge: the taller one is cropped rather than letterboxed. */
      .study-row--even {
        --row-aspect: 2086 / 1344;
      }

      .study-row--even .study-plate {
        aspect-ratio: var(--row-aspect);
      }

      /* Only the still is cropped to the frame. Cropping a video would clip it
         again, and a clipped video shifts colour as the compositor switches
         paths mid-scroll — so each row's frame matches its own recording. */
      .study-row--even .study-plate img {
        height: 100%;
        object-fit: cover;
      }

      /* The integrations recording is a rectangular crop with no alpha, so its
         plate draws the corner. The window beside it carries its radius baked
         into the PNG, and this matches it at the row's display width. */
      .study-row--integrations .study-plate--lift {
        border-radius: 0.5rem;
      }

      /* The landing recording is cropped to the browser window, whose rounded
         corners (32px at 1928 x 1132) are still in the frame; the plate clips
         to them. The phones beside it centre on the window's height. */
      .study-row--landing {
        align-items: center;
      }

      /* Two groups, not one row of four: the phones sit a phone-gap apart
         (baked into the PNG), and a much wider gap separates them from the
         window. Columns in the two visuals' ratios (1928/1132, 2958/1927),
         so the phones stand exactly as tall as the window. */
      .study-row.study-row--band.study-row--landing {
        grid-template-columns: 1.703fr 1.535fr;
        column-gap: clamp(2.5rem, 6vw, 6rem);
      }

      @media (max-width: 520px) {
        .study-row.study-row--band.study-row--landing {
          grid-template-columns: minmax(0, 1fr);
        }
      }

      .study-row--landing .study-plate--lift {
        border-radius: 1.66% / 2.83%;
      }

      .study-row--three { --row-columns: 3; }

      /* Phones on tiles of their own rather than one band: the unbanded row's
         flat plates, 4px apart in a 2x2 grid, each screen centred and kept
         whole. Landscape tiles, so the grid stays short at full width. Sized
         by width so the img box is the phone itself, which the lightbox
         animates from; 24% puts the phone at about 78% of the tile height. */
      .study-row--tiles {
        --row-columns: 2;
        --phone: 24%;
      }

      .study-row--tiles .study-plate {
        display: grid;
        place-items: center;
        aspect-ratio: 16 / 10;
      }

      .study-row--tiles .study-plate img {
        width: var(--phone);
        filter: drop-shadow(0 0.75rem 1.5rem rgb(17 17 17 / 0.18));
      }

      /* A tile holding a short sequence: the phones composed into one
         transparent image, so they expand together. The image carries a gap
         of 1/12 of a phone between each, so n phones span (13n - 1) / 12
         phone widths and every phone stays at the single-phone size. */
      .study-row--tiles .study-plate--duo img {
        width: calc(var(--phone) * 25 / 12);
      }

      .study-row--tiles .study-plate--trio img {
        width: calc(var(--phone) * 38 / 12);
      }

      .study-row--tiles .study-plate--quartet img {
        width: calc(var(--phone) * 51 / 12);
      }

      /* A row with four phones in one tile: smaller phones throughout the row
         so the four fit and its neighbouring tile matches. */
      .study-row--tiles-tight {
        --phone: 21%;
      }

      /* One tile across the row, the height of a 16:10 tile. Its image is a
         strip of App Store screenshots sized to the phones' 78% height. */
      .study-row--tiles .study-plate--wide {
        grid-column: 1 / -1;
        aspect-ratio: 32 / 10;
      }

      .study-row--tiles .study-plate--wide img {
        width: 72%;
      }

      /* A sequence alone on a wide tile: the tile spans two, so the phones
         keep the half-width tiles' phone size. */
      .study-row--tiles .study-plate--wide.study-plate--duo img {
        width: calc(var(--phone) * 25 / 24);
      }

      .study-row--tiles .study-plate--wide.study-plate--trio img {
        width: calc(var(--phone) * 38 / 24);
      }

      .study-row--tiles .study-plate--wide.study-plate--quartet img {
        width: calc(var(--phone) * 51 / 24);
      }

      .study-row--tiles .study-plate--wide.study-plate--single img {
        width: calc(var(--phone) / 2);
      }

      /* A phone's feature callouts, set as labels under its tile rather than
         as bubbles on it. */
      .study-note-pair.study-note-pair--four {
        grid-template-columns: repeat(4, minmax(0, 1fr));
      }

      .study-row--tiles + .study-note-pair--four .study-note {
        padding-top: clamp(1.25rem, 2vw, 1.75rem);
      }

      @media (max-width: 850px) {
        .study-note-pair.study-note-pair--four {
          grid-template-columns: repeat(2, minmax(0, 1fr));
        }
      }

      /* A phone recording (transparent WebM) on a wide tile, at the same
         phone size. */
      .study-row--tiles .study-plate video {
        width: var(--phone);
        filter: drop-shadow(0 0.75rem 1.5rem rgb(17 17 17 / 0.18));
      }

      .study-row--tiles .study-plate--wide video {
        width: calc(var(--phone) / 2);
        filter: drop-shadow(0 0.75rem 1.5rem rgb(17 17 17 / 0.18));
      }

      /* Godot Tech studies: backdrops stay the flat plate until their art is
         chosen. */
      .study-plate.study-plate--hero.study-plate--unpainted {
        background: var(--surface);
      }

      /* One painting across a row of tiles: each tile shows its own part of
         it, and the gaps cut through it. The painting covers the whole row,
         centred, and each tile offsets it by its column. Measured in cqw of
         the row, the tiles' container. Defaults fit two 16:10 tiles and a
         3.2:1 crop; --mural-cols, --tile-ratio and --mural-ratio change them
         (one wide tile, three 4:5 moments). */
      .study-row--mural {
        container-type: inline-size;
        --mural-cols: 2;
        --tile-ratio: 1.6;
        --mural-ratio: 3.2;
        --mural-gap: 4px;
        gap: var(--mural-gap);
        --tile-w: calc((100cqw - var(--mural-gap) * (var(--mural-cols) - 1)) / var(--mural-cols));
        --tile-h: calc(var(--tile-w) / var(--tile-ratio));
        --mural-w: max(100cqw, var(--tile-h) * var(--mural-ratio));
        --mural-x: calc((var(--mural-w) - 100cqw) / 2);
        --mural-y: calc((var(--mural-w) / var(--mural-ratio) - var(--tile-h)) / 2);
      }

      .study-row--mural .study-plate {
        --col: 0;
        background: var(--mural) no-repeat;
        background-size: var(--mural-w) auto;
        background-position:
          calc((var(--mural-x) + var(--col) * (var(--tile-w) + var(--mural-gap))) * -1)
          calc(var(--mural-y) * -1);
      }

      .study-row--mural .study-plate:nth-child(2) { --col: 1; }
      .study-row--mural .study-plate:nth-child(3) { --col: 2; }

      .study-row--mural-wide {
        --mural-cols: 1;
        --tile-ratio: 3.2;
      }

      .study-row--mural-moments {
        --mural-cols: 3;
        --tile-ratio: 0.8;
        --mural-ratio: 2.398;
      }

      /* Results under a group of tiles: plain type, no plate. Figures at the
         break copy's size, quiet lead-in and bold number the way a break mixes
         the two; labels set like the section sublines. Starts on the labels'
         left edge. */
      /* Bottom padding closes the section: the results end it, so the next
         break needs more room than it would after a row of visuals. */
      .study-metrics {
        padding: clamp(3rem, 5vw, 4.5rem) var(--study-gutter) clamp(2rem, 4vw, 3.5rem)
          calc(var(--study-gutter) + var(--lede-indent));
      }

      .study-metrics-list {
        display: grid;
        grid-template-columns: repeat(3, minmax(0, 1fr));
        gap: 2rem;
      }

      .study-metric {
        margin: 0;
      }

      .study-metric strong {
        display: block;
        font-size: clamp(1.5rem, 2.4vw, 2.125rem);
        font-weight: 800;
        letter-spacing: -0.03em;
        line-height: 1.2;
        color: var(--ink);
      }

      .study-metric strong .metric-quiet {
        font-weight: 400;
        color: var(--quiet);
      }

      .study-metric > span {
        display: block;
        margin-top: 0.375rem;
        font-size: var(--type-meta);
        line-height: 1.4;
        color: var(--quiet);
      }

      @media (max-width: 520px) {
        .study-metrics-list {
          grid-template-columns: 1fr;
          gap: 1.25rem;
        }
      }

      /* Labels for two tiles side by side: the same two-line label, one over
         each tile, each held in from its own tile's edge by the lede indent
         so the pair lines up with a single label's left edge. */
      .study-note-pair {
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 4px;
        padding: 0 var(--study-gutter);
      }

      /* Tighter above than a new section's label: the pair continues the
         group started by the tile above it. */
      .study-note-pair .study-note {
        padding: clamp(2rem, 3.4vw, 3rem) 0 4px var(--lede-indent);
      }

      /* Labels living inside their tiles' row, one per column: auto-placement
         puts the notes on the first grid row and the tiles under them, so the
         desktop reads exactly like the old separate pair — and mobile can
         reorder each label directly above its own tile. */
      .study-row--paired .study-note {
        padding: clamp(2rem, 3.4vw, 3rem) 0 0 var(--lede-indent);
      }

      /* The row's own gap already provides the 4px the pair's bottom padding
         used to. Mural columns count from the plates' new child positions. */
      .study-row--mural.study-row--paired .study-plate:nth-child(3) { --col: 0; }
      .study-row--mural.study-row--paired .study-plate:nth-child(4) { --col: 1; }
      .study-row--mural.study-row--paired-three .study-plate:nth-child(4) { --col: 0; }
      .study-row--mural.study-row--paired-three .study-plate:nth-child(5) { --col: 1; }
      .study-row--mural.study-row--paired-three .study-plate:nth-child(6) { --col: 2; }

      .study-plate {
        display: block;
        min-width: 0;
        overflow: hidden;
        border-radius: 1rem;
        background: var(--surface);
      }

      .study-plate img,
      .study-plate video {
        display: block;
        width: 100%;
        height: auto;
      }

      /* The same quiet hover as a Feed tile, so a plate that opens reads as one.
         Only the picture scales; a composed plate's backdrop holds still. */
      a.study-plate img {
        transition: transform 180ms var(--ease-out);
      }

      @media (hover: hover) and (pointer: fine) {
        a.study-plate:hover img { transform: scale(1.01); }
      }

      /* Press feedback, as on case cards and Feed tiles. The lightbox waits for
         the full image to decode, so a tap would otherwise show nothing at first.
         The whole plate presses, so it doesn't stack with the picture's hover. */
      a.study-plate,
      button.study-plate {
        transition: transform 160ms var(--ease-out);
      }

      a.study-plate:active,
      button.study-plate:active {
        transform: scale(0.985);
      }

      /* Rounding a video by clipping its parent drops it off the compositor's
         fast path, and scrolling then flips it between paths — which shifts the
         colour. Round the video itself instead. */
      .study-plate video {
        border-radius: inherit;
      }

      /* Keyed to the element, not its parent: the lightbox moves this same video
         into the dialog, and a rule scoped to the plate would stop applying
         there — leaving the two states on different paths, which is the colour
         change you see on expand and collapse. */
      [data-gallery-video] {
        transform: translateZ(0);
        backface-visibility: hidden;
      }

      a.study-plate,
      button.study-plate {
        cursor: pointer;
      }

      /* A plate that is also the recording's expand trigger. */
      button.study-plate {
        width: 100%;
        padding: 0;
        border: 0;
        font: inherit;
        line-height: 0;
        appearance: none;
      }

      button.study-plate video {
        pointer-events: none;
      }

      a.study-plate:focus-visible,
      button.study-plate:focus-visible {
        outline: 2px solid var(--ink);
        outline-offset: 4px;
      }

      /* One backdrop under the whole group; the windows sit on top of it. */
      .study-row.study-row--band {
        margin-inline: var(--study-gutter);
        padding: clamp(2.5rem, 4.5vw, 4.5rem) clamp(1.5rem, 3vw, 3rem);
        gap: clamp(1rem, 2vw, 2rem);
        border-radius: var(--card-radius);
        background: var(--surface);
      }

      /* Same band, artwork instead of a flat tint. The piece is a token, so a
         band swaps art by setting --band-art rather than by a new rule. */
      .study-row.study-row--band.study-row--art {
        background: var(--band-art, url("assets/craftnote-desktop/latest-backdrop.webp")) center / cover;
      }

      .study-row--art-team {
        --band-art: url("assets/craftnote-desktop/team-backdrop.webp");
      }

      /* Bands stacked inside one group read as separate beats, not a mosaic. */
      .study-row--band + .study-row--band {
        margin-top: clamp(0.75rem, 1.5vw, 1.25rem);
      }

      /* A single visual sitting on the band rather than filling it, so the
         artwork reads as a backdrop and the clip stays a comfortable size. */
      .study-row--inset {
        justify-items: center;
      }

      .study-row--inset .study-plate,
      .study-row--inset button.study-plate {
        width: 60%;
      }

      /* The email cards are already a composed card on a white page, so they
         read heavy at full column width. Sitting them back a little lets the
         band's artwork carry more of the row. */
      .study-row--mails {
        justify-items: center;
      }

      .study-row--mails .study-plate {
        width: 85%;
      }

      /* Opaque screens on a band: they carry no chrome and no alpha, so the
         plate's own radius and a box-shadow stand in for a window. */
      .study-plate--lift {
        box-shadow: 0 0.75rem 1.5rem rgb(17 17 17 / 0.16);
      }

      /* While its screen is expanded, a lifted card on artwork leaves the art
         bare, the way a window does, rather than an empty card and shadow. */
      .study-row--art .study-plate--lift[data-expanded] {
        background: none;
        box-shadow: none;
      }

      /* Windows lifted off their gradient backdrops: no plate of their own, and
         the shadow follows the PNG's alpha the way the hero's does. */
      .study-plate.study-plate--window {
        background: none;
        border-radius: 0;
        overflow: visible;
      }

      .study-plate.study-plate--window img {
        filter: drop-shadow(0 0.75rem 1.5rem rgb(17 17 17 / 0.22));
      }

      .study-plate--pending {
        display: grid;
        place-items: center;
        aspect-ratio: 16 / 10;
        border: 1px dashed var(--rule);
        color: var(--quiet);
        font-size: var(--type-meta);
      }

      /* Discovery's cause and effect: three 4:5 tiles, each a phone at the
         trigger with its intro sheet rising over it (see below). */
      .study-row--moments {
        --row-columns: 3;
      }

      .study-plate.study-plate--moment {
        display: flex;
        flex-direction: column;
        align-items: center;
        aspect-ratio: 4 / 5;
        padding-top: clamp(1.25rem, 2.2vw, 2rem);
      }
      /* The polished moment: the user's own screen at the trigger (a framed
         phone mock), then the app's scrim and the intro sheet rising over it,
         clipped to the phone's screen. The screen box is measured off the
         mock: 1167 x 2532 at (72, 60) in a 1311 x 2652 frame, corner radius
         138 (the frame every mobile mock shares). The flag rings the trigger
         the case-study doc names. With no motion the tile holds its end state. */
      .study-plate--moment-live {
        --app-accent: #556cff;
        --app-tint: #e8e9ff;
      }

      .moment-stage {
        display: flex;
        flex: 1;
        justify-content: center;
        width: 100%;
        min-height: 0;
        padding-bottom: clamp(1.25rem, 2.2vw, 2rem);
      }

      .moment-phone {
        position: relative;
        display: block;
        height: 100%;
        aspect-ratio: 1311 / 2652;
        line-height: 0;
        filter: drop-shadow(0 0.75rem 1.5rem rgb(17 17 17 / 0.18));
      }

      .study-plate .moment-phone > img {
        width: 100%;
        height: 100%;
      }

      .moment-screen {
        position: absolute;
        top: 2.262%;
        left: 5.492%;
        width: 89.016%;
        height: 95.475%;
        overflow: hidden;
        border-radius: 11.825% / 5.45%;
      }

      /* Each tile places its ring over its own trigger, in percentages of
         the screen box, through --flag-x, --flag-y, --flag-w and --flag-h. */
      .moment-flag {
        position: absolute;
        top: var(--flag-y);
        left: var(--flag-x);
        width: var(--flag-w);
        height: var(--flag-h);
        border-radius: 999px;
        box-shadow: 0 0 0 2px var(--app-accent), 0 0 0 6px rgb(85 108 255 / 0.2);
        opacity: 0;
      }

      .moment-scrim {
        position: absolute;
        inset: 0;
        background: rgb(17 17 17 / 0.16);
      }

      .study-plate .moment-sheet-img {
        position: absolute;
        bottom: 0;
        left: 0;
        width: 100%;
        height: auto;
      }

      /* The ring, scrim and sheet ride over the phone, so they leave with it
         while it is expanded instead of floating on the bare art. */
      .moment-phone[data-expanded] .moment-screen {
        visibility: hidden;
      }

      .moment-phone:focus-visible {
        outline: 2px solid var(--ink);
        outline-offset: 4px;
      }

      /* One loop: a beat on the plain transcript, the default name flags,
         then the scrim and sheet come up, hold, and everything resets. */
      @media (prefers-reduced-motion: no-preference) {
        .is-playing .moment-flag {
          animation: moment-flag 8s ease-in-out infinite;
        }

        .is-playing .moment-scrim {
          animation: moment-scrim 8s ease-in-out infinite;
        }

        .is-playing .moment-sheet-img {
          animation: moment-sheet 8s cubic-bezier(0.2, 0.8, 0.2, 1) infinite;
        }
      }

      @keyframes moment-flag {
        0%, 8% { opacity: 0; transform: scale(1.06); }
        14%, 34% { opacity: 1; transform: scale(1); }
        40%, 100% { opacity: 0; transform: scale(1); }
      }

      @keyframes moment-scrim {
        0%, 32% { opacity: 0; }
        42%, 88% { opacity: 1; }
        96%, 100% { opacity: 0; }
      }

      @keyframes moment-sheet {
        0%, 34% { transform: translateY(100%); }
        46%, 88% { transform: translateY(0); }
        97%, 100% { transform: translateY(100%); }
      }
      /* Labels over three tiles, and results aligned under them. */
      .study-metrics.study-metrics--tiles {
        padding-left: var(--study-gutter);
      }

      .study-metrics--tiles .study-metrics-list {
        gap: 4px;
      }

      .study-metrics--tiles .study-metric {
        padding-left: var(--lede-indent);
      }

      .study-outro {
        padding: clamp(4rem, 7vw, 7rem) var(--study-gutter);
      }

      @media (max-width: 850px) {
        .study-lede {
          grid-template-columns: 1fr;
          align-items: flex-start;
          row-gap: 1.25rem;
        }

        /* No alignment play at phone width: indents drop, capped blocks centre,
           and the text inside stays left-aligned. */
        .study-lede h1 {
          grid-column: 1;
          margin: 0;
          font-size: clamp(2rem, 7vw, 2.75rem);
        }

        .study-platforms {
          margin-left: 0;
        }

        .study-year {
          grid-column: auto;
        }

        .study-intro {
          grid-template-columns: 1fr;
        }

        .study-intro > div {
          grid-column: 1;
          margin-inline: auto;
        }

        .study-intro--break > div {
          margin-inline: auto;
        }

        .study-note {
          padding-left: var(--study-gutter);
        }

        .study-note-pair .study-note {
          padding-left: 0;
        }

        .study-metrics--tiles .study-metric {
          padding-left: 0;
        }

        /* Never two tiles side by side on mobile: every row stacks, and each
           paired label reorders to sit directly above its own tile. */
        .study-row--two,
        .study-row--three,
        .study-row--tiles,
        .study-row--moments { --row-columns: 1; }

        .study-row.study-row--band.study-row--landing {
          grid-template-columns: minmax(0, 1fr);
        }

        .study-row--paired .study-note:nth-child(1) { order: 1; }
        .study-row--paired .study-plate:nth-child(3) { order: 2; }
        .study-row--paired .study-note:nth-child(2) { order: 3; }
        .study-row--paired .study-plate:nth-child(4) { order: 4; }

        .study-row--paired-three .study-note:nth-child(1) { order: 1; }
        .study-row--paired-three .study-plate:nth-child(4) { order: 2; }
        .study-row--paired-three .study-note:nth-child(2) { order: 3; }
        .study-row--paired-three .study-plate:nth-child(5) { order: 4; }
        .study-row--paired-three .study-note:nth-child(3) { order: 5; }
        .study-row--paired-three .study-plate:nth-child(6) { order: 6; }

        .study-row--paired .study-note {
          padding-left: 0;
        }

        /* Stacked mural tiles each show the painting's centre crop. */
        .study-row--mural { --mural-cols: 1; }

        .study-row--mural .study-plate:nth-child(2),
        .study-row--mural .study-plate:nth-child(3),
        .study-row--mural.study-row--paired .study-plate:nth-child(4),
        .study-row--mural.study-row--paired-three .study-plate:nth-child(5),
        .study-row--mural.study-row--paired-three .study-plate:nth-child(6) { --col: 0; }

        /* Less frame at phone size, or the window gets unreadable. */
        .study-plate.study-plate--hero img {
          width: 82%;
        }

        .case-grid,
        .case-grid.case-grid--two {
          grid-template-columns: 1fr;
        }
      }

      @media (max-width: 520px) {
        .study-row--two,
        .study-row--three { --row-columns: 1; }
      }

      @media (prefers-reduced-motion: reduce) {
        .case-card-plate img,
        .case-card-plate video {
          transition: none;
        }

        .case-card:hover .case-card-plate img,
        .case-card:hover .case-card-plate video {
          transform: none;
        }

        /* Movement off, fades kept: the card still crossfades into its study,
           it just doesn't travel. */
        ::view-transition-group(*) {
          animation: none;
        }

        .case-card-plate,
        .tile-frame,
        a.study-plate,
        button.study-plate,
        a.study-plate img {
          transition: none;
        }

        .case-card:active .case-card-plate,
        .tile-link:active .tile-frame,
        a.study-plate:active,
        button.study-plate:active,
        a.study-plate:hover img {
          transform: none;
        }
      }

      /* Email links copy the address instead of opening a mail app (site.js).
         The two labels share one grid cell, so the link keeps the wider width
         and nothing shifts when "Copied to clipboard" fades in. The copied
         label is the same 30% grey as the hover. */
      .copy-swap {
        display: inline-grid;
      }

      .copy-swap > span {
        grid-area: 1 / 1;
        transition: opacity 160ms ease;
      }

      .identity-email.copy-swap > span {
        text-align: right;
      }

      .copy-swap .copy-done {
        opacity: 0;
        pointer-events: none;
      }

      .copy-swap.is-copied .copy-main {
        opacity: 0;
      }

      .copy-swap.is-copied .copy-done {
        opacity: 1;
      }

      a.copy-swap.is-copied,
      a.copy-swap.is-copied:hover,
      a.copy-swap.is-copied:focus-visible {
        color: rgb(17 17 17 / 0.3);
      }

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