/* spidleweb.net: the case-study and Writing pages (Phase 2C)
   The /writing/ index, the endings of a case-study page (More from and the
   next-story band), and the post page. /work/ redirects to the home page,
   which took over the Work index's case-study facts. The case-study layout
   and the tokens stay in style.css, the site chrome is its "Site chrome"
   block, and the stream cards inside More from take their styles from
   stream.css. Values come from rows 11 to 14 of "current · redesign" in
   Paper, read through its JSX. */

:root {
  /* --site-accent, the site's red under its own name, is in style.css. */
  /* Grey bars that stand in for text still to be written. */
  --placeholder-fill: color-mix(in srgb, var(--ink) 8%, transparent);
}

/* Paper places each page's first line a fixed distance below the top of
   the window: 124px on a case study, 132px on an index, and 163px on a post.
   The desktop masthead sizes to its content at 76px, so each page's own top
   padding is that distance less 76px. If the masthead's height changes,
   change these three with it. */

/* ---- Case-study pages ---------------------------------------------------- */

/* Inside a case study, the chrome keeps the site's red for its markers. On
   the project's ink a marker in the project colour would disappear. */
.case-study :is(.masthead, .to-top) { --accent: var(--site-accent); }

/* A figure named in a link, such as /work/conservis-maps#fig-conservis-02, lands
   with a little room above it. */
.case-proofs [id^="fig-"] { scroll-margin-top: var(--space-24); }

/* The facts carry visible labels, the same ones the home page's panel shows,
   plus Industry and Team, which the generator writes from the stream and
   content/details.json. A label sits in a narrow mono column beside its value. */
.case-context__facts--labelled { display: grid; gap: 0; padding-top: 0; }
.case-context__facts--labelled > div {
  display: grid;
  grid-template-columns: 72px minmax(0, 1fr);
  gap: var(--space-12);
  align-items: baseline;
  padding: var(--space-10) 0;
  border-bottom: 1px solid var(--line);
  border-image: linear-gradient(to top, var(--line) var(--hairline), transparent 0) 1;
}
.case-context__facts--labelled dt {
  font-family: var(--mono);
  font-size: var(--text-micro);
  letter-spacing: 0.06em;
  line-height: var(--text-micro-line);
  text-transform: uppercase;
  color: color-mix(in oklch, var(--ink) 70%, var(--paper));
}
/* Values set as the panel sets them, in the mono label, so the same facts
   read the same on the home page and here. A scope item never breaks, and
   carries its separator, so no line starts with a dot. */
.case-context__facts--labelled dd {
  font-family: var(--mono);
  font-size: var(--text-micro);
  letter-spacing: 0.06em;
  line-height: var(--text-micro-line);
  text-transform: uppercase;
  text-wrap: pretty;
}
.case-context__facts--labelled dd > span { white-space: nowrap; }
/* A lone credit reaches past its row's 10px padding, so a 14px name takes
   a 38px target. A row of two or more names sets them on 24px lines, so
   each name, the middle one of three too, is a 24px target of its own and
   its focus ring clears the next (WCAG 2.5.8). */
.case-context__facts--labelled dd > .credit-link { position: relative; display: inline-block; }
.case-context__facts--labelled dd > .credit-link::before {
  content: "";
  position: absolute;
  inset: 0;
}
.case-context__facts--labelled dd:has(> .credit-link ~ .credit-link) { line-height: 24px; }
.case-context__facts--labelled dd > .credit-link:only-of-type::before { top: -12px; bottom: -12px; }
.case-context__facts--labelled + .case-context__note { margin-top: var(--space-16); border-top: 0; padding-top: 0; }

/* Grouped media: a row of phones and their clips. */
.proof-phone.proof--video .proof__frame { height: auto; overflow: visible; background: var(--fixed-ink); }

.proof__frame--video { position: relative; background: var(--fixed-ink); }
.proof__video { display: block; width: 100%; height: 100%; object-fit: contain; }

/* A prototype waits behind its poster until asked, then runs in place. */
.proof__demo { position: relative; aspect-ratio: 16 / 10; overflow: hidden; background: var(--frame-bg); }
.proof__demo img { width: 100%; height: 100%; object-fit: cover; object-position: top; }
.proof__demo iframe { display: block; width: 100%; height: 100%; border: 0; background: var(--frame-screen); }
.proof__try {
  position: absolute;
  left: 16px;
  bottom: 16px;
  display: inline-flex;
  align-items: center;
  gap: var(--space-8);
  min-height: 44px;
  /* 2px less on the icon's side, which reads as more space than text. */
  padding: 0 var(--space-16) 0 var(--space-14);
  border: 0;
  background: var(--fixed-ink);
  color: var(--fixed-paper);
  cursor: pointer;
  transition: background-color 120ms var(--ease-fade), scale 150ms var(--ease-out);
}
/* The pointing hand is paper at rest and Archive Red on hover or keyboard
   focus, as on the home page's Try it badge (Jason, 2026-10-02). */
.proof__try .icon { color: var(--fixed-paper); transition: background-color 120ms var(--ease-fade); }
.proof__try:active { scale: 0.96; }
/* The ring the video buttons take: paper on an ink halo, so it shows on a
   light screenshot or a dark one. */
.proof__try:focus-visible {
  outline: 2px solid var(--fixed-paper);
  outline-offset: 2px;
  box-shadow: 0 0 0 4px var(--fixed-ink);
}
.proof__try:focus-visible .icon { background-color: var(--site-accent); }
@media (hover: hover) and (pointer: fine) {
  .proof__try:hover { background: var(--case-dark); }
  .proof__try:hover .icon { background-color: var(--site-accent); }
}
@media (prefers-reduced-motion: reduce) {
  .proof__try, .proof__try .icon { transition: none; }
}
/* The link is one 26px line of caption, so an ::before gives it a 44px target.
   It takes its own line under the caption: inline, the ↗ sliding in on hover
   could push it past the line's end, wrap it away from the pointer, and
   flicker (Jason, 2026-11-01). */
.proof__open { position: relative; display: block; width: fit-content; white-space: nowrap; }
.proof__open::before { content: ""; position: absolute; inset: -9px 0; }
/* A web prototype does not run on a phone, as on the home page; the link still opens it. */
@media (max-width: 799px) {
  .proof--demo:not([data-platform="mobile"]) .proof__try { display: none; }
}

/* Every still in a case study opens larger in the lightbox (site.js). */
.js .case-proofs .proof__frame:not(.proof__frame--video, .proof__demo) img { cursor: zoom-in; }
.case-proofs img[role="button"]:focus-visible { outline: 2px solid var(--case-accent); outline-offset: 2px; }
/* So does every figure in a post, which a phone shows too small to read. */
.js .post__body figure img { cursor: zoom-in; }
.post__body figure img[role="button"]:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }

/* The next-story band: forward only, in the next case study's colours,
   which its theme class sets. The slope sits on a pseudo-element, so the
   focus ring is never clipped. */
.next-story {
  position: relative;
  isolation: isolate;
  padding: var(--space-112) var(--gutter) var(--space-64);
  color: var(--case-paper);
}
.next-story::before {
  content: "";
  position: absolute;
  /* One pixel past the bottom, under the footer, so a fractional edge
     never shows a hairline of paper between the two. */
  inset: 0 0 -1px;
  z-index: -1;
  background: var(--case-dark);
  clip-path: polygon(0 48px, 100% 0, 100% 100%, 0 100%);
}
.next-story__link {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-24);
  min-height: 44px;
  padding: var(--space-12) 0;
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(2.25rem, 4.72vw, 4.25rem);
  line-height: 1.176;
  letter-spacing: -0.025em;
  text-transform: uppercase;
  text-decoration: none;
}
.next-story__name { min-width: 0; text-wrap: balance; overflow-wrap: anywhere; }
/* The arrow rests a little short of the band's edge and travels the rest of
   the way while the band is hovered or its link has focus, so the band says
   which way it goes (Jason, 2026-10-11). Reduced motion keeps the two places
   and drops the glide. */
.next-story__arrow {
  flex-shrink: 0;
  translate: -0.55em 0;
  transition: translate 180ms var(--ease-out);
}
.next-story__link:focus-visible .next-story__arrow { translate: 0 0; }
@media (hover: hover) and (pointer: fine) {
  .next-story__link:hover .next-story__arrow { translate: 0 0; }
}
@media (prefers-reduced-motion: reduce) {
  .next-story__arrow { transition: none; }
}
/* The whole band is the link: the link's box stretches over the section,
   and hovering anywhere on it deepens the ground instead of tinting the
   words (Jason, 2026-10-02). */
.next-story__link::after {
  content: "";
  position: absolute;
  inset: 0;
}
.next-story::before { transition: background-color 160ms var(--ease-fade); }
@media (hover: hover) and (pointer: fine) {
  .next-story:has(.next-story__link:hover)::before {
    background: color-mix(in oklch, var(--case-dark) 78%, black);
  }
}

/* ---- The /writing/ index ---------------------------------------------------- */

/* Short pages keep the footer line at the bottom of the window. */
body.page { display: flex; flex-direction: column; min-height: 100vh; min-height: 100dvh; }
body.page > main { flex: 1 0 auto; }

.page-main { padding: 0 var(--gutter) var(--page-end); }

.page-head {
  padding-top: var(--page-top);
  padding-bottom: var(--space-28);
}
.page-head__title {
  font-family: var(--display);
  font-weight: 400;
  font-size: 42px;
  line-height: 44px;
  letter-spacing: -0.02em;
  text-transform: uppercase;
}

/* The 404 page's message under its head, set like an entry's dek. */
.page-note {
  max-width: 520px;
  margin-top: var(--space-32);
  font-family: var(--serif);
  font-weight: 400;
  font-size: 24px;
  line-height: 30px;
  font-synthesis: none;
  text-wrap: pretty;
}
.page-head + .page-note { margin-top: var(--space-4); }
.page-note + .page-note { margin-top: var(--space-16); }
.page-note a { color: inherit; }
.page-note a:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }

/* Posts as issues, from Paper's "Writing index · issues" (Covers): one card
   per post in its own colour, its cover framed in a lighter tint of it, the
   title, the dek, a tag and the call to action. The cards share one row, as
   the home grid's five columns do, so they shrink as issues are added and
   their type and spacing scale with each card's width; a sixth issue starts
   a second row. Corners stay sharp. No kicker or date, by Jason's rulings. */
.page-head:has(+ .entry-list) { padding-bottom: var(--space-40); }

.entry-list {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  gap: var(--space-48) var(--space-20);
  list-style: none;
}
.entry-list[data-placeholders="hide"] > .entry[data-placeholder] { display: none; }

/* Each issue's colour comes from its cover. A placeholder takes ink. */
.entry {
  --issue: var(--fixed-ink);
  --issue-edge: oklch(0.359 0.003 106.6);
  --issue-dek: var(--on-ink);
  position: relative;
  container-type: inline-size;
}
.issue--sheets-to-layers { --issue: oklch(0.469 0.104 153.5); --issue-edge: oklch(0.63 0.082 154.8); --issue-dek: oklch(0.92 0.017 151.1); }
.issue--description-generator { --issue: oklch(0.436 0.159 276.7); --issue-edge: oklch(0.623 0.111 281.5); --issue-dek: oklch(0.903 0.029 283.6); }
/* Clay, the cover's own ground. */
.issue--replacing-a-cms { --issue: oklch(0.47 0.12 40); --issue-edge: oklch(0.64 0.1 42); --issue-dek: oklch(0.92 0.025 45); }
/* Teal, the brokerage screen's header in the cover. */
.issue--recreating-reframe { --issue: oklch(0.42 0.06 228); --issue-edge: oklch(0.6 0.06 226); --issue-dek: oklch(0.92 0.015 222); }

/* Sizes are Paper's at two across (670px) and five across (256px), as a
   share of the card's width between those. */
.entry__link {
  display: flex;
  flex-direction: column;
  gap: clamp(24px, 9.5cqi, 64px);
  padding: clamp(8px, 2.1cqi, 14px);
  padding-bottom: clamp(16px, 4.2cqi, 28px);
  background: var(--issue);
  color: var(--fixed-paper);
  text-decoration: none;
  transition: background-color 160ms var(--ease-fade);
  /* Cards in a row end together, whatever the length of each dek. */
  height: 100%;
}
.entry__link:focus-visible { outline: 2px solid var(--site-ink); outline-offset: 2px; }

.entry__cover {
  aspect-ratio: 16 / 9;
  overflow: hidden;
  border: 3px solid var(--issue-edge);
  background: var(--frame-screen);
}
.entry__cover img { display: block; width: 100%; height: 100%; object-fit: cover; }
.entry__cover--empty { display: grid; place-items: center; color: var(--issue-dek); background: none; }

.entry__body {
  display: flex;
  flex-direction: column;
  gap: clamp(12px, 3cqi, 20px);
  flex: 1 0 auto;
  padding-inline: clamp(8px, 2.7cqi, 18px);
}
/* 9.5% of the card keeps the longest word, DESCRIPTION, inside it. */
.entry__title {
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(1.375rem, 9.5cqi, 4rem);
  line-height: 0.93;
  letter-spacing: -0.035em;
  text-transform: uppercase;
  overflow-wrap: anywhere;
}
.entry__dek {
  font-family: var(--serif);
  font-weight: 400;
  /* At a 16px root: 17px to 24px, with a 12.7px fixed term. */
  font-size: clamp(1.0625rem, calc(0.79375rem + 1.69cqi), 1.5rem);
  line-height: 1.3;
  color: var(--issue-dek);
  font-synthesis: none;
  text-wrap: pretty;
}
/* On a narrow card the call to action wraps under the tag. */
.entry__foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-10) var(--space-12);
  margin-top: auto;
  padding-top: clamp(4px, 1.2cqi, 12px);
}
/* A label, not a control: no box, since only what can be pressed carries one. */
.entry__tag { color: var(--issue-dek); white-space: nowrap; }
.entry__cta { margin-left: auto; white-space: nowrap; }
/* Hovering anywhere on a card deepens its ground, as the next-story band
   does, and underlines the call to action. */
@media (hover: hover) and (pointer: fine) {
  a.entry__link:hover { background-color: color-mix(in oklch, var(--issue) 78%, black); }
  .entry__link:hover .entry__cta { text-decoration: underline; text-underline-offset: 4px; }
}
@media (prefers-reduced-motion: reduce) {
  .entry__link { transition: none; }
}

/* Coming soon: a strip of green gaffer tape across the cover's lower right
   corner, with the words in Jason's hand outlined on top. It sits where the
   cover ends, and scales with the card. */
.entry__tape {
  position: absolute;
  z-index: 1;
  top: 46cqi;
  right: 1.5cqi;
  width: 46cqi;
  rotate: -7deg;
  filter: drop-shadow(0 1px 1px oklch(0 0 0 / 0.18)) drop-shadow(0 6px 10px oklch(0 0 0 / 0.22));
  pointer-events: none;
}
.entry__tape-strip { display: block; width: 100%; height: auto; }
.entry__tape-words { position: absolute; left: 11.3%; top: 27%; width: 62%; height: auto; }

/* Six or more issues: the home grid's five columns, row after row. */
@media (width >= 1024px) {
  .writing:not(:has(> .notes-rail)) .entry-list:has(> :nth-child(6 of .entry:not([data-placeholder]))),
  .writing:not(:has(> .notes-rail)) .entry-list[data-placeholders="show"]:has(> :nth-child(6 of .entry)) {
    grid-auto-flow: row;
    grid-template-columns: repeat(5, minmax(0, 1fr));
  }
}
@media (width < 1024px) {
  .entry-list { grid-auto-flow: row; grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 599px) {
  .page-head:has(+ .entry-list) { padding-bottom: var(--space-24); }
  .entry-list { grid-template-columns: minmax(0, 1fr); gap: var(--space-16); }
}

/* ---- Notes ---------------------------------------------------------------- */

/* Notes live at /writing/notes/, with a rail of the latest beside
   the posts on /writing/, after waxy.org's links (specs/notes-feed.md,
   variant E, Jason 2026-10-14). scripts/notes.mjs writes the markup. A day
   heads its notes; a link note's title is the link, underlined at rest,
   with the ↗ sliding in on hover as every outbound arrow does; the ink "#"
   at the end of a note is its permalink. */

.writing__feed { margin-top: 20px; }
.writing__feed a,
.notes-rail__foot a,
.notes-page__feeds a {
  color: var(--ink);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 5px;
}
:is(.writing__feed a, .notes-rail__foot a, .notes-page__feeds a, .note a):focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 3px;
}

/* From 1024px the posts take what's left beside a narrow rail, two cards a
   row: waxy.org's proportions, a column of about 45 characters and a wide
   gutter (Jason, 2026-10-28, after the 2-of-5 rail read too wide). With no
   notes the rail is empty and the posts keep the whole width. Below, the
   rail follows the posts. */
@media (width >= 1024px) {
  .writing:has(> .notes-rail) {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 360px;
    column-gap: var(--space-64);
    align-items: start;
  }
  .writing:has(> .notes-rail) > .writing__posts { grid-column: 1; }
  .writing > .notes-rail { grid-column: 2; }
  .writing:has(> .notes-rail) .entry-list {
    grid-auto-flow: row;
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}
@media (width >= 1024px) and (width < 1200px) {
  .writing:has(> .notes-rail) { grid-template-columns: minmax(0, 1fr) 320px; column-gap: var(--space-48); }
}
@media (width < 1024px) {
  .notes-rail { margin-top: var(--space-64); }
  /* Full width is too long a line for the serif. */
  .notes-rail .note { max-width: 640px; }
}
/* Phones show the latest three notes and a button to the rest, as
   waxy.org's More Links does. */
.notes-rail__more { display: none; }
.notes-rail__foot .notes-rail__more { text-decoration: none; }
@media (width >= 600px) {
  .notes-rail:has([data-always]) .notes-rail__foot { display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-24); }
  .notes-rail__more[data-always] { display: inline-flex; }
}
@media (max-width: 599px) {
  .notes-rail [data-extra] { display: none; }
  .notes-rail__foot { display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-24); }
  .notes-rail__more { display: inline-flex; }
}

/* Posts and Notes head their columns as peers. Notes links to every note
   at /writing/notes/, never underlined (Jason, 2026-10-28). */
.writing { padding-top: var(--page-top); }
.writing__title {
  margin-bottom: var(--space-28);
  font-family: var(--display);
  font-weight: 400;
  font-size: 42px;
  line-height: 44px;
  letter-spacing: -0.02em;
  text-transform: uppercase;
}
.writing__title a { color: inherit; text-decoration: none; }
.writing__title a:focus-visible { outline: 2px solid var(--ink); outline-offset: 4px; }
@media (max-width: 599px) {
  .writing__title { margin-bottom: var(--space-24); font-size: 30px; line-height: 32px; }
}
/* The rail's first day sits on the heading like the first card does. */
.notes-rail .notes__day:first-of-type { padding-top: 0; }


.notes-rail__foot { padding-top: var(--space-40); }

/* ---- Blogroll: people Jason reads, under the Notes rail ----
   A run of serif names separated by commas, headed like Posts and Notes.
   scripts/blogroll.mjs writes every name; site.js shows eight at random. */

/* From 1024px it takes the rail's column, under the rail. The posts span
   both rows so the rail and the Blogroll stack without a gap. */
@media (width >= 1024px) {
  .writing:has(> .notes-rail) > .writing__posts { grid-row: 1 / span 2; }
  .writing > .notes-rail { grid-row: 1; }
  .writing:has(> .notes-rail) > .blogroll { grid-column: 2; grid-row: 2; }
}
.writing > .blogroll { margin-top: var(--space-64); }
@media (width < 1024px) {
  .blogroll { max-width: 640px; }
}

.blogroll { display: flex; flex-direction: column; }
.blogroll .writing__title { margin-bottom: var(--space-12); }
.blogroll__dek { margin-bottom: var(--space-12); color: var(--ink-soft); }
.blogroll__list {
  font-family: var(--serif);
  font-size: 25px;
  line-height: 36px;
  font-synthesis: none;
  text-wrap: pretty;
}
.blogroll__item { display: inline; }
.blogroll .blogroll__item[hidden] { display: none; }
/* A comma after each name showing but the last, which site.js marks. The
   comma sits outside the link, so it isn't underlined. A line may break
   between names or inside one. */
.blogroll__item::after { content: ","; }
.blogroll__item:last-child::after,
.blogroll__item[data-last]::after { content: none; }
.blogroll__link {
  color: inherit;
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 5px;
  text-decoration-color: var(--line-strong);
}
@media (hover: hover) and (pointer: fine) {
  .blogroll__link:hover { text-decoration-color: currentColor; }
}
.blogroll__link:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; border-radius: 2px; }

/* Shuffle and See all need site.js, so they wait for it. */
.blogroll__actions { display: none; gap: var(--space-20); padding-top: var(--space-20); }
.js .blogroll[data-ready] .blogroll__actions { display: flex; }
.blogroll__button {
  min-height: 44px;
  margin-block: -12px;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  color: var(--ink-soft);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  cursor: pointer;
}
@media (hover: hover) and (pointer: fine) {
  .blogroll__button:hover { color: var(--ink); }
}
.blogroll__button:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
@media (prefers-reduced-motion: no-preference) {
  .blogroll__list.is-dealt .blogroll__item {
    animation: blogroll-deal 260ms var(--ease-out) both;
    animation-delay: calc(var(--i, 0) * 35ms);
  }
}
@keyframes blogroll-deal { from { opacity: 0; } }
/* Until site.js picks, show the first eight, so nothing jumps. */
.js .blogroll:not([data-ready]) .blogroll__item:nth-child(n + 9) { display: none; }
.js .blogroll:not([data-ready]) .blogroll__item:nth-child(8)::after { content: none; }

/* In the rail, space alone separates the days, as on waxy.org: the date
   sits close to its notes and well clear of the day before. */
.notes__day {
  display: flex;
  flex-direction: column;
  gap: var(--space-24);
  padding: 18px 0 24px;
  border-bottom: var(--hairline) solid var(--line);
}
.notes-rail .notes__day { padding: var(--space-32) 0 0; border-bottom: 0; }
.notes-rail .notes__date { margin-bottom: calc(var(--space-12) - var(--space-24)); }
.notes__date { color: var(--ink-soft); }

.note {
  display: flex;
  flex-direction: column;
  gap: var(--space-10);
  min-width: 0;
}
.note__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 2px 10px;
}
.note__title {
  font-family: var(--body);
  font-weight: 500;
  font-size: 17px;
  line-height: 22px;
}
/* .arrow-link keeps a short label on one line; a title may need two. */
.note__link {
  white-space: normal;
  color: inherit;
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 4px;
}
.note__domain { color: var(--ink-soft); }
.note__text {
  font-family: "EB Garamond", "EB Garamond Fallback", Georgia, serif;
  font-weight: 400;
  font-size: 19px;
  line-height: 25px;
  font-synthesis: none;
  text-wrap: pretty;
}
.note__text a {
  color: inherit;
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}
/* A title run into the text leads the note in the sans, like waxy.org's
   bold link, with ⌁ before the commentary. */
.note__text .note__link {
  font-family: var(--body);
  font-weight: 500;
  font-size: 0.86em;
  text-underline-offset: 4px;
}
.note__sep { padding-inline: 0.1em; }
.note__text code { font-family: var(--mono); font-size: 0.8em; }
/* The permalink: ink, and its own mark, so no underline until hover. */
.note__text .note__hash {
  padding-inline: 2px;
  text-decoration: none;
}
@media (hover: hover) and (pointer: fine) {
  .note__text .note__hash:hover { text-decoration: underline; }
}
.note__figure { margin-top: 8px; }
.note__figure img {
  display: block;
  width: 100%;
  height: auto;
  background: var(--frame-bg);
}
.note__code {
  margin-top: 8px;
  padding: 14px 16px 16px;
  background: var(--frame-empty);
}
.note__code figcaption { color: var(--ink-soft); margin-bottom: 8px; }
.note__code pre {
  font-family: var(--mono);
  font-size: 13px;
  line-height: 20px;
  overflow-x: auto;
}

/* /writing/notes/: the same note, its day in the first column from 1024px. */
.notes-page__feeds {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 24px;
  margin-top: 20px;
}
.notes--section { border-top: var(--hairline) solid var(--line); }
.notes--section .notes__day { padding: 20px 0 32px; gap: 28px; }
.notes--section .notes__date { font-size: var(--text-micro); }
.notes--section .note { max-width: 532px; }
@media (width >= 1024px) {
  .notes--section .notes__day {
    display: grid;
    grid-template-columns: repeat(5, minmax(0, 1fr));
    column-gap: 20px;
  }
  .notes--section .notes__date { grid-column: 1; grid-row: 1; padding-top: 4px; }
  .notes--section .note { grid-column: 2 / 4; max-width: none; }
}

/* A note's own page: the same note, larger, then the next ones. */
.note-page .page-head__title a { color: inherit; text-decoration: none; }
@media (hover: hover) and (pointer: fine) {
  .note-page .page-head__title a:hover { text-decoration: underline; text-decoration-thickness: 2px; text-underline-offset: 6px; }
}
/* The same inline note as the lists (title, ⌁, text), set larger. */
.note--page { max-width: 640px; gap: var(--space-16); }
.note--page .notes__date { margin-bottom: var(--space-4); }
.note--page .note__text { font-size: 24px; line-height: 32px; }
.note--page .note__text .note__link { text-underline-offset: 5px; }
/* Under the note, the next older ones as the lists show them, then the
   archive (Jason, 2026-10-28). */
.note-page__more {
  max-width: 640px;
  margin-top: var(--space-56);
  border-top: var(--hairline) solid var(--line);
}
.note-page__more .notes__day { padding: var(--space-32) 0 0; border-bottom: 0; }
.note-page__more .notes__date { margin-bottom: calc(var(--space-12) - var(--space-24)); }
.note-page__all { padding-top: var(--space-40); }
.note-page__all .button-outline { text-decoration: none; }
@media (max-width: 599px) {
  .note--page .note__text { font-size: 21px; line-height: 28px; }
}

/* ---- The About page ------------------------------------------------------- */

/* About's grid, from style.css, on the page's gutter, starting where a
   post's first line does. */
.about-page, .resume, .privacy { padding: var(--page-top) var(--gutter) var(--page-end); }
.resume .reading-text, .privacy .reading-text { font-size: 1.125rem; line-height: 1.5; }

.privacy__intro, .privacy__sections section { max-width: 550px; }
.privacy__intro { margin-bottom: var(--space-48); }
.privacy__intro h1 { font: inherit; font-size: 24px; line-height: 1.2; margin-bottom: var(--space-16); }
.privacy__sections { display: grid; gap: 44px var(--editorial-gap); }
.privacy .micro { line-height: 14px; }
.privacy__sections h2 { margin-bottom: var(--space-20); }
.privacy__sections p + p { margin-top: var(--space-16); }

@media (min-width: 1200px) {
  .privacy__sections { grid-template-columns: repeat(2, minmax(0, 550px)); }
  .privacy__sections #tools { grid-column: 2; grid-row: 1 / span 3; }
}
@media (max-width: 799px) {
  .privacy__intro { margin-bottom: 36px; }
}

/* Beside the pile's prints an 80px portrait read as the smallest thing on
   the page, so it takes 120px wherever the pile has a column or a row of
   its own. 240px is the largest the site publishes, two to the pixel here. */
@media (min-width: 800px) {
  .about__photo img { width: 120px; height: 120px; }
}

/* The pile: photos and a clip from Jason's life, in the space right of the
   body (Jason, 2026-10-11). scripts/make-about-photos.py writes it from a
   folder of originals and captions each print with its file's name. The
   prints overlap down two loose columns, each leaning a little its own
   way, and a click brings one to the front, where it stands straight
   (about.js). They are flat, as the page is: a white mat and a hairline, no
   shadow. From 1420px the pile is a third column, ending on the gutter;
   under that it follows the body, and on phones the roles. Without the
   script the prints are a plain column, each whole. */
.about__pile {
  grid-area: pile;
  container: about-pile / inline-size;
  justify-self: end;
  width: 100%;
  max-width: 600px;
}

@media (min-width: 1420px) {
  .about__inner:has(.about__pile) {
    grid-template-columns: var(--column) minmax(0, 580px) minmax(0, 1fr);
    grid-template-areas:
      "photo body pile"
      "title body pile"
      "status body pile"
      "roles body pile"
      "contact body pile";
  }
}

@media (min-width: 800px) and (max-width: 1419px) {
  .about__inner:has(.about__pile) {
    grid-template-rows: repeat(4, auto) 1fr auto;
    grid-template-areas:
      "photo body"
      "title body"
      "status body"
      "roles body"
      "contact body"
      ". pile";
  }
  .about__pile { justify-self: start; max-width: 440px; margin-top: var(--space-56); }
}

@media (max-width: 799px) {
  .about__inner:has(.about__pile) {
    grid-template-areas:
      "photo"
      "title"
      "status"
      "contact"
      "body"
      "roles"
      "pile";
  }
  .about__pile { justify-self: start; margin-top: var(--space-24); }
}

.pile {
  /* The foot holds two lines of caption. */
  --caption-size: 24px;
  --foot: calc(var(--caption-size) * 2 + 16px);
  display: grid;
  gap: var(--space-16);
  margin: 0;
  padding: 0;
  list-style: none;
}

.pile__print figure {
  position: relative;
  margin: 0;
  padding: var(--space-8) var(--space-8) 0;
  background: oklch(1 0 0);
  color: var(--fixed-ink);
  outline: 1px solid var(--line);
  outline-offset: -1px;
}

.pile__screen { position: relative; display: block; }
.pile__print :is(img, video) { display: block; width: 100%; height: auto; background: var(--fixed-ink); }
/* Each print is a Polaroid: a foot of the same height under every picture,
   whatever its width, with the caption handwritten in it (Jason,
   2026-10-20). Spidleweb is Jason's own handwriting, made in Fontself; it
   has no curly quotes, so those fall back to the mono. The markup keeps
   .micro, so these override its face, case and tracking. */
@font-face {
  font-family: "Spidleweb";
  src: url("/assets/fonts/spidleweb/spidleweb-regular.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

.pile__print figcaption {
  display: flex;
  align-items: center;
  justify-content: center;
  height: var(--foot);
  padding: 0 var(--space-4);
  font-family: "Spidleweb", var(--mono);
  font-size: var(--caption-size);
  line-height: 1;
  letter-spacing: 0;
  text-transform: none;
  text-align: center;
  text-wrap: balance;
}

/* The script places each print, in hundredths of the pile's width: --x, --y
   and --w, and --nx, --ny and --nw for a pile under 330px wide, the third
   column up to about 1470px, which runs as one staggered column. Until
   about.js shuffles the stacking (--z), each print lies over the top of the
   one below it (Jason, 2026-10-11; shuffled since 2026-10-20). The pile's
   height is the script's too, plus room for the last caption. */
.js .pile {
  display: block;
  position: relative;
  isolation: isolate;
  height: calc(var(--h) * 1cqw + var(--foot) + 16px);
}

.js .pile__print {
  position: absolute;
  top: calc(var(--y) * 1cqw);
  left: calc(var(--x) * 1%);
  z-index: var(--z, calc(var(--n) - var(--i)));
  width: calc(var(--w) * 1%);
  rotate: var(--tilt);
  /* Where a lifted print grows from: the side nearest the pile's edge, so it
     grows inward, and from its top near the pile's top, its foot near the
     foot. */
  transform-origin: calc(var(--x) / max(100 - var(--w), 1) * 100%) calc(var(--y) / var(--h) * 100%);
  transition: rotate 180ms var(--ease-out), scale 170ms var(--ease-out);
}

@container about-pile (max-width: 329px) {
  .js .pile { height: calc(var(--nh) * 1cqw + var(--foot) + 16px); }
  .js .pile__print {
    top: calc(var(--ny) * 1cqw);
    left: calc(var(--nx) * 1%);
    width: calc(var(--nw) * 1%);
    transform-origin: calc(var(--nx) / max(100 - var(--nw), 1) * 100%) calc(var(--ny) / var(--nh) * 100%);
  }
}

.js .pile__print[data-front] { rotate: 0deg; }

/* A print the pointer rests on, or a tap brings forward, is lifted: larger,
   never wider than the pile, so a phone never scrolls sideways (Jason,
   2026-10-20). 1.4 times, over 170ms, after a 40ms rest, with 24px
   captions, chosen with a tuning panel over 1.3, 240ms, 70ms and 26px. */
.js .pile__print[data-lifted] { scale: min(1.4, calc(100 / var(--w))); }
@container about-pile (max-width: 329px) {
  .js .pile__print[data-lifted] { scale: min(1.4, calc(100 / var(--nw))); }
}

/* The prints land on the pile from the bottom up, 90ms apart, each settling
   from a little above and a little larger, as if dropped (Jason, 2026-10-20).
   about.js sets --enter from the stacking and data-ready once the pile is in
   view. Until then the prints are clear but still focusable, so Tab can reach
   them and bring the pile into view. Should about.js never run, they show
   after 3s. */
.js .pile:not([data-ready]) .pile__print {
  opacity: 0;
  animation: pile-show 0s 3s forwards;
}
.js .pile[data-waiting]:not([data-ready]) .pile__print { animation: none; }

.js .pile[data-ready] .pile__print {
  animation: pile-land 460ms var(--ease-out) backwards;
  animation-delay: calc(var(--enter, 0) * 90ms);
}

@media (prefers-reduced-motion: reduce) {
  .js .pile[data-ready] .pile__print {
    animation-name: page-fade;
    animation-duration: 200ms;
    animation-delay: calc(var(--enter, 0) * 40ms);
  }
}

@keyframes pile-land {
  0% { opacity: 0; translate: 0 -14px; scale: 1.05; }
  40% { opacity: 1; }
}
@keyframes pile-show { to { opacity: 1; } }

/* The whole print is the button that brings it forward; on a clip it plays
   and pauses too. It lies over the picture, clear, and the clip's marks
   show through it. */
.pile__pick {
  position: absolute;
  z-index: 1;
  inset: 0;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

.pile__print[data-front]:not(.pile__print--clip) .pile__pick { cursor: default; }
.pile__pick:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
/* The focused print rises over its neighbours while it holds focus, so its
   ring is never cut by the print above it. The pile is isolated, so this
   stays inside it. */
.js .pile__print:has(.pile__pick:focus-visible) { z-index: 10000; }

/* A caption that links out lies over the print's button, so a press on the
   words follows the link and a press anywhere else still brings the print
   forward. It is a .text-link: underlined at rest, heavier under a mouse. */
.pile__print figcaption a {
  position: relative;
  z-index: 2;
}
.pile__print figcaption a::before { content: ""; position: absolute; inset: -7px -6px -8px; }
.pile__print figcaption a:focus-visible { outline: 2px solid var(--fixed-ink); outline-offset: 2px; }

/* The clip's marks, after the site's player: Play or Pause at the lower
   left, drawn only (the print is the button), and Sound at the lower right
   once the clip is in front. It is the one clip on the site with sound, and
   it starts silent. */
.pile__screen .video-toggle { left: 8px; bottom: 8px; pointer-events: none; }
.pile__screen .video-toggle::after { content: none; }

.pile__sound {
  position: absolute;
  z-index: 2;
  right: 8px;
  bottom: 8px;
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  padding: 0;
  border: 1px solid var(--ink-edge);
  background: var(--ink-veil);
  color: var(--fixed-paper);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background-color 120ms var(--ease-fade), scale 150ms var(--ease-out);
}

.pile__sound:active { scale: 0.96; }
.pile__sound::after { content: ""; position: absolute; inset: -7px; }
.pile__print:not([data-front]) .pile__sound { display: none; }

.pile__sound:focus-visible {
  outline: 2px solid var(--fixed-paper);
  outline-offset: 2px;
  box-shadow: 0 0 0 4px var(--fixed-ink);
}

@media (hover: hover) and (pointer: fine) {
  .pile__sound:hover { background: var(--ink-scrim); }
  .pile__sound:hover .icon { background-color: var(--site-accent); }
}

@media (prefers-reduced-motion: reduce) {
  .pile__sound { transition: none; }
  .js .pile__print { transition: none; }
}

/* ---- The post page -------------------------------------------------------- */

/* The post reads in the case study's wide column, so both kinds of page
   share one measure. */
.post {
  display: grid;
  grid-template-columns: var(--editorial-columns);
  column-gap: var(--editorial-gap);
  padding: var(--page-top) var(--gutter) var(--page-end);
}
.post__article { grid-column: main; min-width: 0; }

/* A post's margin notes, where a case study keeps its context: the title,
   the dek, the date, and where to find the thing it describes. They start
   level with the cover and stay in view while the post scrolls. */
.post__context {
  grid-column: rail;
  grid-row: 1;
  align-self: start;
  position: sticky;
  top: 24px;
  display: grid;
  gap: var(--space-20);
}
.post__context ~ .post__article { grid-row: 1; }
/* In the narrow column the title and dek step down from the full width's
   42/46 and 24/32, so a title like "How I built" keeps its first line. */
.post__context .post__title { font-size: 32px; line-height: 36px; }
.post__context .post__dek { font-size: 20px; line-height: 27px; }
.post__context .post__kicker { margin-top: var(--space-4); }
.post__article > .post__cover:first-child { margin-top: 0; }

.post__head { display: grid; gap: var(--space-20); }
/* On a phone Back to top and Back to home sit over the running text, so a
   link reached by Tab stops clear of them. */
@media (max-width: 999px) {
  html:has(.page--post) { scroll-padding-bottom: 72px; }
}
.post__kicker { color: var(--ink-soft); }
.post__title {
  max-width: 900px;
  font-family: var(--display);
  font-weight: 400;
  font-size: 42px;
  line-height: 46px;
  letter-spacing: -0.02em;
  text-transform: uppercase;
  overflow-wrap: anywhere;
}
.post__dek {
  max-width: 700px;
  font-family: var(--serif);
  font-weight: 400;
  font-size: 24px;
  line-height: 32px;
  font-synthesis: none;
  text-wrap: pretty;
}
/* The site's mono line is 15.6px to Paper's 14px, so the spacing around it
   gives back the difference. */
.post__facts { display: flex; flex-wrap: wrap; gap: var(--space-4) var(--space-32); padding-top: var(--space-6); }
.post__facts div { display: flex; gap: var(--space-10); }
.post__facts dt { color: var(--ink-soft); }

.post__cover {
  margin-top: var(--space-40);
  overflow: hidden;
  outline: 1px solid var(--frame-edge);
  outline-offset: -1px;
  background: var(--frame-empty);
}
.post__cover img { width: 100%; }
/* Wider screens trim the cover's empty top and bottom, as Paper frames it. */
@media (min-width: 600px) {
  .post__cover img { aspect-ratio: 1026 / 538; object-fit: cover; }
}

.post__body { margin-top: var(--space-56); }

/* The post's text, in the serif the site keeps for long reading, on a
   measure that keeps every line under 75 characters. Figures take the
   whole column under it. */
.post__body > p {
  max-width: 550px;
  font-family: var(--serif);
  font-weight: 400;
  font-size: 21px;
  line-height: 31px;
  font-synthesis: none;
  text-wrap: pretty;
}
.post__body > p + p { margin-top: var(--paragraph-gap); }
.post__body a:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 2px;
}
.post__body code { font-family: var(--mono); font-size: 0.8em; }
/* A highlight is a flat band of --marker until post-highlights.js takes over
   and draws it as a marker stroke behind the words. */
.post__body mark { background-color: var(--marker); color: inherit; }
.post__body--marked mark { background-color: transparent; }
/* The stroke is drawn in --marker-stroke, the light marker. On dark paper a
   filter takes it down to the flat band's --marker, so a switch of scheme
   recolours strokes already drawn. */
@media (prefers-color-scheme: dark) {
  .post__body .highlight { filter: brightness(0.42); }
}
/* A stroke's frayed end runs a few pixels past the words, into the gutter.
   Clip only past the page's edge, so a mark at the end of a phone's line
   can't scroll the page sideways. clip, not hidden, keeps the notes sticky. */
.post:has(.post__body--marked) { overflow-x: clip; }
/* A figure and its caption are one framed object: the caption sits inside
   the frame, under a hairline, at the image's width. */
.post__figure {
  margin: var(--space-48) 0;
  /* The frame's four hairlines are background layers inside a transparent
     1px border, since a border-image can't draw a closed frame. */
  border: 1px solid transparent;
  background:
    linear-gradient(var(--line), var(--line)) top / 100% var(--hairline) no-repeat border-box,
    linear-gradient(var(--line), var(--line)) bottom / 100% var(--hairline) no-repeat border-box,
    linear-gradient(var(--line), var(--line)) left / var(--hairline) 100% no-repeat border-box,
    linear-gradient(var(--line), var(--line)) right / var(--hairline) 100% no-repeat border-box;
}
.post__figure img { display: block; width: 100%; height: auto; }
.post__figure figcaption {
  padding: 12px 16px 13px;
  /* The hairline keeps the frame's width; the words stop at 520px, under
     75 characters a line. */
  padding-right: max(var(--space-16), calc(100% - 536px));
  border-top: 1px solid var(--line);
  border-image: linear-gradient(var(--line) var(--hairline), transparent 0) 1;
  font-size: 15px;
  line-height: 22px;
  color: var(--ink-soft);
  text-wrap: pretty;
}
/* The cheatsheet's syntax list, set as text under its image so it stays
   readable on a phone. Two columns where the figure is wide enough. */
.post__syntax { padding: var(--space-14) var(--space-16) var(--space-16); border-top: 1px solid var(--line); border-image: linear-gradient(var(--line) var(--hairline), transparent 0) 1; font-size: 15px; line-height: 22px; }
.post__syntax-title { font: inherit; margin-bottom: var(--space-8); }
/* A label in the figure, not running text: the body's serif rule outranks the one above. */
.post--feature .post__body .post__syntax-title { font: inherit; max-width: none; }
.post__syntax dl { display: grid; grid-template-columns: minmax(0, 1fr); column-gap: var(--space-32); }
.post__syntax dl > div { display: grid; grid-template-columns: 88px minmax(0, 1fr); gap: var(--space-12); padding: 5px 0; border-top: 1px solid var(--line); border-image: linear-gradient(var(--line) var(--hairline), transparent 0) 1; }
.post__syntax dd { color: var(--ink-soft); text-wrap: pretty; }
.post__syntax code { font-size: 0.9em; color: var(--ink); }
@media (min-width: 900px) {
  .post__syntax dl { grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-rows: repeat(4, auto); grid-auto-flow: column; }
}
/* A cover drawn edge to edge shows whole rather than trimmed to Paper's frame. */
.post__cover--whole img { aspect-ratio: auto; }
.post__placeholder { color: var(--ink-soft); }
.post__bars { display: grid; gap: var(--space-28); margin-top: var(--space-24); }
.post__bars p { display: grid; gap: var(--space-14); }
/* Tracks that never grow past the column, so a bar's width is a ceiling. */
.post__bars,
.post__bars p { grid-template-columns: minmax(0, 1fr); }
.post__bars span { display: block; max-width: 100%; height: 12px; background: var(--placeholder-fill); }
.post__bars p:first-child span:nth-child(1) { width: 680px; }
.post__bars p:first-child span:nth-child(2) { width: 660px; }
.post__bars p:first-child span:nth-child(3) { width: 672px; }
.post__bars p:first-child span:nth-child(4) { width: 420px; }
.post__bars p:last-child span:nth-child(1) { width: 676px; }
.post__bars p:last-child span:nth-child(2) { width: 650px; }
.post__bars p:last-child span:nth-child(3) { width: 540px; }

/* ---- The Feature post ------------------------------------------------------ */

/* "3 Feature · 1440" and "· 390" on "Post layouts · magazine · 2026-10-02"
   in Paper: a post laid out like a magazine feature. The title runs the
   full width over the cover, and one reading column, indented by the
   margin a case study keeps its notes in, bends around the pictures: an
   inset hangs past the measure beside shortened lines, a screen breaks out
   to the full width, and two highlighted lines come back as display-size
   pull quotes. Each figure's treatment is chosen per post in its markup. */
.post--feature {
  display: block;
}
.post--feature :is(.post__head, .post__body) {
  display: grid;
  grid-template-columns: var(--feature-columns);
  column-gap: var(--editorial-gap);
}
.post--feature :is(.post__head, .post__body) > * { grid-column: main; min-width: 0; }
.post--feature .post__head { row-gap: var(--space-28); }
.post--feature .post__title {
  grid-column: full;
  max-width: none;
  font-size: clamp(2.125rem, 7.22vw, 6.5rem);
  line-height: 0.923;
  letter-spacing: -0.035em;
}
/* The dek and its meta line stack at the left edge, under the title. */
.post--feature .post__standfirst { grid-column: full; display: grid; justify-items: start; gap: var(--space-12); }
.post--feature .post__dek { max-width: 780px; font-size: 28px; line-height: 36px; }
/* Beats the margin note's :first-child reset; a Feature post has no notes. */
.post--feature .post__article > .post__cover { margin-top: var(--space-48); }

/* Paragraphs sit --paragraph-gap apart, as About's do. */
.post--feature .post__body { margin-top: var(--space-64); row-gap: var(--paragraph-gap); }
.post--feature .post__body p {
  /* Long-form text is 20/30px at every width, as About's is (Jason,
     2026-10-19), held to about 70 characters a line. */
  max-width: 26em;
  font-family: var(--serif);
  font-weight: 400;
  font-size: 20px;
  line-height: 30px;
  font-synthesis: none;
  text-wrap: pretty;
}
.post--feature .post__body p + p { margin-top: 0; }
.post--feature .post__body .post__lede { max-width: 640px; font-size: 27px; line-height: 38px; }

/* A pull quote is part of the text: it stands where its sentence would
   have run in the paragraph above, so the line is read once, and screen
   readers read it too. Each line gets its own band of marker, 58px tall
   with 6px of paper between. The face's own box is taller than the band,
   so the band is a background sized to it rather than the span's padding.
   Under 1440 wide the quote shrinks with the window, so it keeps the
   breaks it has at 1440 as the column narrows. */
.post__pull {
  --pull-size: min(50px, 3.47vw);
  --pull-pitch: min(64px, 4.44vw);
  --pull-band: min(58px, 4.03vw);
  --pull-inline: var(--space-10);
  max-width: 1028px;
  margin-block: var(--space-48);
  font-family: var(--display);
  font-weight: 400;
  font-size: var(--pull-size);
  line-height: var(--pull-pitch);
  letter-spacing: -0.025em;
  text-transform: uppercase;
  /* A short display line never leaves one word on its last line. */
  text-wrap: balance;
}
.post__pull span {
  padding-inline: var(--pull-inline);
  background: linear-gradient(var(--marker), var(--marker)) 0 50% / 100% var(--pull-band) no-repeat;
  -webkit-box-decoration-break: clone;
  box-decoration-break: clone;
}
/* Paper breaks the first quote after "fix this" and "going to"; a measure
   of 18.4em puts plain wrapping there too, at any size. */
.post__pull--measured { max-width: 18.4em; }

/* A wrap sets text beside an inset: the inset hangs past the measure, and
   the lines beside it shorten to about 40 characters. In the markup the
   inset comes first, which is where it sits once the wrap stacks. */
.post__wrap { display: grid; row-gap: var(--space-20); }
.post--feature .post__figure { margin: 0; }
.post__figure--inset { max-width: 640px; }
.post__wrap-text { display: grid; row-gap: var(--space-20); }
/* The cheatsheet's syntax list, in the inset's two columns. */
.post__figure--inset .post__syntax dl { column-gap: var(--space-24); }
.post__figure--inset .post__syntax dl > div { grid-template-columns: 76px minmax(0, 1fr); gap: var(--space-10); }
/* The text keeps its 356px, about 40 characters, and the inset gives up
   width as the window narrows. Below 1200 wide the inset would be too
   small to read, so the wrap stacks. */
@media (min-width: 1200px) {
  .post__wrap {
    grid-template-columns: minmax(356px, 1fr) minmax(0, 640px);
    column-gap: var(--space-32);
    align-items: start;
    max-width: 1028px;
  }
  .post__wrap > .post__figure--inset { grid-column: 2; grid-row: 1; }
  .post__wrap-text { grid-column: 1; grid-row: 1; }
  /* Paper drops the cheatsheet's wrap a little lower. */
  .post__wrap:has(.post__syntax) { margin-top: var(--space-8); }
}

/* A breakout takes the full width, and its caption starts on the margin
   under it. The margin alone is too narrow for a sentence, so the caption
   runs on to a post figure's 520px measure. */
.post--feature .post__body > .post__breakout {
  grid-column: full;
  display: grid;
  grid-template-columns: subgrid;
  row-gap: var(--space-14);
  margin-block: var(--space-40);
}
/* The site's <picture> is display: contents, so the image is the grid item. */
.post__breakout img {
  grid-column: 1 / -1;
  display: block;
  width: 100%;
  height: auto;
  outline: 1px solid var(--frame-edge);
  outline-offset: -1px;
}
.post__breakout figcaption {
  grid-column: 1 / -1;
  max-width: 520px;
  font-size: 15px;
  line-height: 22px;
  color: var(--ink-soft);
  text-wrap: pretty;
}

/* Where to find the plugin, after the last line: an ink rule, then a row
   for each link with its arrow at the far end. */
.post__end-links {
  max-width: 550px;
  margin-top: var(--space-28);
  list-style: none;
  border-top: 1px solid var(--ink);
}
.post__end-links li { border-bottom: 1px solid var(--line); border-image: linear-gradient(to top, var(--line) var(--hairline), transparent 0) 1; }
.post__end-links a {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-16);
  min-height: 48px;
  text-decoration: none;
}
@media (hover: hover) and (pointer: fine) {
  .post__end-links a:hover { text-decoration: underline; text-underline-offset: 4px; }
}

/* ---- Tablet and phone ------------------------------------------------------ */

@media (max-width: 999px) {
  .next-story { padding-top: var(--space-96); padding-bottom: var(--space-56); }
  .next-story__link { font-size: clamp(2rem, 6vw, 3.75rem); }
  /* One column: the notes sit above the title, no longer sticky. */
  .post__context { position: static; grid-row: auto; margin-bottom: var(--space-20); }
  .post__context ~ .post__article { grid-row: auto; }
  /* A Feature post drops its margin: the column, a breakout's caption and
     the pull quotes all start on the gutter. */
  .post--feature .post__body > .post__breakout { grid-template-columns: minmax(0, 1fr); }
  .post__pull { --pull-size: 38px; --pull-pitch: 50px; --pull-band: 45px; --pull-inline: var(--space-8); margin-block: var(--space-32); }
}

@media (max-width: 599px) {
  .next-story { padding-top: var(--space-64); padding-bottom: var(--space-28); }
  .next-story::before { clip-path: polygon(0 20px, 100% 0, 100% 100%, 0 100%); }
  .next-story__link { font-size: clamp(1.75rem, 7.5vw, 2.625rem); gap: var(--space-16); }

  .page-head { padding-bottom: var(--space-24); }
  .page-head__title { font-size: 30px; line-height: 32px; }


  .post__head { gap: var(--space-16); }
  .post__title { font-size: 28px; line-height: 32px; }
  .post__dek { font-size: 20px; line-height: 26px; }
  .post__facts { display: grid; gap: var(--space-6); padding-top: 3px; }
  /* Wide enough for "First published" on one line. */
  .post__facts div { display: grid; grid-template-columns: 120px 1fr; gap: var(--space-10); }
  .post__cover { margin-top: var(--space-28); }
  .post__cover img { aspect-ratio: 356 / 198; object-fit: cover; }
  .post__cover--whole img { aspect-ratio: auto; }
  .post__body > p { font-size: 19px; line-height: 28px; }
  .post__figure { margin: var(--space-32) 0; }
  .post__figure figcaption { padding: 10px 12px 11px; font-size: 14px; line-height: 20px; }
  .post__body { margin-top: var(--space-32); }
  .post__bars { gap: var(--space-40); margin-top: var(--space-20); }
  .post__bars p { gap: var(--space-12); }
  .post__bars span { height: 11px; }
  .post__bars p:first-child span:nth-child(1) { width: 358px; }
  .post__bars p:first-child span:nth-child(2) { width: 340px; }
  .post__bars p:first-child span:nth-child(3) { width: 352px; }
  .post__bars p:first-child span:nth-child(4) { width: 200px; }
  .post__bars p:last-child span:nth-child(1) { width: 356px; }
  .post__bars p:last-child span:nth-child(2) { width: 330px; }
  .post__bars p:last-child span:nth-child(3) { width: 250px; }

  /* The Feature post on a phone, from "3 Feature · 390" in Paper. */
  .post--feature .post__dek { font-size: 22px; line-height: 29px; }
  .post--feature .post__article > .post__cover { margin-top: var(--space-32); }
  /* A phone's measure is narrower than 18.4em, so the first quote balances instead. */
  .post__pull--measured { text-wrap: balance; }
  .post--feature .post__body { margin-top: var(--space-40); }
  .post--feature .post__body .post__lede { font-size: 22px; line-height: 31px; }
  .post__wrap,
  .post__wrap-text { row-gap: var(--space-16); }
  .post__pull { --pull-size: 26px; --pull-pitch: 36px; --pull-band: 32px; --pull-inline: var(--space-6); margin-block: var(--space-20); }
  .post--feature .post__body > .post__breakout { margin-block: var(--space-16); row-gap: var(--space-10); }
  .post__breakout figcaption { font-size: 14px; line-height: 20px; }
}
