/* 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: 24px; }

/* 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: 12px;
  align-items: baseline;
  padding: 10px 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: 14px;
  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: 18px;
  text-transform: uppercase;
  text-wrap: pretty;
}
.case-context__facts--labelled dd > span { white-space: nowrap; }
/* A lone credit reaches into its row's 10px padding, so an 18px name takes
   a 38px target. Two names can wrap onto two lines, and stretched targets
   would overlap there, so in a shared row each takes its own 18px line and
   only the outer edges reach into the padding: the first upward, the last
   downward, 28px apiece and never touching. */
.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 > .credit-link:first-of-type::before { top: -10px; }
.case-context__facts--labelled dd > .credit-link:last-of-type::before { bottom: -10px; }
.case-context__facts--labelled + .case-context__note { margin-top: 18px; 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: 8px;
  min-height: 44px;
  /* 2px less on the icon's side, which reads as more space than text. */
  padding: 0 16px 0 14px;
  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. */
.proof__open { position: relative; display: inline-block; 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: 3px; }

/* 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: 112px var(--gutter) 68px;
  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: 24px;
  min-height: 44px;
  padding: 12px 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) 96px; }

.page-head {
  padding-top: 56px;
  padding-bottom: 28px;
}
.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: 32px;
  font-family: "EB Garamond", "EB Garamond Fallback", Georgia, serif;
  font-weight: 400;
  font-size: 24px;
  line-height: 30px;
  font-synthesis: none;
  text-wrap: pretty;
}
.page-head + .page-note { margin-top: 4px; }
.page-note + .page-note { margin-top: 16px; }
.page-note a { color: inherit; text-decoration-thickness: 1px; text-underline-offset: 4px; }
.page-note a:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }

/* 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: 40px; }

.entry-list {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  gap: 44px 20px;
  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: oklch(0.834 0.007 106.5);
  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); }

/* 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: 4px; }

.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: "EB Garamond", "EB Garamond Fallback", Georgia, 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: 10px 12px;
  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) {
  .entry-list:has(> :nth-child(6 of .entry:not([data-placeholder]))),
  .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: 24px; }
  .entry-list { grid-template-columns: minmax(0, 1fr); gap: 16px; }
}

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

/* About's grid, from style.css, on the page's gutter, starting where a
   post's first line does. */
.about-page { padding: 87px var(--gutter) 96px; }

/* 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: 392px 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: 56px; }
}

@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: 22px; }
}

.pile {
  display: grid;
  gap: 16px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.pile__print figure {
  position: relative;
  margin: 0;
  padding: 6px 6px 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); }
.pile__print figcaption { padding: 7px 2px 8px; 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. Each print
   lies over the top of the one below it, so its caption, at its foot, is
   never covered (Jason, 2026-10-11). 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 + 60px);
}

.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);
  transition: rotate 180ms var(--ease-out);
}

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

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

/* 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: 3px; }

/* 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. Underlined at rest, as a credit is where a finger can't hover. */
.pile__print figcaption a {
  position: relative;
  z-index: 2;
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}
.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; }

/* A print under the pointer straightens most of the way, to say it will come. */
@media (hover: hover) and (pointer: fine) {
  .js .pile__print:not([data-front]):hover { rotate: calc(var(--tilt) * 0.3); }
}

/* 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: 87px var(--gutter) 96px;
}
.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: 20px;
}
.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: 4px; }
.post__article > .post__cover:first-child { margin-top: 0; }

.post__head { display: grid; gap: 20px; }
.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: "EB Garamond", "EB Garamond Fallback", Georgia, 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: 4px 32px; padding-top: 6px; }
.post__facts div { display: flex; gap: 10px; }
.post__facts dt { color: var(--ink-soft); }

.post__cover {
  margin-top: 40px;
  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: 55px; }

/* 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: "EB Garamond", "EB Garamond Fallback", Georgia, serif;
  font-weight: 400;
  font-size: 21px;
  line-height: 31px;
  font-synthesis: none;
  text-wrap: pretty;
}
.post__body > p + p { margin-top: 20px; }
.post__body a { text-decoration: underline; text-underline-offset: 3px; text-decoration-thickness: 1px; }
.post__body a:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 3px;
}
.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: 48px 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(16px, 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: 14px 16px 16px; 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: 8px; }
.post__syntax dl { display: grid; grid-template-columns: minmax(0, 1fr); column-gap: 32px; }
.post__syntax dl > div { display: grid; grid-template-columns: 88px minmax(0, 1fr); gap: 12px; 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: 28px; margin-top: 24px; }
.post__bars p { display: grid; gap: 14px; }
/* 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: 28px; }
.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: 12px; }
.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: 48px; }

.post--feature .post__body { margin-top: 64px; row-gap: 20px; }
.post--feature .post__body p {
  max-width: 550px;
  font-family: "EB Garamond", "EB Garamond Fallback", Georgia, serif;
  font-weight: 400;
  font-size: 21px;
  line-height: 31px;
  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: 10px;
  max-width: 1028px;
  margin-block: 44px;
  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: 20px; }
.post--feature .post__figure { margin: 0; }
.post__figure--inset { max-width: 640px; }
.post__wrap-text { display: grid; row-gap: 20px; }
/* The cheatsheet's syntax list, in the inset's two columns. */
.post__figure--inset .post__syntax dl { column-gap: 24px; }
.post__figure--inset .post__syntax dl > div { grid-template-columns: 76px minmax(0, 1fr); gap: 10px; }
/* 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: 32px;
    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: 8px; }
}

/* 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: 14px;
  margin-block: 36px;
}
/* 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: 28px;
  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: 16px;
  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: 96px; padding-bottom: 56px; }
  .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: 20px; }
  .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: 8px; margin-block: 32px; }
}

/* Below 800px the masthead puts the role under the name and sizes to its
   content, 96px tall, where Paper's phone masthead block runs to 86px. */
@media (max-width: 799px) {
  .page-head { padding-top: 59px; }
  .post { padding-top: 58px; }
  .about-page { padding-top: 58px; padding-bottom: 64px; }
}

@media (max-width: 599px) {
  .next-story { padding-top: 64px; padding-bottom: 28px; }
  .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: 16px; }

  .page-main { padding-bottom: 64px; }
  .page-head { padding-bottom: 24px; }
  .page-head__title { font-size: 30px; line-height: 32px; }


  .post { padding-bottom: 64px; }
  .post__head { gap: 16px; }
  .post__title { font-size: 28px; line-height: 32px; }
  .post__dek { font-size: 20px; line-height: 26px; }
  .post__facts { display: grid; gap: 6px; padding-top: 3px; }
  /* Wide enough for "First published" on one line. */
  .post__facts div { display: grid; grid-template-columns: 120px 1fr; gap: 10px; }
  .post__cover { margin-top: 28px; }
  .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__body > p + p { margin-top: 16px; }
  .post__figure { margin: 32px 0; }
  .post__figure figcaption { padding: 10px 12px 11px; font-size: 14px; line-height: 20px; }
  .post__body { margin-top: 32px; }
  .post__bars { gap: 36px; margin-top: 20px; }
  .post__bars p { gap: 12px; }
  .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: 32px; }
  /* 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: 40px; row-gap: 16px; }
  .post--feature .post__body p { font-size: 19px; line-height: 28px; }
  .post--feature .post__body .post__lede { font-size: 22px; line-height: 31px; }
  .post__wrap,
  .post__wrap-text { row-gap: 16px; }
  .post__pull { --pull-size: 26px; --pull-pitch: 36px; --pull-band: 32px; --pull-inline: 6px; margin-block: 20px; }
  .post--feature .post__body > .post__breakout { margin-block: 16px; row-gap: 10px; }
  .post__breakout figcaption { font-size: 14px; line-height: 20px; }
}
