/* The blog. Every page opens on the home page's sky, torn off along the bottom
   onto a neutral reading paper. The pinned bar and the foot are the orange
   stock the home page's banner and about panel are printed on. Impact for
   display, a book face for reading. Values live in tokens.css.

   Every effect here is an enhancement over a page that already reads: no
   scroll timelines, no view transitions, no JavaScript, or reduced motion,
   and the page is the same page standing still. */
*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-padding-top: calc(var(--bar-h) + var(--ruler-h) + var(--space-3));
}

body {
  margin: 0;
  color: var(--ink);
  background: var(--paper);
  font: 400 var(--size-text)/var(--leading-text) var(--font-text);
}

::selection { background: var(--stock); color: var(--ink); }

a { color: inherit; text-decoration-color: var(--stock); text-decoration-thickness: var(--rule); text-underline-offset: 0.18em; }
a:hover { opacity: 0.66; }
a:focus-visible { outline: var(--rule) solid var(--ink); outline-offset: var(--space-1); }

/* Holds the letterpress filter. Out of sight but not display:none, which in
   some browsers stops the filter applying to the text that names it. */
.defs { position: absolute; width: 0; height: 0; overflow: hidden; }

.eyebrow {
  margin: 0 0 var(--space-3);
  font: 600 var(--size-eyebrow)/1.4 var(--font-ui);
  letter-spacing: var(--track-eyebrow);
  text-transform: uppercase;
  color: var(--ink-soft);
}

.title {
  margin: 0 0 var(--space-4);
  max-width: 18ch;
  font: 400 var(--size-title)/var(--leading-tight) var(--font-display);
  letter-spacing: var(--track-display);
  text-transform: uppercase;
  text-wrap: balance;
}

.standfirst {
  margin: 0;
  max-width: var(--measure);
  font: 400 var(--size-standfirst)/1.45 var(--font-ui);
  text-wrap: pretty;
}

/* ===========================================================================
   The sky, torn

   Three layers under the copy. .hero-fringe is the white core of the paper,
   cut to the lower of the two edges in tear-fringe.svg; .hero-under around it
   casts the shadow of the whole torn sheet onto the page, and stays still so
   the shadow is drawn once rather than every frame. .hero-sky is the print,
   cut to the upper edge in tear-edge.svg, so the core shows between the two.
   Each mask is a solid block down to the top of the tear band, joined to
   the tile repeated along the bottom; the tile is the same size at any width,
   so the tear keeps its grain on a phone instead of being squeezed.
   =========================================================================== */

.hero {
  position: relative;
  z-index: 1;
  isolation: isolate;
  padding-bottom: calc(var(--tear-h) + var(--hero-air));
}

.hero-under,
.hero-fringe,
.hero-sky {
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
}

.hero-under { filter: var(--tear-shadow); }

.hero-fringe {
  background: var(--fiber);
  -webkit-mask:
    var(--solid) top / 100% calc(100% - var(--tear-h) + var(--rule-thin)) no-repeat,
    var(--tear-fringe) bottom center / var(--tear-w) var(--tear-h) repeat-x;
  mask:
    var(--solid) top / 100% calc(100% - var(--tear-h) + var(--rule-thin)) no-repeat,
    var(--tear-fringe) bottom center / var(--tear-w) var(--tear-h) repeat-x;
}

.hero-sky {
  overflow: hidden;
  background: var(--fiber);
  -webkit-mask:
    var(--solid) top / 100% calc(100% - var(--tear-h) + var(--rule-thin)) no-repeat,
    var(--tear-edge) bottom center / var(--tear-w) var(--tear-h) repeat-x;
  mask:
    var(--solid) top / 100% calc(100% - var(--tear-h) + var(--rule-thin)) no-repeat,
    var(--tear-edge) bottom center / var(--tear-w) var(--tear-h) repeat-x;
}

/* Laid out as the home page lays its sky: a window's worth of picture, cover,
   centred, from the top of the page. So at the top of a post the hero is a
   crop of exactly what the home page was showing, and the sky can carry over
   between them pixel for pixel. A small viewport, because that is what a
   fixed full-window element measures on a phone at load, and it never
   changes as the toolbar comes and goes. As the page scrolls it lags behind
   at well under the scroll speed, so the gap it leaves at the top is always
   above the window. */
.hero-sky::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: max(100vh, 100%);
  height: max(100svh, 100%);
  background: var(--sky) var(--sky-focus) / cover no-repeat;
}

/* A little light at the top, where the masthead sits on the brightest cloud. */
.hero-sky::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(to bottom, var(--wash), transparent 30%);
}

.masthead,
.hero-copy,
main,
.colophon-in {
  max-width: var(--sheet);
  margin: 0 auto;
  padding-left: var(--gutter);
  padding-right: var(--gutter);
}

.masthead {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--space-3);
  padding-top: var(--space-4);
  font: 600 var(--size-eyebrow)/1 var(--font-ui);
  letter-spacing: var(--track-eyebrow);
  text-transform: uppercase;
}

.masthead a { text-decoration: none; }

.hero-copy { padding-top: var(--hero-air); }

.hero .eyebrow { color: var(--ink); opacity: 0.78; }

/* The title is printed onto the sky in the poster's ink, as the out-of-ammo
   line is on the home page. */
.hero .title { filter: url("#letterpress"); }

.hero-index .title {
  max-width: none;
  margin-bottom: var(--space-4);
  font-size: var(--size-index-title);
  line-height: 0.86;
}

/* As the page scrolls the sky lags and the copy lifts away a little faster,
   so the hero comes apart in depth rather than moving as one flat card. Run
   by the scroll itself, off the main thread, where the browser can. */
@supports (animation-timeline: scroll()) {
  @media (prefers-reduced-motion: no-preference) {
    .hero-sky::before {
      animation: sky-drift linear both;
      animation-timeline: scroll(root);
      animation-range: 0vh 100vh;
    }
    .hero-copy {
      animation: copy-lift linear both;
      animation-timeline: scroll(root);
      animation-range: 0vh 90vh;
    }
  }
}

@keyframes sky-drift { to { transform: translateY(var(--drift)); } }
@keyframes copy-lift { to { transform: translateY(calc(-1 * var(--lift))); opacity: 0.35; } }

/* ===========================================================================
   The pinned bar, and the yardstick under it

   Slides in once the hero has scrolled away (blog.js watches for that). The
   ruler fills as the page is read: driven by the scroll timeline where the
   browser has one, otherwise by --read, which blog.js keeps up to date.
   =========================================================================== */

.topbar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 20;
  height: var(--bar-h);
  background-color: var(--stock);
  background-image: var(--paper-img, none);
  background-size: var(--paper-size);
  border-bottom: var(--rule) solid var(--ink);
  box-shadow: var(--bar-shadow);
  transform: translateY(calc(-100% - var(--ruler-h) - var(--space-4)));
  visibility: hidden;
  transition: transform var(--settle) var(--ease), visibility 0s linear var(--settle);
}

.topbar.on {
  transform: none;
  visibility: visible;
  transition: transform var(--settle) var(--ease), visibility 0s;
}

.topbar-in {
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: 0 max(var(--space-3), 2vw);
}

.topbar-title {
  margin: 0;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  font: 400 var(--size-bar-title)/1.2 var(--font-display);
  letter-spacing: var(--track-display);
  text-transform: uppercase;
}

.topbar-nav {
  flex: none;
  display: flex;
  gap: var(--space-4);
  font: 600 var(--size-eyebrow)/1 var(--font-ui);
  letter-spacing: var(--track-eyebrow);
  text-transform: uppercase;
}

.topbar-nav a { text-decoration: none; }

@media (max-width: 640px) { /* yardstick-allow-inline: a media query cannot read a custom property */
  /* Room for one link beside the title: Writing where there is one, else home. */
  .topbar-nav a:first-child:not(:last-child) { display: none; }
}

.ruler {
  position: absolute;
  left: 0;
  right: 0;
  top: 100%;
  height: var(--ruler-h);
  margin-top: var(--rule);
  pointer-events: none;
  background: color-mix(in srgb, var(--paper) 88%, transparent);
  -webkit-backdrop-filter: blur(var(--space-1));
  backdrop-filter: blur(var(--space-1));
}

/* Three sets of ticks, as on a rule: every eighth, every fifth of those taller,
   every tenth tallest. Faint across the whole width; in full ink up to where
   the reader has got to. */
.ruler::before,
.ruler-read {
  position: absolute;
  inset: 0;
  background:
    linear-gradient(to right, var(--ink) var(--rule-thin), transparent var(--rule-thin)) 0 0 / var(--tick-major) var(--tick-major-h) repeat-x,
    linear-gradient(to right, var(--ink) var(--rule-thin), transparent var(--rule-thin)) 0 0 / var(--tick-mid) var(--tick-mid-h) repeat-x,
    linear-gradient(to right, var(--ink) var(--rule-thin), transparent var(--rule-thin)) 0 0 / var(--tick) var(--tick-h) repeat-x;
}

.ruler::before { content: ""; opacity: 0.22; }

.ruler-read { clip-path: inset(0 calc(100% - var(--read, 0) * 100%) 0 0); }

@supports (animation-timeline: scroll()) {
  .ruler-read {
    animation: read linear both;
    animation-timeline: scroll(root);
  }
}

@keyframes read {
  from { clip-path: inset(0 100% 0 0); }
  to   { clip-path: inset(0 0 0 0); }
}

/* ===========================================================================
   A post
   =========================================================================== */

main { padding-top: var(--space-4); padding-bottom: var(--space-6); }

/* Text keeps to the reading measure; a figure takes the column's full width. */
.prose > :not(.print) { max-width: var(--measure); }

.prose > :first-child { margin-top: 0; }
.prose p, .prose ul, .prose ol, .prose blockquote { margin: 0 0 var(--space-3); }
.prose p { text-wrap: pretty; }
.prose li { margin-bottom: var(--space-1); }

/* Tables (the privacy policy's provider list): ruled rows, nothing boxed. */
.prose table { width: 100%; border-collapse: collapse; margin: 0 0 var(--space-4); }
.prose th,
.prose td { text-align: left; vertical-align: top; padding: var(--space-1) var(--space-3) var(--space-1) 0; border-bottom: var(--rule-thin) solid var(--ink-rule); }
.prose th { font-family: var(--font-ui); font-size: 0.8em; letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-soft); border-bottom: var(--rule) solid var(--ink); }

.prose h2,
.prose h3 {
  margin: var(--space-5) 0 var(--space-2);
  font-family: var(--font-display);
  font-weight: 400;
  letter-spacing: var(--track-display);
  text-transform: uppercase;
  line-height: 1.05;
  text-wrap: balance;
}

.prose h2 { font-size: var(--size-h2); }
.prose h3 { font-size: var(--size-h3); }

.prose blockquote {
  padding-left: var(--space-3);
  border-left: var(--rule) solid var(--stock);
  font-style: italic;
}

.prose code {
  font-size: 0.9em;
  background: var(--ink-faint);
  padding: 0 var(--space-1);
}

.prose pre {
  overflow-x: auto;
  padding: var(--space-3);
  background: var(--ink-faint);
  font-size: var(--size-small);
}

.prose pre code { background: none; padding: 0; }

.prose img {
  display: block;
  max-width: 100%;
  height: auto;
}

.prose hr { border: 0; border-top: var(--rule-thin) solid var(--ink); margin: var(--space-5) 0; }

/* A figure takes the sheet's full width rather than the reading measure, so a
   screenshot is shown at a size its detail can be read at. */
.print { margin: var(--space-5) 0; }

.zoom { display: block; cursor: zoom-in; }
.zoom:hover { opacity: 1; }

.print img {
  width: 100%;
  border: var(--rule-thin) solid var(--ink-rule);
  box-shadow: var(--print-shadow);
  transition: transform var(--settle) var(--ease), box-shadow var(--settle) var(--ease);
}

.zoom:hover img { transform: translateY(calc(-1 * var(--space-1) / 2)); box-shadow: var(--print-shadow-lift); }

.print figcaption {
  max-width: var(--measure);
  margin-top: var(--space-2);
  padding-left: var(--space-2);
  border-left: var(--rule) solid var(--stock);
  font: 400 var(--size-small)/1.45 var(--font-ui);
  color: var(--ink-soft);
}

.back {
  max-width: var(--measure);
  margin: var(--space-6) 0 0;
  font: 600 var(--size-eyebrow)/1 var(--font-ui);
  letter-spacing: var(--track-eyebrow);
  text-transform: uppercase;
}

/* Figures and cards rise into place as they scroll into view. Over a set
   distance of entering rather than a share of their height: a tall card on a
   phone would otherwise still be part-way in when the page opens on it. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .print,
    .card {
      animation: rise linear both;
      animation-timeline: view();
      animation-range: entry 0% entry var(--reveal);
    }
  }
}

@keyframes rise { from { opacity: 0; transform: translateY(var(--space-5)); } }

/* --- the enlarged picture --- */

.lightbox {
  width: 100vw;
  max-width: none;
  height: 100dvh;
  max-height: none;
  margin: 0;
  padding: var(--gutter);
  border: 0;
  background: none;
  cursor: zoom-out;
}

.lightbox[open] { display: grid; place-items: center; }
/* The whole dialog is the close control, so a ring round the window says
   nothing; Escape and a click anywhere both close it. */
.lightbox:focus-visible { outline: none; }

.lightbox::backdrop {
  background: var(--veil);
  -webkit-backdrop-filter: blur(var(--space-1));
  backdrop-filter: blur(var(--space-1));
}

.lightbox img {
  display: block;
  max-width: 100%;
  max-height: calc(100dvh - 2 * var(--gutter));
  width: auto;
  height: auto;
  box-shadow: var(--lightbox-shadow);
}

/* ===========================================================================
   The index
   =========================================================================== */

.posts { list-style: none; margin: 0; padding: 0; }

.card {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr) var(--thumb-w);
  gap: var(--space-5);
  align-items: center;
  padding: var(--space-5) 0;
}

.card + .card { border-top: var(--rule-thin) solid var(--ink-rule); }
.card-plain { grid-template-columns: minmax(0, 1fr); }

.card h2 {
  margin: 0 0 var(--space-2);
  font: 400 var(--size-list-title)/1.02 var(--font-display);
  letter-spacing: var(--track-display);
  text-transform: uppercase;
  text-wrap: balance;
}

/* An underline in the stock that draws across the title, every line of it. */
.card h2 a {
  text-decoration: none;
  background: linear-gradient(var(--stock), var(--stock)) 0 100% / 0 var(--rule) no-repeat;
  -webkit-box-decoration-break: clone;
  box-decoration-break: clone;
  transition: background-size var(--settle) var(--ease);
}

.card h2 a::after { content: ""; position: absolute; inset: 0; }
.card h2 a:hover { opacity: 1; }
.card:hover h2 a,
.card h2 a:focus-visible { background-size: 100% var(--rule); }
.card h2 a:focus-visible { outline: none; }
.card:has(a:focus-visible) { outline: var(--rule) solid var(--ink); outline-offset: var(--space-2); }

.card-text > p:last-child { margin: 0; max-width: var(--measure); }

.card-print {
  aspect-ratio: 4 / 3;
  overflow: hidden;
  border: var(--rule-thin) solid var(--ink-rule);
  box-shadow: var(--print-shadow);
}

.card-print img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: top left;
  transition: transform var(--settle) var(--ease);
}

.card:hover .card-print img { transform: scale(1.04); }

@media (max-width: 760px) { /* yardstick-allow-inline: a media query cannot read a custom property */
  .card { grid-template-columns: minmax(0, 1fr); gap: var(--space-3); }
  .card-print { order: -1; }
}

.empty { color: var(--ink-soft); }

/* ===========================================================================
   The foot: the end of the home page's about panel
   =========================================================================== */

.colophon {
  position: relative;
  background-color: var(--stock);
  background-image: var(--paper-img, none);
  background-size: var(--paper-size);
  border-top: var(--rule) solid var(--ink);
  box-shadow: var(--sheet-shadow);
  font: 400 var(--size-small)/1.6 var(--font-ui);
}

.colophon-in { padding-top: var(--space-6); padding-bottom: var(--space-7); }

.colophon .eyebrow { color: var(--ink); opacity: 0.72; }

.colophon p:last-child { margin: var(--space-5) 0 0; max-width: var(--measure); }

.inklines { display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-2); }
.inklines a { display: block; line-height: 0; }
.inklines img { display: block; width: auto; height: var(--ink-line); }
/* The phone line is set larger than the email in its own file, as on the
   home page; keeping that ratio is what makes them read as one printing. */
.inklines a:first-child img { height: var(--ink-line-lead); }

/* ===========================================================================
   Between pages

   Moving from the index to a post, the post's title travels from its card up
   to where it is printed on the sky, and the sky stays put while the words
   change under it. The sky is named on every page, the home page's too, and
   laid out the same way on each, so its group holds still while each page's
   tear dissolves into the next; the masthead is named so that it stays above
   it. Each page's copy and body are named for
   their own page, so they never morph into each other: the old words leave
   quickly, then the new ones arrive. Browsers without cross-document view
   transitions just load the page.
   =========================================================================== */

.hero-sky { view-transition-name: sky; }
.masthead { view-transition-name: masthead; }
/* Post titles carry a per-post name; this class lets one rule address them all. */
.hero .title,
.card h2 { view-transition-class: words; }
.hero-index .hero-copy { view-transition-name: index-copy; }
.hero-post .hero-copy { view-transition-name: post-copy; }
main.index { view-transition-name: index-body; }
main.post { view-transition-name: post-body; }

/* While a picture is enlarged or put back (blog.js), only the picture moves.
   !important to outrank the title's inline name. */
.zooming .hero-sky,
.zooming .masthead,
.zooming .hero-copy,
.zooming main,
.zooming .title,
.zooming .card h2 { view-transition-name: none !important; }

@view-transition { navigation: auto; }

@media (prefers-reduced-motion: reduce) {
  @view-transition { navigation: none; }
}

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

::view-transition-old(index-copy),
::view-transition-old(post-copy),
::view-transition-old(index-body),
::view-transition-old(post-body) {
  animation: leave var(--quick) var(--ease) both;
}

::view-transition-new(index-copy),
::view-transition-new(post-copy),
::view-transition-new(index-body),
::view-transition-new(post-body) {
  animation: arrive var(--settle) var(--ease) var(--quick) both;
}

/* Arriving from the home page. Its sky is the same picture as this one, named
   the same, so it stays where it is and settles into the hero, its lower part
   giving way to the tear. The about panel falls away off it, clipped by the
   home page to what was on screen (pageswap in script.js), so the fall shows
   only what the visitor was looking at. The banner
   goes up out of the way, and the rest of the home page's furniture fades.
   A post's title has nowhere to travel from on the home page, so it waits
   for the sky to clear and arrives with the other words. */
::view-transition-old(home-about) { animation: fall var(--fall) var(--fall-ease) both; }
::view-transition-new(*.words):only-child { animation: arrive var(--settle) var(--ease) var(--quick) both; }

/* The blog's head marks a transition from the home page `from-home` (see
   render.mjs). Then the whole sky stays on screen while the panel falls, and
   only after it does the sky below the tear dissolve away and the blog's
   words arrive. The new sky is drawn over the old with normal blending: the
   two are the same picture above the tear, so nothing there changes. */
html:active-view-transition-type(from-home)::view-transition-new(sky) { animation: none; mix-blend-mode: normal; }
html:active-view-transition-type(from-home)::view-transition-old(sky) {
  animation: leave var(--settle) var(--ease) var(--fall) both;
  mix-blend-mode: normal;
}
html:active-view-transition-type(from-home)::view-transition-new(masthead),
html:active-view-transition-type(from-home)::view-transition-new(index-copy),
html:active-view-transition-type(from-home)::view-transition-new(post-copy),
html:active-view-transition-type(from-home)::view-transition-new(index-body),
html:active-view-transition-type(from-home)::view-transition-new(post-body),
html:active-view-transition-type(from-home)::view-transition-new(*.words):only-child {
  animation: arrive var(--settle) var(--ease) var(--fall) both;
}
::view-transition-old(home-bar) { animation: rise-off var(--settle) var(--ease) both; }
::view-transition-old(home-hud),
::view-transition-old(home-art),
::view-transition-old(home-hero) { animation: leave var(--quick) var(--ease) both; }

/* Everything left unnamed, on either page, is gone quickly, so nothing of
   the old page shows through under the sky while it settles. */
::view-transition-old(root) { animation-duration: var(--quick); }

@keyframes fall { to { transform: translateY(100vh); } }
@keyframes rise-off { to { transform: translateY(-100%); opacity: 0; } }
@keyframes leave { to { opacity: 0; } }
@keyframes arrive { from { opacity: 0; transform: translateY(var(--space-3)); } }
