/* WordPress-native, component-scoped content styles. */

/* embeds/Tweet.astro */

  .tweet {
    margin-block: 2.25em;
    margin-inline: calc(-1 * var(--rule-gap, 0px));
  }

  .tweet .frame {
    display: grid;
    justify-items: center;
    min-block-size: 4.5rem;
  }

  /* Both children share one cell, so the placeholder sits exactly where the
	   rendered post will and the swap costs no layout shift. */
  .tweet .frame > * {
    grid-area: 1 / 1;
    inline-size: 100%;
  }

  .tweet .frame .twitter-tweet {
    margin-block: 0 !important;
  }

  .tweet blockquote.twitter-tweet {
    /* Hidden until the widget claims it — the bare markup is a stray link
		   otherwise. Made visible again in the global block below. */
    visibility: hidden;
  }

  .tweet .card {
    display: flex;
    align-items: center;
    gap: 0.7rem;
    padding: 0.9rem 1rem;
    border: 1px solid var(--rule);
    background: var(--bg-sunken);
    font-family: var(--face-sans);
    font-size: var(--step--1);
    color: var(--fg-muted);
    text-decoration: none;
    transition:
      border-color var(--fast) var(--ease),
      background var(--fast) var(--ease);
  }

  .tweet .card:hover {
    border-color: var(--rule-strong);
    background: var(--bg-raised);
  }

  .tweet .mark svg {
    display: block;
    inline-size: 0.95rem;
    block-size: 0.95rem;
  }

  .tweet .who {
    font-family: var(--face-mono);
    color: var(--fg);
  }

  .tweet .go {
    margin-inline-start: auto;
    color: var(--fg-faint);
  }

  .tweet figcaption {
    font-family: var(--face-sans);
    font-size: var(--step--1);
    line-height: 1.5;
    color: var(--fg-faint);
    margin-block-start: 0.7rem;
    padding-inline: var(--rule-gap, 0px);
    text-align: center;
    text-wrap: pretty;
  }


  /* X swaps the blockquote for its own rendered node; at that point the
	   placeholder has done its job. These selectors have to reach markup this
	   component never wrote, so they cannot be scoped. */
  .tweet [data-tweet]:has(.twitter-tweet-rendered) [data-tweet-card],
  .tweet [data-tweet]:has(iframe) [data-tweet-card] {
    display: none;
  }

  .tweet [data-tweet] .twitter-tweet-rendered {
    visibility: visible;
  }


/* embeds/YouTube.astro */

  .yt {
    margin-block: 2.25em;
    margin-inline: calc(-1 * var(--rule-gap, 0px));
  }

  .yt .frame {
    position: relative;
    aspect-ratio: 16 / 9;
    overflow: hidden;
    border: 1px solid var(--rule);
    background-color: var(--bg-sunken);
    background-image: var(--poster);
    background-size: cover;
    background-position: center;
  }

  /* Sits over the poster so the still reads as a surface rather than a
	   photograph, and so the light-on-dark play glyph always has contrast. */
  .yt .frame::after {
    content: '';
    position: absolute;
    inset: 0;
    background: linear-gradient(to bottom, oklch(0% 0 0 / 0.1), oklch(0% 0 0 / 0.32));
    pointer-events: none;
    transition: background var(--mid) var(--ease);
  }

  .yt .frame:hover::after {
    background: linear-gradient(to bottom, oklch(0% 0 0 / 0.04), oklch(0% 0 0 / 0.22));
  }

  .yt .play {
    position: absolute;
    inset: 0;
    z-index: 1;
    display: grid;
    place-items: center;
    inline-size: 100%;
    block-size: 100%;
    padding: 0;
    border: 0;
    background: none;
    color: inherit;
    cursor: pointer;
  }

  .yt .glyph {
    display: grid;
    place-items: center;
    inline-size: 3.5rem;
    block-size: 3.5rem;
    border-radius: 50%;
    border: 1px solid oklch(100% 0 0 / 0.5);
    background: oklch(0% 0 0 / 0.3);
    color: oklch(100% 0 0);
    backdrop-filter: blur(6px);
    transition:
      transform var(--mid) var(--ease),
      background var(--mid) var(--ease),
      border-color var(--mid) var(--ease);
  }

  .yt .glyph svg {
    inline-size: 1.5rem;
    block-size: 1.5rem;
    /* Optically centres the triangle inside the circle. */
    margin-inline-start: 0.12rem;
  }

  .yt .play:hover .glyph {
    transform: scale(1.08);
    background: oklch(0% 0 0 / 0.5);
    border-color: oklch(100% 0 0 / 0.8);
  }

  .yt .play:active .glyph {
    transform: scale(0.98);
  }

  .yt .play:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: -3px;
  }

  /* A slow breath on the ring, so a still frame does not read as a dead
	   image. Stops the moment the pointer arrives — hover takes over. */
  @media (prefers-reduced-motion: no-preference) {
    .yt .glyph {
      animation: youtube-pulse 3.6s var(--ease) infinite;
    }

    .yt .play:hover .glyph {
      animation: none;
    }

    @keyframes youtube-pulse {
      0%,
      100% {
        box-shadow: 0 0 0 0 oklch(100% 0 0 / 0.28);
      }
      60% {
        box-shadow: 0 0 0 14px oklch(100% 0 0 / 0);
      }
    }
  }

  .yt .frame iframe {
    position: absolute;
    inset: 0;
    inline-size: 100%;
    block-size: 100%;
    border: 0;
  }

  .yt .fallback {
    position: absolute;
    inset-block-end: 0.75rem;
    inset-inline-start: 0.75rem;
    z-index: 2;
    color: oklch(100% 0 0);
    font-family: var(--face-sans);
    font-size: var(--step--1);
  }

  .yt figcaption {
    font-family: var(--face-sans);
    font-size: var(--step--1);
    line-height: 1.5;
    color: var(--fg-faint);
    margin-block-start: 0.7rem;
    padding-inline: var(--rule-gap, 0px);
    text-align: center;
    text-wrap: pretty;
  }


/* embeds/Gallery.astro */

  .gallery {
    margin-block: 2.25em;
    margin-inline: calc(-1 * var(--rule-gap, 0px));
  }

  .gallery .grid {
    display: grid;
    gap: 0.5rem;
    grid-template-columns: repeat(var(--cols, 2), 1fr);
  }

  .gallery[data-columns='3'] .grid {
    --cols: 3;
  }

  .gallery .cell {
    display: block;
    overflow: hidden;
    border: 1px solid var(--rule);
    background: var(--bg-sunken);
  }

  .gallery .cell img {
    display: block;
    inline-size: 100%;
    block-size: 100%;
    /* The border lives on the cell, not the picture. */
    border: 0;
    object-fit: cover;
    aspect-ratio: var(--ratio);
    transition: scale var(--slow) var(--ease);
  }

  @media (prefers-reduced-motion: no-preference) {
    .gallery .cell:hover img {
      scale: 1.03;
    }
  }

  @media (max-width: 34rem) {
    .gallery .grid {
      --cols: 1 !important;
    }
  }

  .gallery figcaption {
    font-family: var(--face-sans);
    font-size: var(--step--1);
    line-height: 1.5;
    color: var(--fg-faint);
    margin-block-start: 0.7rem;
    padding-inline: var(--rule-gap, 0px);
    text-align: center;
    text-wrap: pretty;
  }


/* figures/Figure.astro */

  .figure {
    /* Edge to edge between the column rules, matching the hero. */
    margin-block: 2.25em;
    margin-inline: calc(-1 * var(--rule-gap, 0px));
  }

  .figure .frame {
    padding: clamp(1.5rem, 5vw, 2rem);
    background: var(--bg-sunken);
  }

  .figure figcaption {
    font-family: var(--face-sans);
    font-size: var(--step--1);
    line-height: 1.5;
    color: var(--fg-faint);
    margin-block-start: 0.7rem;
    padding-inline: var(--rule-gap, 0px);
    text-align: center;
    text-wrap: pretty;
  }


/* figures/FocusDemo.astro */

  .focusdemo {
    display: grid;
    gap: 1rem;
    grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  }

  .focusdemo .pane {
    display: grid;
    gap: 0.65rem;
    justify-items: start;
  }

  .focusdemo .tag {
    font-family: var(--face-mono);
    font-size: var(--step--1);
    color: var(--fg-ghost);
  }

  .focusdemo .btn {
    font-family: var(--face-sans);
    font-size: var(--step--1);
    padding: 0.4rem 0.9rem;
    border-radius: var(--radius);
    border: 1px solid var(--rule-strong);
    background: var(--bg-raised);
    color: var(--fg);
  }

  @media (prefers-reduced-motion: no-preference) {
    .focusdemo .btn--ring {
      animation: focusdemo-ring 3s var(--ease) infinite;
    }

    @keyframes focusdemo-ring {
      0%,
      30% {
        box-shadow: 0 0 0 0 transparent;
      }
      45%,
      85% {
        box-shadow:
          0 0 0 2px var(--bg-sunken),
          0 0 0 4px var(--fg);
      }
      100% {
        box-shadow: 0 0 0 0 transparent;
      }
    }
  }

  @media (prefers-reduced-motion: reduce) {
    .focusdemo .btn--ring {
      box-shadow:
        0 0 0 2px var(--bg-sunken),
        0 0 0 4px var(--fg);
    }
  }


/* figures/WrapDemo.astro */

  /* wpautop may place an empty paragraph between the inline label and the
     authored sample; it is not part of the component's visual structure. */
  .wrapdemo .pane > p:empty {
    display: none;
  }

  .wrapdemo {
    display: grid;
    gap: 1rem;
    grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  }

  .wrapdemo .pane {
    display: grid;
    gap: 0.5rem;
    align-content: start;
  }

  .wrapdemo .tag {
    font-family: var(--face-mono);
    font-size: var(--step--1);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--fg-ghost);
  }

  .wrapdemo .sample {
    font-family: var(--face-sans);
    font-size: var(--step-0);
    font-weight: 500;
    line-height: 1.25;
    color: var(--fg);
    margin: 0;
  }

  .wrapdemo .sample--off {
    text-wrap: wrap;
  }

  .wrapdemo .sample--on {
    text-wrap: balance;
  }


/* embeds/BarChart.astro */

  .chart {
    margin-block: 2.25em;
    margin-inline: calc(-1 * var(--rule-gap, 0px));
    padding: clamp(1.25rem, 5vw, 1.75rem);
    border: 1px solid var(--rule);
    background: var(--bg-sunken);
    font-family: var(--face-sans);
  }

  .chart .title {
    font-size: var(--step--1);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--fg-faint);
    margin-block-end: 1.1rem;
  }

  .chart .plot {
    display: grid;
    gap: 0.55rem;
  }

  .chart .row {
    display: grid;
    grid-template-columns: minmax(6.5rem, auto) 1fr auto;
    align-items: center;
    gap: 0.75rem;
    font-size: var(--step--1);
  }

  .chart .label {
    color: var(--fg-muted);
    text-align: end;
    line-height: 1.3;
    text-wrap: balance;
  }

  .chart .track {
    block-size: 1.15rem;
    background: color-mix(in oklch, var(--fg) 5%, transparent);
  }

  .chart .bar {
    display: block;
    block-size: 100%;
    inline-size: var(--pct);
    background: color-mix(in oklch, var(--fg) 26%, transparent);
    transform-origin: 0 50%;
  }

  .chart .row_on .bar {
    background: var(--fg);
  }

  .chart .row_on .label,
  .chart .row_on .value {
    color: var(--fg);
  }

  .chart .value {
    font-family: var(--face-mono);
    font-variant-numeric: tabular-nums;
    color: var(--fg-faint);
    min-inline-size: 3ch;
    text-align: end;
  }

  .chart .note {
    margin-block-start: 1rem;
    font-size: var(--step--1);
    line-height: 1.5;
    color: var(--fg-faint);
    text-wrap: pretty;
  }

  /* Bars wipe in, staggered, the first time the figure is seen. Held at
	   full width otherwise, so the chart is never wrong — only unanimated. */
  @media (prefers-reduced-motion: no-preference) {
    .chart [data-chart] .bar {
      scale: 0 1;
    }

    .chart [data-chart][data-seen] .bar {
      animation: barchart-grow 900ms var(--ease-out-quart) forwards;
      animation-delay: calc(var(--i) * 110ms);
    }

    @keyframes barchart-grow {
      to {
        scale: 1 1;
      }
    }
  }

  @media (max-width: 34rem) {
    .chart .row {
      grid-template-columns: 1fr auto;
      gap: 0.15rem 0.6rem;
    }

    .chart .label {
      grid-column: 1;
      text-align: start;
    }

    .chart .value {
      grid-column: 2;
    }

    .chart .track {
      grid-column: 1 / -1;
      block-size: 0.7rem;
    }
  }


/* embeds/Shot.astro */

  .shot {
    margin-block: 2.25em;
    margin-inline: calc(-1 * var(--rule-gap, 0px));
  }

  .shot img {
    display: block;
    inline-size: 100%;
    block-size: auto;
    border: 1px solid var(--rule);
    background: var(--bg-sunken);
  }

  .shot--portrait img {
    inline-size: auto;
    max-block-size: 32rem;
    margin-inline: auto;
  }

  .shot figcaption {
    font-family: var(--face-sans);
    font-size: var(--step--1);
    line-height: 1.5;
    color: var(--fg-faint);
    margin-block-start: 0.7rem;
    padding-inline: var(--rule-gap, 0px);
    text-align: center;
    text-wrap: pretty;
  }


/* figures/CostCurves.astro */

  .curves > p:empty {
    display: none;
  }

  .curves {
    position: relative;
  }

  .curves svg {
    display: block;
    inline-size: 100%;
    block-size: auto;
  }

  .curves .axis {
    stroke: var(--rule);
    stroke-width: 1;
    stroke-dasharray: 4 4;
    vector-effect: non-scaling-stroke;
  }

  .curves .trace {
    fill: none;
    stroke-width: 1.5;
    stroke-linecap: round;
    vector-effect: non-scaling-stroke;
  }

  .curves .trace--software {
    stroke: var(--fg-faint);
  }

  .curves .trace--code {
    stroke: color-mix(in oklch, var(--fg) 24%, transparent);
  }

  .curves .key,
  .curves .span {
    position: absolute;
    margin: 0;
    font-family: var(--face-mono);
    font-size: 0.6rem;
    letter-spacing: 0.07em;
    text-transform: uppercase;
    color: var(--fg-ghost);
    white-space: nowrap;
  }

  .curves .key--software {
    inset-block-start: 4%;
    inset-inline-end: 0;
    color: var(--fg-faint);
  }

  .curves .key--code {
    inset-block-end: 14%;
    inset-inline-end: 0;
  }

  .curves .span {
    inset-block-end: -1.3rem;
    inset-inline-start: 0;
  }

  @media (prefers-reduced-motion: no-preference) {
    /* Both traces draw left to right at the same rate, so the gap opens in
		   front of you rather than being there on arrival. */
    .curves .trace {
      stroke-dasharray: 1;
      stroke-dashoffset: 1;
      animation: costcurves-draw 5.5s var(--ease-out-quart) infinite alternate;
    }

    .curves .trace--code {
      animation-delay: 220ms;
    }

    @keyframes costcurves-draw {
      from {
        stroke-dashoffset: 1;
      }
      to {
        stroke-dashoffset: 0;
      }
    }
  }


/* figures/AgentLanes.astro */

  .lanes .track > p:has(> .playhead) {
    display: contents;
  }

  .lanes {
    display: grid;
    gap: 1.15rem;
  }

  .lanes .group {
    display: grid;
    gap: 0.35rem;
  }

  .lanes .track {
    position: relative;
    display: grid;
    gap: 3px;
  }

  .lanes .lane {
    position: relative;
    block-size: 0.85rem;
    background: color-mix(in oklch, var(--fg) 5%, transparent);
  }

  .lanes .block {
    position: absolute;
    inset-block: 0;
    inset-inline-start: var(--x);
    inline-size: var(--w);
    background: color-mix(in oklch, var(--fg) 24%, transparent);
  }

  /* The one thing that sweeps, drawn over both stacks at the same rate so
	   the two spans are visibly the same span. */
  .lanes .playhead {
    position: absolute;
    inset-block: -0.2rem;
    inline-size: 1px;
    background: var(--fg-faint);
  }

  .lanes .head,
  .lanes .note {
    margin: 0;
    font-family: var(--face-mono);
    font-size: 0.6rem;
    letter-spacing: 0.07em;
    text-transform: uppercase;
    color: var(--fg-ghost);
  }

  .lanes .note {
    text-align: end;
  }

  .lanes .rule {
    border-block-start: 1px dashed var(--rule);
  }

  @media (prefers-reduced-motion: no-preference) {
    .lanes .playhead {
      animation: agentlanes-sweep 7s linear infinite;
    }

    @keyframes agentlanes-sweep {
      from {
        inset-inline-start: 0;
      }
      to {
        inset-inline-start: 100%;
      }
    }
  }

  @media (prefers-reduced-motion: reduce) {
    /* Without the sweep the playhead is a stray tick, so it goes away and
		   the blocks carry the comparison on their own. */
    .lanes .playhead {
      display: none;
    }
  }


/* figures/Middleman.astro */

  .chain {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 0.3rem;
  }

  .chain .step {
    display: grid;
    justify-items: center;
    gap: 0.4rem;
    flex: 0 0 auto;
  }

  .chain .dot {
    inline-size: 12px;
    block-size: 12px;
    border-radius: 50%;
    background: color-mix(in oklch, var(--fg) 26%, transparent);
  }

  .chain .step--dead .dot {
    background: transparent;
    border: 1px dashed var(--rule-strong);
  }

  .chain .name {
    font-family: var(--face-mono);
    font-size: 0.6rem;
    letter-spacing: 0.04em;
    color: var(--fg-faint);
  }

  .chain .step--dead .name {
    color: var(--fg-ghost);
  }

  .chain .arrow {
    flex: 1;
    block-size: 1px;
    margin-block-start: 5.5px;
    background: repeating-linear-gradient(
      to right,
      var(--rule-strong) 0 4px,
      transparent 4px 8px
    );
  }

  @media (prefers-reduced-motion: no-preference) {
    /* A pulse runs the length of the chain and straight past the article,
		   which never lights up — that is the whole point. */
    .chain .dot {
      animation: middleman-hop 6s var(--ease) infinite;
      animation-delay: calc(var(--i) * 700ms);
    }

    @keyframes middleman-hop {
      0%,
      8% {
        background: color-mix(in oklch, var(--fg) 26%, transparent);
      }
      14% {
        background: var(--fg);
        scale: 1.25;
      }
      26%,
      100% {
        background: color-mix(in oklch, var(--fg) 26%, transparent);
        scale: 1;
      }
    }

    /* Skipped deliberately: the stored article is passed over, not read. */
    .chain .step--dead .dot {
      animation: none;
    }
  }


/* figures/ThreadCompare.astro */

  .compare {
    display: grid;
    grid-template-columns: 1fr 1.25fr;
    gap: clamp(1rem, 5vw, 2rem);
    align-items: start;
  }

  .compare .col {
    display: grid;
    gap: 0.7rem;
  }

  .compare .cap {
    font-family: var(--face-mono);
    font-size: 0.62rem;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--fg-ghost);
  }

  .compare .linear {
    display: grid;
    gap: 0.4rem;
  }

  .compare .msg {
    block-size: 0.85rem;
    border-radius: 3px;
  }

  .compare .msg--you {
    inline-size: 46%;
    justify-self: end;
    background: color-mix(in oklch, var(--fg) 22%, transparent);
  }

  .compare .msg--ai {
    inline-size: 88%;
    background: color-mix(in oklch, var(--fg) 10%, transparent);
  }

  .compare .tree {
    inline-size: 100%;
    block-size: auto;
  }

  .compare .edge {
    stroke: var(--rule-strong);
    stroke-width: 1;
  }

  .compare .node {
    fill: color-mix(in oklch, var(--fg) 12%, transparent);
  }

  .compare .node--root {
    fill: var(--fg-faint);
  }

  .compare .node--leaf {
    fill: color-mix(in oklch, var(--fg) 22%, transparent);
  }

  @media (prefers-reduced-motion: no-preference) {
    /* The left column scrolls on forever; the right one grows outward.
		   Both loops are the same length, so the contrast keeps landing. */
    .compare .msg {
      opacity: 0;
      animation: threadcompare-appear 9s var(--ease) infinite;
      animation-delay: calc(var(--i) * 420ms);
    }

    @keyframes threadcompare-appear {
      0% {
        opacity: 0;
        transform: translateY(4px);
      }
      8%,
      88% {
        opacity: 1;
        transform: none;
      }
      100% {
        opacity: 0;
      }
    }

    .compare .edge {
      stroke-dasharray: 90;
      stroke-dashoffset: 90;
      animation: threadcompare-draw 9s var(--ease) infinite;
      animation-delay: calc(var(--i) * 420ms + 300ms);
    }

    @keyframes threadcompare-draw {
      0% {
        stroke-dashoffset: 90;
      }
      10%,
      88% {
        stroke-dashoffset: 0;
      }
      100% {
        stroke-dashoffset: 90;
      }
    }

    .compare .node:not(.node--root) {
      opacity: 0;
      animation: threadcompare-appear 9s var(--ease) infinite;
      animation-delay: calc(var(--i) * 420ms + 500ms);
    }
  }


/* figures/Swatches.astro */

  .swatches {
    display: flex;
    gap: clamp(0.35rem, 2vw, 0.7rem);
    justify-content: center;
    align-items: center;
    block-size: 8rem;
  }

  .swatches .swatch {
    flex: 1;
    max-inline-size: 2.2rem;
    block-size: 100%;
    border-radius: var(--radius-pill);
    background: var(--c);
  }

  @media (prefers-reduced-motion: no-preference) {
    .swatches .swatch {
      animation: swatches-breathe 3.2s var(--ease) infinite alternate;
      animation-delay: calc(var(--i) * 110ms);
    }

    @keyframes swatches-breathe {
      from {
        block-size: 62%;
      }
      to {
        block-size: 100%;
      }
    }
  }


/* figures/Terminal.astro */

  .term {
    overflow: hidden;
    border: 1px solid var(--rule);
    background: var(--bg);
  }

  .term .bar {
    display: flex;
    gap: 5px;
    padding: 0.5rem 0.7rem;
    border-block-end: 1px solid var(--rule);
    background: var(--bg-sunken);
  }

  .term .bar span {
    inline-size: 7px;
    block-size: 7px;
    border-radius: 50%;
    background: var(--fg-ghost);
  }

  .term .body {
    display: grid;
    gap: 0.3rem;
    padding: 0.85rem 0.9rem;
    font-family: var(--face-mono);
    font-size: var(--step--1);
    line-height: 1.5;
    color: var(--fg-muted);
  }

  .term .line {
    white-space: pre-wrap;
    word-break: break-word;
  }

  .term .line:first-child,
  .term .line:nth-child(4) {
    color: var(--fg);
  }

  @media (prefers-reduced-motion: no-preference) {
    .term .line {
      opacity: 0;
      animation: terminal-type-in calc(var(--n) * 1.4s + 2.5s) steps(1) infinite;
      animation-delay: calc(var(--i) * 700ms);
    }

    @keyframes terminal-type-in {
      0% {
        opacity: 0;
      }
      4%,
      90% {
        opacity: 1;
      }
      100% {
        opacity: 0;
      }
    }
  }


/* embeds/VideoEmbed.astro */

  .video {
    margin-block: 2.25em;
    margin-inline: calc(-1 * var(--rule-gap, 0px));
  }

  .video video {
    display: block;
    inline-size: 100%;
    block-size: auto;
    border: 1px solid var(--rule);
    background: var(--bg-sunken);
  }

  .video figcaption {
    font-family: var(--face-sans);
    font-size: var(--step--1);
    line-height: 1.5;
    color: var(--fg-faint);
    margin-block-start: 0.7rem;
    padding-inline: var(--rule-gap, 0px);
    text-align: center;
    text-wrap: pretty;
  }


/* EventGraphic.astro */

  .event-graphic {
    display: grid;
    place-items: center;
    border-radius: var(--radius);
    background: var(--bg-sunken);
    overflow: hidden;
  }

  .event-graphic--row {
    aspect-ratio: 3 / 2;
  }

  /* Opening the page: a band rather than a thumbnail, sitting on the text
	   column like the writing under it. The drawing keeps its own proportions
	   and centres inside it. */
  .event-graphic--page {
    aspect-ratio: 5 / 2;
  }

  .event-graphic svg {
    inline-size: 100%;
    block-size: 100%;
  }

  /* The loop */

  .event-graphic .ring {
    stroke: var(--rule-strong);
    stroke-width: 1.25;
    stroke-dasharray: 3 4;
    stroke-linecap: round;
  }

  .event-graphic .node {
    fill: var(--bg-raised);
    stroke: var(--fg-faint);
    stroke-width: 1.25;
    transform-box: fill-box;
    transform-origin: center;
  }

  /* The stage */

  .event-graphic .screen {
    fill: var(--bg-raised);
    stroke: var(--rule-strong);
    stroke-width: 1.25;
  }

  .event-graphic .slide-line {
    fill: var(--rule-strong);
    transform-origin: left;
  }

  .event-graphic .slide-line--accent {
    fill: var(--fg-faint);
  }

  /*
	 * The drawing runs once when it is pointed at: the loop fires node by node
	 * the way it would be walked, and the slide draws itself in. In the list
	 * that is the row being hovered; at the head of a page it is the drawing
	 * itself, which also plays once as the page arrives — `is-playing` is put
	 * on and taken off by the script below. Held still under reduced motion:
	 * the drawings read perfectly well without any of it.
	 */
  @media (prefers-reduced-motion: no-preference) {
    .event-graphic--page.is-playing .node,
    .picture-row:hover .event-graphic .node,
    .picture-row:focus-visible .event-graphic .node {
      animation: event-event-node-pulse 640ms var(--ease-out-quart) both;
      animation-delay: calc(var(--i, 0) * 120ms);
    }

    .event-graphic--page.is-playing .ring,
    .picture-row:hover .event-graphic .ring,
    .picture-row:focus-visible .event-graphic .ring {
      animation: event-event-ring-turn 1100ms var(--ease-out-quart) both;
    }

    .event-graphic--page.is-playing .slide-line,
    .picture-row:hover .event-graphic .slide-line,
    .picture-row:focus-visible .event-graphic .slide-line {
      animation: event-event-line-draw 560ms var(--ease-out-quart) both;
      animation-delay: calc(var(--i, 0) * 70ms);
    }
  }

  @keyframes event-event-node-pulse {
    50% {
      scale: 1.32;
      fill: color-mix(in oklch, var(--fg-faint) 85%, transparent);
    }
  }

  @keyframes event-event-ring-turn {
    from {
      stroke-dashoffset: 14;
    }
    to {
      stroke-dashoffset: 0;
    }
  }

  @keyframes event-event-line-draw {
    from {
      scale: 0.8 1;
    }
    to {
      scale: 1 1;
    }
  }


/* SiteGraphic.astro */

  /* The box is EventGraphic's row variant, to the pixel: the two sit in the
	   same list and any drift between them would show. */
  .site-graphic {
    display: grid;
    place-items: center;
    aspect-ratio: 3 / 2;
    border-radius: var(--radius);
    background: var(--bg-sunken);
    overflow: hidden;
  }

  .site-graphic svg {
    inline-size: 100%;
    block-size: 100%;
  }

  .site-graphic .sheet {
    fill: var(--bg-raised);
    stroke: var(--rule-strong);
    stroke-width: 1.25;
  }

  /* The one behind is paper, not content: no fill of its own to fight the
	   front sheet, and a lighter line. */
  .site-graphic .sheet--back {
    fill: none;
    stroke: var(--rule);
  }

  .site-graphic .note-line {
    fill: var(--rule-strong);
  }

  .site-graphic .note-line--accent {
    fill: var(--fg-faint);
  }

  /*
	 * Pointed at, the stack fans and the writing inks in, line by line down the
	 * page. Both halves are transitions, so the drawing eases back to rest when
	 * the pointer leaves instead of snapping, and catches whatever position it
	 * is in when the pointer comes and goes quickly.
	 *
	 * The two sheets pivot near their own bottom corners rather than their
	 * centres — paper on a desk turns about the corner it rests on, and a
	 * centre pivot reads as a shape being rotated instead.
	 *
	 * Held still under reduced motion: the whole block goes, transitions and
	 * transforms together, so nothing is left half-stated.
	 */
  @media (prefers-reduced-motion: no-preference) {
    .site-graphic .sheet--back,
    .site-graphic .page {
      transform-box: fill-box;
      transition:
        translate var(--mid) var(--ease-out-quart),
        rotate var(--mid) var(--ease-out-quart);
    }

    .site-graphic .sheet--back {
      transform-origin: 15% 85%;
    }

    .site-graphic .page {
      transform-origin: 85% 85%;
    }

    .site-graphic .note-line {
      transition: fill var(--mid) var(--ease);
    }

    .picture-row:hover .site-graphic .sheet--back,
    .picture-row:focus-visible .site-graphic .sheet--back {
      translate: 6px -5px;
      rotate: 5deg;
    }

    .picture-row:hover .site-graphic .page,
    .picture-row:focus-visible .site-graphic .page {
      translate: -2px 2px;
      rotate: -1.5deg;
    }

    /* Down the page a line at a time on the way in. Leaving, the delay goes
		   and the three settle together — a staggered exit reads as the drawing
		   being slow to let go. */
    .picture-row:hover .site-graphic .note-line,
    .picture-row:focus-visible .site-graphic .note-line {
      fill: var(--fg-faint);
      transition-delay: calc(var(--i, 0) * 70ms);
    }

    .picture-row:hover .site-graphic .note-line--accent,
    .picture-row:focus-visible .site-graphic .note-line--accent {
      fill: var(--fg-muted);
    }
  }


/* Resources.astro */

  /*
	 * The list steps outside the measure: the rules run wider than the text on
	 * both sides, and the number and the host hang off into those margins, so
	 * the label alone starts on the same line as the body copy above it.
	 *
	 *   |<- rule ----------------------------------------------- rule ->|
	 *        01   Install Claude Code              code.claude.com  ↗
	 *             ^ the measure's left edge           its right edge ^
	 */
  .resources {
    /* The gap between columns, needed twice: the number's column is the
		   hang less the gap, so that hang is the whole distance from the text
		   to the number rather than only part of it. */
    --resource-gap: 0.9rem;
    /* How far the number and the host sit outside the measure. */
    --resource-hang: 2.4rem;
    /* How far the rules reach past the text — the page's own wide edge,
		   which the photo slider lines its first frame up with. */
    --resource-bleed: var(--page-bleed, 0px);

    list-style: none;
    padding: 0;
    margin-block: 1.8em;
    margin-inline: calc(-1 * var(--resource-bleed));
    font-family: var(--face-sans);
    font-size: var(--step-0);
    border-block: 1px solid var(--rule);
  }

  /* `.prose li + li` puts 0.35em above every list item after the first. Here
	   that space landed under the previous row's text but above this row's
	   rule, leaving the first row of a multi-row list bottom-heavy while the
	   last one — which ends on the list's own border — looked right. The rows
	   space themselves with padding; the margin has to go. */
  .resources li + li {
    margin-block-start: 0;
    border-block-start: 1px solid var(--rule);
  }

  .resources .resource {
    display: grid;
    grid-template-columns: calc(var(--resource-hang) - var(--resource-gap)) 1fr auto auto;
    align-items: center;
    column-gap: var(--resource-gap);
    padding-block: 0.8rem;
    /* Puts the label column back on the measure and leaves the number and
		   the host outside it. Clamped at zero for the narrow windows where
		   the bleed has closed up and there is nothing left to hang into. */
    padding-inline: max(0px, calc(var(--resource-bleed) - var(--resource-hang)));
    color: var(--fg-muted);
    /* This is a row, not a sentence: no underline, here or on hover. */
    text-decoration: none;
    transition: color var(--fast) var(--ease);
  }

  .resources .resource__index,
  .resources .resource__host {
    font-family: var(--face-mono);
    font-size: var(--step--1);
    letter-spacing: 0.02em;
  }

  .resources .resource__index {
    font-variant-numeric: tabular-nums;
    color: var(--fg-faint);
    transition:
      color var(--fast) var(--ease),
      translate var(--mid) var(--ease-out-quart);
  }

  /* The faintest of the four: it names where the link goes, which the label
	   has usually said already. */
  .resources .resource__host {
    text-align: end;
    color: var(--fg-ghost);
  }

  .resources .resource__label {
    color: var(--fg);
    transition: translate var(--mid) var(--ease-out-quart);
  }

  .resources .resource__arrow {
    width: 0.9em;
    height: 0.9em;
    color: var(--fg-ghost);
    transition:
      color var(--fast) var(--ease),
      translate var(--mid) var(--ease-out-quart);
  }

  /* Hovering steps the row forward: the number and the label slide a little
	   to the right and the arrow leaves up and out, the way it points. */
  .resources .resource:hover,
  .resources .resource:focus-visible {
    color: var(--fg);
    text-decoration: none;
  }

  .resources .resource:hover .resource__index,
  .resources .resource:focus-visible .resource__index {
    color: var(--fg-muted);
  }

  .resources .resource:hover .resource__index,
  .resources .resource:focus-visible .resource__index,
  .resources .resource:hover .resource__label,
  .resources .resource:focus-visible .resource__label {
    translate: 4px;
  }

  .resources .resource:hover .resource__arrow,
  .resources .resource:focus-visible .resource__arrow {
    color: var(--fg-muted);
    translate: 2px -2px;
  }

  /* The host is the least of the four; drop it before anything wraps. */
  @media (max-width: 32rem) {
    .resources .resource {
      grid-template-columns: calc(var(--resource-hang) - var(--resource-gap)) 1fr auto;
    }

    .resources .resource__host {
      display: none;
    }
  }


/* figures/EventPhoto.astro */

  .photos {
    /* The same corner the drawing at the top of the page carries, so the
		   two read as one page rather than two treatments. */
    --photo-radius: var(--radius);
    --photo-gap: 0.75rem;
    /* The text column, whatever the window has left it at. */
    --photo-column: min(var(--shell), calc(100vw - 2 * var(--gutter)));
  }

  .photos .photos__item {
    display: block;
    inline-size: 100%;
    block-size: auto;
    border-radius: var(--photo-radius);
    /* .prose puts a hairline on every image; the pictures here carry the
		   rounded corner instead. */
    border: 0;
  }

  .photos .photos__track {
    display: flex;
    gap: var(--photo-gap);
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    /* The buttons are the control; the bar underneath is noise. */
    scrollbar-width: none;
  }

  /*
	 * The row itself runs the whole width of the window while the first frame
	 * still begins on the column, so the pictures carry on past the text
	 * rather than sitting in a box the width of it.
	 *
	 *   |            |<- text ->|                                    |
	 *   |<- track ---[ frame ][ frame ][ frame ---------------------->|
	 */
  .photos--wide .photos__track {
    --photo-room: var(--page-room, max(var(--gutter), calc((100vw - var(--shell)) / 2)));
    margin-inline: calc(-1 * var(--photo-room));
    /*
		 * The row opens with its first frame out on the page's wide edge,
		 * level with the rules of a resource list rather than with the text.
		 * Everything after it works to the column instead — that is what the
		 * scroll padding below is for — and the room at the end buys the last
		 * frame enough travel to reach the column too.
		 */
    padding-inline: calc(var(--photo-room) - var(--page-bleed, 0px)) var(--photo-room);
    scroll-padding-inline-start: var(--photo-room);
  }

  .photos .photos__track::-webkit-scrollbar {
    display: none;
  }

  /* Both shapes stand a frame the same height: three quarters of the column,
	   so a picture in the usual landscape shape comes out exactly as wide as
	   the text. */
  .photos--slider .photos__item {
    block-size: calc(var(--photo-column) * 3 / 4);
    scroll-snap-align: start;
  }

  /* `wide`: shoulder to shoulder, each keeping the width its own picture
	   wants, the row carrying on past the right of the page. */
  .photos--wide .photos__item {
    flex: 0 0 auto;
    inline-size: auto;
  }

  /* `inset`: one frame at a time, the width of the column, sliding within
	   it. A picture that is not the usual shape is cropped to fit rather than
	   changing how wide the slider is. */
  .photos--inset .photos__item {
    flex: 0 0 100%;
    inline-size: 100%;
    object-fit: cover;
  }

  .photos .photos__nav {
    display: flex;
    gap: 0.4rem;
    margin-block-start: 0.75rem;
  }

  .photos .photos__button {
    display: grid;
    place-items: center;
    /* Wider than tall: a small control that reads as a step sideways
		   rather than a button box. */
    inline-size: 1.6rem;
    block-size: 1.2rem;
    padding: 0;
    border: 1px solid color-mix(in oklch, var(--rule) 65%, transparent);
    border-radius: 4px;
    background: none;
    color: var(--fg-muted);
    cursor: pointer;
    transition:
      color var(--fast) var(--ease),
      border-color var(--fast) var(--ease);
  }

  .photos .photos__button svg {
    inline-size: 0.7rem;
    block-size: 0.7rem;
  }

  .photos .photos__button:hover:not(:disabled),
  .photos .photos__button:focus-visible:not(:disabled) {
    color: var(--fg);
    border-color: var(--rule-strong);
  }

  .photos .photos__button:disabled {
    color: var(--fg-ghost);
    cursor: default;
  }

  @media (prefers-reduced-motion: no-preference) {
    .photos .photos__track {
      scroll-behavior: smooth;
    }
  }
