/*
Theme Name: The Development Curve
Theme URI: https://thedevelopmentcurve.com/
Author: Marc Gené Nogales
Description: A single-author essay theme. Marginal citations, a reading indicator drawn along the site's own emblem, alternating cover rows on the homepage, and an archive with live title search. Four page types: homepage, archive, article, about.
Version: 1.5.2
Requires at least: 6.4
Tested up to: 6.7
Requires PHP: 7.4
License: GPL-2.0-or-later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Text Domain: development-curve
Tags: blog, one-column, editor-style, custom-colors, translation-ready
*/

/* ---------------------------------------------------------------------------
   Everything below is ported from the visual proof. Run tools/validate.py
   against this file before committing: it checks for script inside the
   stylesheet, brace and comment balance, dead rules and stray colours.
   --------------------------------------------------------------------------- */

/* ——— font loading: metric-matched fallbacks so the webfont swap is
         dimensionally neutral. Percentages are estimates — re-measure with a
         fallback-metrics generator before production. ——— */
  @font-face{
    font-family:"Serif Fallback";
    src:local("Georgia"),local("Charter"),local("Times New Roman");
    size-adjust:97%;ascent-override:92%;descent-override:24%;line-gap-override:0%;
  }
  @font-face{
    font-family:"Sans Fallback";
    src:local("Helvetica Neue"),local("Arial"),local("Segoe UI");
    size-adjust:104%;ascent-override:90%;descent-override:22%;line-gap-override:0%;
  }

  :root{
    --paper:#F7F5F0;
    --ink:#1C1A16;
    --meta:#6B6558;
    --rule:#DFD9CB;
    --rule-ui:#8F8878;
    --accent:#7A6A45;
    --accent-2:#8E6152;      /* quoted-document rule only */
    --ink-soft:#3A362E;      /* secondary prose */
    --paper-deep:#F1EDE3;    /* the lead block's ground */
    --photo-ground:#E9E3D4;  /* the placeholder ground, before an image loads */
    /* One switch for the whole photographic system. The duotone version is
       kept below if it is ever wanted back:
       --photo-filter:grayscale(1) contrast(1.08) brightness(1.03); */
    --photo-filter:saturate(.96) contrast(1.03);
    --placeholder-ink:#665F52;


    --serif:"Source Serif 4","Serif Fallback",Georgia,Charter,"Times New Roman",serif;
    --sans:Inter,"Sans Fallback",ui-sans-serif,system-ui,-apple-system,Helvetica,Arial,sans-serif;

    --measure:34rem;
    --margin-col:14rem;
    --gutter:4rem;
    --col:calc(var(--measure) + var(--gutter) + var(--margin-col));
    --pad:2.5rem;
    --view-top:1.5rem;

    /* 8px scale. Every vertical gap on the page is one of these, so the rhythm
       is decided once instead of per element. Section gaps are deliberately
       much larger than gaps inside a section — that contrast is what makes
       whitespace read as structure rather than slack. */
    --s1:.5rem;   /*  8 */
    --s2:1rem;    /* 16 */
    --s3:1.5rem;  /* 24 */
    --s4:2rem;    /* 32 */
    --s5:3rem;    /* 48 */
    --s6:4rem;    /* 64 */
    --s7:6rem;    /* 96 */

    --scaffold-h:0px;
    --bar-h:3rem;              /* measured at runtime, for scroll-padding */
    /* One motion vocabulary: three durations, one easing curve.
       --slow, --rise and the photograph transitions are for things arriving on
       their own as you scroll, and they are deliberately long.
       --fast is different: it answers a pointer. Interaction feedback has a
       ceiling of roughly 400ms before it reads as lag rather than grace, so it
       is raised rather than doubled. Change it here if you want it slower. */
    /* Emphasised decelerate: arrives quickly, settles slowly. An entrance on a
       linear or ease-in curve reads as mechanical however long it runs. */
    --ease:cubic-bezier(.05,.7,.1,1);
    --ease-exit:cubic-bezier(.3,0,.8,.15);
    --fast:420ms;    /* hover states, colour changes — see the note below */
    --mid:760ms;     /* panels, reveals of small things */
    --slow:1800ms;   /* whole blocks entering */
    --rise:48px;     /* how far a block travels in. Distance is what makes a
                        reveal legible — 6px was invisible at any duration. */
  }

  *{box-sizing:border-box}
  html{
    -webkit-text-size-adjust:100%;
    scroll-behavior:smooth;
    overflow-x:clip;                      /* required for the full-bleed dividers */
    scroll-padding-top:calc(var(--scaffold-h) + var(--bar-h) + 1rem);
  }
  body{
    margin:0;padding-top:var(--scaffold-h);
    background:var(--paper);color:var(--ink);
    font-family:var(--serif);font-optical-sizing:auto;
    font-size:19px;line-height:1.65;text-rendering:optimizeLegibility;
  }
  a{color:inherit}
  img{max-width:100%;height:auto}
  figure{margin:0}
  h1,h2,h3{font-weight:400;margin:0;line-height:1.18;text-wrap:balance}
  p{margin:0;text-wrap:pretty}
  :focus-visible{outline:2px solid var(--accent);outline-offset:3px}

  .visually-hidden{
    position:absolute;width:1px;height:1px;margin:-1px;padding:0;
    overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0;
  }
  .skip{
    position:absolute;left:.75rem;top:calc(var(--scaffold-h) + .5rem);z-index:50;
    transform:translateY(-200%);
    font-family:var(--sans);font-size:.8125rem;
    background:var(--paper);color:var(--ink);
    border:1px solid var(--accent);padding:.5rem .8rem;text-decoration:none;
  }
  .skip:focus{transform:none}


  /* ——— one column definition for the header and the page body ——— */
  .col,.shell{width:min(100% - var(--pad), var(--col));margin-inline:auto}

  /* ——— the curve: header rule and section dividers only ———
     The line rises left to right, so it meets the bottom of its box only at the
     left end; the wedge of space beneath it towards the right is inherent to a
     rising line used as a rule, and is kept by decision. */
  .curveline{display:block;width:100%;height:24px;overflow:visible}
  .curveline path{fill:none;stroke-width:1.25;stroke-linecap:round;stroke-linejoin:round}
  .curveline .track{stroke:var(--rule)}

  /* Reading indicator.
     The fill is a copy of the curve revealed by clip-path, NOT by
     stroke-dashoffset: dashoffset measures distance along the path, so the
     steep segments would consume more of the fill per horizontal pixel than the
     shallow ones and the indicator would drift out of step with the scroll.
     Clipping keeps the reveal edge a true vertical line at the exact horizontal
     fraction. The filled stroke is also heavier than the track, so the signal
     does not rely on hue alone. */
  .rule-wrap{position:relative}
  .rule-fill{
    position:absolute;inset:0;pointer-events:none;display:block;
    clip-path:inset(0 100% 0 0);
    /* No CSS transition here. Two drivers feed this element, and a transition
       restarts on every new value — against the per-frame scroll driver that
       reads as rubber-banding, and against the observer's steps it lags behind
       the scroll. The smoothing is done in script instead, by easing the drawn
       value toward the target every frame. */
  }
  .rule-fill.is-tracking{will-change:clip-path}
  .rule-fill svg{position:absolute;inset:0;width:100%;height:100%}
  .rule-fill path{stroke:var(--accent);stroke-width:1.75}

  /* ——— header ———
     ONE object: wordmark, slogan and nav share a single baseline row, with the
     curve rule as its bottom edge. It is position:sticky at a CONSTANT height —
     nothing collapses or shrinks on scroll, so the stuck state is identical to
     the resting state and the flow space it reserves never changes. That is what
     stops the stuck header reading as cramped, and it removes the duplicate
     site-name-and-nav that the two-block version needed. */
  .siteheader{
    position:sticky;top:var(--scaffold-h);z-index:30;
    background:var(--paper);
    margin:0;padding:1.05rem 0 0;border:0;
  }
  .siteheader .col{
    display:flex;align-items:baseline;gap:1.1rem;
    line-height:1.25;padding-bottom:.62rem;
  }
  .wordmark{font-size:1.7rem;letter-spacing:-0.01em;white-space:nowrap}
  .wordmark a{text-decoration:none}
  .slogan{
    font-family:var(--sans);font-size:.8125rem;color:var(--meta);
    letter-spacing:.01em;white-space:nowrap;
  }

  /* wp_nav_menu wraps every item in an <li>; display:contents makes the
     anchors the flex children so the row behaves as designed. */
  .nav ul,.nav li{list-style:none;margin:0;padding:0;display:contents}
  .nav{font-family:var(--sans);font-size:.8125rem;display:flex;gap:1.4rem;margin-left:auto;flex:none}
  .nav a{position:relative;text-decoration:none;color:var(--meta);padding-bottom:3px;transition:color var(--fast) ease-in-out}
  .nav a::after{
    content:"";position:absolute;left:0;right:0;bottom:0;height:1px;background:currentColor;
    transform:scaleX(0);transform-origin:left center;
    transition:transform var(--fast) var(--ease);
  }
  .nav a:hover,.nav a:focus-visible{color:var(--ink)}
  .nav a:hover::after,.nav a:focus-visible::after{transform:scaleX(1)}

  main{padding-top:var(--view-top);margin:0}
  main > :first-child{margin-top:0}

  /* ——— section kickers ——— */
  .kicker{
    scroll-margin-top:calc(var(--scaffold-h) + var(--bar-h) + 1rem);
    font-family:var(--sans);font-size:.8125rem;font-weight:500;
    font-variant-caps:all-small-caps;letter-spacing:.085em;
    color:var(--accent);margin:var(--s7) 0 var(--s2);
  }
  .divider + .kicker{margin-top:0}
  /* About, Archive and search results: a real page title, not a small-caps
     section label. At .8125rem the reader could not tell which page they were on. */
  .page-title{
    font-size:1.9rem;letter-spacing:-0.012em;margin:0 0 var(--s2);
  }
  .page-title + .listnote{margin-top:calc(-1 * var(--s1))}

  .listnote{font-family:var(--sans);font-size:.75rem;color:var(--meta);margin:0 0 var(--s3)}
  .listnote--after{margin:var(--s3) 0 0}
  .listnote a{color:var(--meta)}

  /* ——— photographic system: one grade, ratio varies by use ——— */
  .photo{
    position:relative;min-height:0;overflow:hidden;
    border:1px solid var(--rule);background:var(--photo-ground);
    display:flex;align-items:center;justify-content:center;text-align:center;
    font-family:var(--sans);font-size:.6875rem;line-height:1.4;
    color:var(--placeholder-ink);padding:.75rem;
  }
  .photo img{
    display:block;width:100%;height:100%;object-fit:cover;
    filter:var(--photo-filter);
  }
  /* Once a real photograph is in the box, the padding and the tinted ground
     stop being scaffolding and start being a beige frame around a picture that
     no longer fills its own aspect ratio. */
  .photo:has(img){padding:0;background:transparent}
  /* With a real image in it, the box is a frame, not a mount: the padding and
     the placeholder ground go away and the picture fills it. */
  .photo:has(img){padding:0;background:none}
  /* Images inside written content get the same light grade. */
  .about-body img,.essay-body img{filter:var(--photo-filter)}
  .portrait{aspect-ratio:4/5}
  /* A cut-out on a white ground. Multiply blending turns that white into the
     paper exactly, with no mask and so no ragged edge, and the figure stands
     on the bottom of its box rather than being cropped to fill it. */
  /* A cut-out with a real alpha channel: no frame, no ground, and no blend
     mode — the earlier multiply was working around a background that is now
     genuinely absent. */
  .portrait--cutout{border:0;background:none;aspect-ratio:auto;height:100%}
  .portrait--cutout img{
    object-fit:contain;object-position:bottom;filter:none;
  }
  .featured-photo{aspect-ratio:3/2}
  .essay-figure{aspect-ratio:16/9;margin-bottom:.6rem}
  .figcaption{font-family:var(--sans);font-size:.6875rem;color:var(--meta);margin-bottom:1.6rem}

  /* ——— homepage introduction ———
     Three grid children (figure, name, body) so the phone layout can put a small
     avatar beside the name without moving anything in the DOM. */
  .intro{
    display:grid;
    grid-template-columns:12.5rem 1fr;
    grid-template-areas:"fig name" "fig body";
    column-gap:2.5rem;row-gap:.85rem;
    /* stretch, so the figure column is as tall as the text beside it */
    align-items:stretch;margin:0;
  }
  .intro-name,.intro-body{align-self:start}
  /* the figure fills its column and the picture sits on the floor of it, which
     lines the bottom of the portrait up with the last line of the text */
  .intro-figure{
    grid-area:fig;min-height:0;
    display:flex;flex-direction:column;justify-content:flex-end;
  }
  .intro-name{grid-area:name;font-family:var(--sans);font-size:.8125rem;color:var(--meta);text-wrap:balance}
  .intro-body{grid-area:body;max-width:var(--measure)}
  .intro-lede{font-size:1.3125rem;line-height:1.46;letter-spacing:-0.005em}
  .intro-more{font-family:var(--sans);font-size:.8125rem;margin-top:var(--s2)}
  .intro-more a{color:var(--accent);text-underline-offset:2px}
  .intro-body p + p{font-size:1.0625rem;margin-top:1.05rem}

  /* ——— alternating cover rows ———
     Full column width rather than the reading measure, so the images have room
     to matter. The photo swaps side row by row, starting opposite the featured
     lead above so the page reads as one continuous zigzag. Whitespace, not
     rules, separates the rows. */
  .pieces{display:grid;gap:var(--s6);max-width:none}
  .piece{display:grid;grid-template-columns:1fr 1fr;gap:var(--s4);align-items:center}
  .piece:nth-child(odd) .piece-photo{order:2}
  /* A piece with no cover: one full-width text column, so the template can
     never render an empty image box. The row simply skips a beat in the
     zigzag rather than leaving a hole. */
  .piece--textonly{grid-template-columns:1fr}
  .piece--textonly .piece-text{max-width:var(--measure)}
  .piece--textonly .piece-title{font-size:1.4rem}
  /* a second run of rows starts on the opposite side, so the zigzag carries on
     across the section break instead of restarting */
  .pieces--flip .piece:nth-child(odd) .piece-photo{order:0}
  .pieces--flip .piece:nth-child(even) .piece-photo{order:2}
  .piece-photo{aspect-ratio:3/2}
  .piece-photo img{transition:transform 2200ms var(--ease)}
  .piece:hover .piece-photo img{transform:scale(1.04)}
  .piece-text{min-width:0}
  .piece-meta{font-family:var(--sans);font-size:.75rem;color:var(--meta)}
  .piece-title{font-size:1.55rem;letter-spacing:-0.01em;margin-top:var(--s1)}
  .piece-dek{
    font-family:var(--sans);font-size:.9375rem;line-height:1.5;
    color:var(--meta);margin-top:var(--s2);max-width:26rem;
  }

  /* ——— featured lead ——— */
  /* a half-step darker than the paper, so the lead reads as set apart without
     needing a border or a label to say so */
  .featured{
    display:grid;grid-template-columns:1fr;gap:2rem;align-items:start;
    background:var(--paper-deep);padding:var(--s4);
  }
  .featured:has(.featured-photo){grid-template-columns:62% 1fr}
  .featured-text{align-self:center}
  .featured-title{font-size:2.15rem;letter-spacing:-0.014em}

  .featured-dek{font-family:var(--sans);font-size:.9375rem;line-height:1.5;color:var(--meta);margin-top:.85rem}
  .featured-meta{display:flex;flex-direction:column;gap:.55rem;align-items:flex-start;margin-top:1.15rem}
  .featured-date{font-family:var(--sans);font-size:.75rem;color:var(--meta)}

  /* ——— full-bleed divider ——— */
  /* The rule carries the section name. A label set into a hairline is an old
     editorial device and it does two jobs at once: it separates, and it says
     what follows. The curve that used to sit here said neither. */
  .divider{
    width:100vw;margin-inline:calc(50% - 50vw);margin-block:var(--s5);
    display:flex;align-items:center;gap:1.5rem;
  }
  .divider .line{flex:1;height:1px;background:var(--rule)}
  .divider .kicker{
    margin:0;flex:none;white-space:nowrap;
    font-size:1.0625rem;letter-spacing:.12em;
  }

  /* ——— lists: typography only. Two independent links per entry — the title
         goes to the piece, the thread tag to the thread. ——— */
  .entries{max-width:var(--measure);position:relative}

  /* ——— hover peek ———
     An enrichment, never the only route: the dek is inline on every entry at
     every width, and the panel adds what the list does not show — reading
     length, where the piece sits in its thread, and its opening line.
     Built from the research rather than by hand:
       · 200ms intent delay before showing. Baymard puts 300–500ms on hover menus
         that obscure content; an unobtrusive overlay in empty margin sits at the
         200–300ms end.
       · asymmetric timing — 340ms decelerate in, 220ms accelerate out. Material:
         exits are shorter because they need less attention than the next task.
       · 260ms grace before hiding, and the panel is itself hoverable, so the
         pointer can travel onto it. WCAG 2.1 SC 1.4.13 (hoverable, persistent).
       · Escape dismisses it without moving pointer or focus (dismissable).
       · it stays put rather than following the cursor, and glides between rows
         on the standard curve when it is already open. */
  /* Titles only on the homepage — but ONLY where a real pointer exists to
     reveal the summary. Touch and narrow viewports keep the dek inline, since
     there is no hover there to recover it with. */
  /* The long list is the one that benefits from titles-only scanning, so the
     summary-on-hover lives on the Archive — and only where a real pointer can
     reveal it. The homepage's short lists show their deks inline. */
  @media (min-width:981px) and (hover:hover) and (pointer:fine){
    #archive-list .entry-dek{display:none}
  }

  .peek{
    position:absolute;
    left:calc(100% + 1.5rem);top:0;
    width:calc(var(--margin-col) + var(--gutter) - 1.5rem);
    opacity:0;pointer-events:none;
    transition:opacity 220ms var(--ease-exit),
               translate 220ms var(--ease-exit);
    translate:0 -8px;
  }
  .peek.is-open{
    opacity:1;pointer-events:auto;translate:0 0;
    transition:opacity var(--mid) var(--ease),
               translate var(--mid) var(--ease);
  }
  .peek.is-moving{transition:top 1040ms var(--ease), opacity var(--mid) var(--ease)}
  .peek-open{
    font-family:var(--sans);font-size:.875rem;line-height:1.5;
    color:var(--meta);padding-left:.8rem;border-left:1px solid var(--accent);
  }
  .entry{display:block;padding:var(--s3) 0;border-top:1px solid transparent}
  .entry + .entry{border-top-color:var(--rule)}
  .entry-date{display:block;font-family:var(--sans);font-size:.75rem;color:var(--meta);margin-bottom:.45rem}
  .entry-title{font-size:1.4rem;letter-spacing:-0.008em}
  .entry-title a:visited{color:var(--accent)}
  .entry-read{
    font-family:var(--sans);font-size:.6875rem;font-weight:400;
    color:var(--meta);white-space:nowrap;vertical-align:.12em;margin-left:.2rem;
  }
  .entry-dek{font-family:var(--sans);font-size:.875rem;line-height:1.5;color:var(--meta);margin-top:.5rem;max-width:32rem}

  /* ——— archive filter ——— */
  .filter-wrap{display:block;margin-bottom:var(--s3)}
  .filter{display:flex;gap:.5rem;max-width:24rem;margin-bottom:.55rem}
  .filter-hint{font-family:var(--sans);font-size:.75rem;color:var(--meta);max-width:28rem}
  /* results pages mark the term in the dek too */
  .entry-dek mark{background:none;color:var(--ink);font-weight:500}
  .filter input{
    flex:1;min-width:0;font:inherit;font-family:var(--sans);font-size:.875rem;
    padding:.5rem .6rem;border:1px solid var(--rule-ui);background:transparent;
    color:var(--ink);border-radius:0;
  }
  .filter input::placeholder{color:var(--meta)}
  .filter input::-webkit-search-cancel-button{display:none}
  .filter-clear{
    font:inherit;font-family:var(--sans);font-size:.8125rem;cursor:pointer;
    padding:.5rem .8rem;border:1px solid var(--rule-ui);background:transparent;
    color:var(--meta);border-radius:0;
    transition:color var(--fast) var(--ease), border-color var(--fast) var(--ease);
  }
  .filter-clear:hover,.filter-clear:focus-visible{color:var(--ink);border-color:var(--ink)}
  .filter-count{font-family:var(--sans);font-size:.75rem;color:var(--meta);margin-bottom:1.6rem}
  .filter-empty{
    font-family:var(--sans);font-size:.875rem;color:var(--meta);
    padding:1.6rem 0;border-top:1px solid var(--rule);max-width:var(--measure);
  }
  /* while filtering, the matched text is the only part at full strength */
  .entry-title mark{background:none;color:var(--ink);font-weight:600}
  .is-filtering .entry-title a{color:var(--meta)}
  .is-filtering .entry-title a:hover{color:var(--ink)}

  /* ——— about page ——— */
  /* the wide image sits across the full column, above the reading measure */
  .about-banner{max-width:none;margin:0 0 var(--s4)}
  .about-banner-photo{aspect-ratio:2.2/1}
  .about{display:grid;grid-template-columns:12.5rem 1fr;column-gap:2.5rem;align-items:start}
  .about--plain{grid-template-columns:1fr}
  /* On About the text runs to the same width as the photograph above it, so the
     two edges line up. Wider than the reading measure used for essays — that is
     the trade for the alignment. */
  .about-body{max-width:none}
  .about-lede{font-size:1.3125rem;line-height:1.46;letter-spacing:-0.005em}
  .about-body p + p{font-size:1.0625rem;margin-top:1.05rem}
  /* The line sits centred between two hairlines: its own rule above and the
     footer's below. The space above the text is its padding-top; the space
     below is the footer's own top margin, so both use the same step. */
  .about-contact{
    margin-top:var(--s5);padding-top:var(--s5);padding-bottom:0;
    border-top:1px solid var(--rule);
    font-family:var(--sans);font-size:.875rem;line-height:1.55;color:var(--meta);
    text-align:center;
  }
  .about-contact a{color:var(--accent);text-underline-offset:2px}

  .placeholder-inline{color:var(--meta)}

  /* ——— essay page ——— */
  .essay{max-width:var(--measure)}
  .essay-meta{font-family:var(--sans);font-size:.75rem;color:var(--meta)}
  .essay-title{font-size:2.05rem;letter-spacing:-0.014em;max-width:28rem;margin-top:.9rem}
  .essay-body{margin-top:2.4rem;font-size:1.1875rem;line-height:1.72}
  .essay-body p + p{margin-top:1.45rem}
  .close{max-width:var(--measure);margin-top:var(--s5)}

  .authorblock{
    display:grid;grid-template-columns:5.5rem 1fr;gap:1.25rem;align-items:start;
    padding-top:var(--s3);border-top:1px solid var(--rule);
  }
  /* no portrait: one column, so the bio is not squeezed into the image slot */
  .authorblock--plain{grid-template-columns:1fr}
  .authorblock .photo{padding:.4rem;font-size:.625rem}
  .authorblock-body{font-family:var(--sans);font-size:.8125rem;line-height:1.55;color:var(--meta)}
  .authorblock-name{font-family:var(--serif);font-size:1.0625rem;color:var(--ink);margin-bottom:.35rem}
  .authorblock-body a{color:var(--accent);text-underline-offset:2px}

  /* where the piece sits in the sequence — navigation, one line */
  .adjacent{
    margin-top:var(--s4);font-family:var(--sans);font-size:.8125rem;line-height:1.6;color:var(--meta);
  }
  .adjacent a{color:var(--meta);text-underline-offset:2px}
  .adjacent a:hover,.adjacent a:focus-visible{color:var(--accent)}

  /* ——— sidenotes ——— */
  .claim{
    border-bottom:1px solid color-mix(in srgb, var(--accent) 38%, transparent);
    cursor:default;
    /* a claim often runs over two or three lines; box-decoration-break keeps the
       rule consistent across the fragments */
    box-decoration-break:clone;-webkit-box-decoration-break:clone;
  }
  .claim.is-active{border-bottom-color:var(--accent);background:color-mix(in srgb, var(--accent) 7%, transparent)}
  .fn{
    font:inherit;background:none;border:0;padding:0 0 0 1px;margin:0;
    color:var(--accent);cursor:pointer;font-size:.68em;vertical-align:.5em;font-family:var(--sans);
  }
  .fn:hover{text-decoration:underline}
  .sidenote{
    float:right;clear:right;width:var(--margin-col);
    margin-right:calc(-1 * (var(--margin-col) + var(--gutter)));
    margin-bottom:1.3rem;padding-left:.7rem;
    border-left:1px solid transparent;
    font-family:var(--sans);font-size:.75rem;line-height:1.55;color:var(--meta);text-align:left;
    overflow-wrap:anywhere;
    transition:border-color var(--fast) ease-in-out, color var(--fast) ease-in-out;
  }
  .sidenote.is-linked{border-left-color:var(--accent);color:var(--ink-soft)}
  .sidenote .sn-num{color:var(--accent);margin-right:.3rem}
  .sn-stat{margin-bottom:.55rem;display:block}
  .sn-figure{font-family:var(--serif);font-size:1.55rem;line-height:1;color:var(--ink);letter-spacing:-0.01em;display:block}
  .sn-bar{height:5px;background:var(--rule);margin-top:.5rem;display:block}
  .sn-bar span{display:block;height:100%;background:var(--accent)}
  .sn-barlabel{margin-top:.35rem;font-size:.6875rem;display:block}
  .sn-quote{
    display:block;border:1px solid var(--rule);border-left:2px solid var(--accent-2);
    padding:.65rem .7rem;margin-bottom:.55rem;
    font-family:var(--serif);font-size:.875rem;line-height:1.45;color:var(--ink-soft);
  }
  .sn-source{display:block;margin-top:.4rem;font-family:var(--sans);font-size:.6875rem;color:var(--meta)}

  /* Jetpack, belt and braces: if the filters are ever bypassed, these are not
     part of the design. Sharing buttons are left visible. */
  #jp-relatedposts,.jp-relatedposts,
  .sharedaddy.sd-like,#jp-post-flair .sharedaddy.sd-like,
  .jetpack-likes-widget-wrapper{display:none!important}
  #jp-post-flair{margin-top:var(--s3)}

  /* ——— footer ——— */
  .footer{
    margin-top:var(--s5);padding:var(--s4) 0 var(--s5);border-top:1px solid var(--rule);
    display:grid;grid-template-columns:1fr 1fr;gap:3rem 2.5rem;
    font-family:var(--sans);font-size:.8125rem;color:var(--meta);
  }
  .lockup{max-width:17rem}
  /* Compact: the wordmark sits BELOW body size (15px against 19px) on one line,
     with the slogan smaller again beneath it. Footer identity, not a masthead. */
  .lockup-name{
    font-family:var(--serif);font-size:.9375rem;line-height:1.2;
    color:var(--ink);letter-spacing:-0.004em;
  }
  .lockup-slogan{font-size:.75rem;line-height:1.4;margin-top:.25rem}
  .footer h3{
    font-family:var(--sans);font-size:.8125rem;font-weight:500;
    font-variant-caps:all-small-caps;letter-spacing:.085em;
    color:var(--ink);margin-bottom:.8rem;
  }
  .lockup-social{margin-top:var(--s3)}
  .social{display:inline-flex;align-items:center;gap:.45rem;text-decoration:none;color:var(--meta)}
  .social:hover{color:var(--accent)}
  .social svg{width:15px;height:15px;fill:currentColor}
  .subscribe{display:flex;gap:.5rem;max-width:20rem;margin-top:.15rem}
  .subscribe input{
    flex:1;min-width:0;font:inherit;font-family:var(--sans);font-size:.8125rem;
    padding:.5rem .55rem;border:1px solid var(--rule-ui);background:transparent;color:var(--ink);border-radius:0;
  }
  .subscribe input::placeholder{color:var(--meta)}
  .subscribe button{
    font:inherit;font-family:var(--sans);font-size:.8125rem;cursor:pointer;
    padding:.5rem .8rem;border:1px solid var(--accent);background:transparent;color:var(--accent);border-radius:0;
    transition:background var(--fast) ease-in-out, color var(--fast) ease-in-out;
  }
  .subscribe button:hover{background:var(--accent);color:var(--paper)}
  .subscribe-note{margin-top:.55rem;font-size:.6875rem}
  .copyright{grid-column:1/-1;font-size:.6875rem;color:var(--meta)}

  /* The indicator is driven from one rAF-throttled scroll handler rather than a
     CSS scroll-timeline. A scroll-linked animation is cheaper, but it is also a
     CSS animation — which outranks inline styles and is switched off wholesale
     by prefers-reduced-motion, so the two routes could not coexist and the
     reduced-motion case lost the indicator altogether. One code path, every
     browser, every setting. */

  /* ——— the underline gesture: grows from the left, same as the nav ———
     Applied to headings-as-links only. Links inside prose keep a permanent
     underline, because removing it from running text costs more than the
     animation adds. */
  .grow-underline{
    text-decoration:none;
    background-image:linear-gradient(currentColor,currentColor);
    background-repeat:no-repeat;
    background-position:0 100%;
    background-size:0% 1px;
    transition:background-size var(--mid) var(--ease);
  }
  .grow-underline:hover,.grow-underline:focus-visible{background-size:100% 1px}

  /* photographs settle very slightly on hover — 2%, over half a second */
  .featured-photo img{transition:transform 2200ms var(--ease)}
  .featured:hover .featured-photo img{transform:scale(1.04)}

  /* the divider curve draws itself in, once, as it arrives */

  /* ——— scroll reveal ——— */
  .reveal{opacity:0;transform:translateY(var(--rise))}
  .reveal.is-in{
    opacity:1;transform:none;
    /* opacity lands before the movement does, the way a physical object settles
       after it has already arrived */
    transition:opacity 1500ms var(--ease), transform var(--slow) var(--ease);
    transition-delay:var(--d,0ms);
  }

  @media (prefers-reduced-motion:no-preference){
    ::view-transition-old(root),::view-transition-new(root){animation-duration:760ms;animation-timing-function:ease-in-out}
    ::view-transition-group(essay-title){animation-duration:1040ms;animation-timing-function:cubic-bezier(.05,.7,.1,1)}
  }

  /* ——— tablet and phone ——— */
  @media (max-width:980px){
    :root{--gutter:0rem;--margin-col:0rem}
    .siteheader{padding-top:.85rem}
    .intro,.about{grid-template-columns:9rem 1fr;column-gap:1.5rem}
    .about--plain{grid-template-columns:1fr}
    .intro-lede{font-size:1.1875rem}
    .featured:has(.featured-photo){grid-template-columns:1fr}
    /* stacked, and the alternation is switched off: on one column it would only
       reorder image and text unpredictably from row to row */
    .pieces{gap:var(--s5)}
    .piece{grid-template-columns:1fr;gap:var(--s2)}
    .piece:nth-child(odd) .piece-photo,
    .pieces--flip .piece:nth-child(even) .piece-photo{order:0}
    .piece-title{font-size:1.35rem}
    .piece-dek{max-width:none}
    .featured{gap:1.25rem}
    .featured-title{font-size:1.6rem}
    .featured-meta{flex-direction:row;align-items:center;gap:1rem;flex-wrap:wrap}
    .essay-figure{aspect-ratio:2/1}
    /* No margin column to put them in. They stay visible — a note you have to
       discover is a note most readers never read — but they are moved out of
       the sentence and gathered directly beneath the paragraph, so the prose is
       never interrupted mid-thought. */
    .sidenote{
      display:block;float:none;width:auto;max-height:none;opacity:1;overflow:visible;
      margin:0;padding:0 0 0 .9rem;
      border-left:1px solid var(--accent);
    }
    /* Endnotes, clearly separated from the prose above and given room between
       them — the previous version sat far too tight against the paragraph. */
    .notes-end{
      margin-top:var(--s6);padding-top:var(--s3);
      border-top:1px solid var(--rule);
      display:flex;flex-direction:column;gap:var(--s3);
    }
    .notes-end-label{
      font-family:var(--sans);font-size:.8125rem;font-weight:500;
      font-variant-caps:all-small-caps;letter-spacing:.11em;
      color:var(--accent);margin:0;
    }
    .notes-end .sidenote{margin:0}
    /* the marker is a reference, not a control, now that nothing is hidden */
    .fn{font-size:.72em;text-decoration:none;cursor:default;padding:0 .1rem}
    .essay-title{font-size:1.65rem}
    .wordmark{font-size:1.45rem}
    .essay-body{font-size:1.125rem}
    .footer{grid-template-columns:1fr;gap:2.25rem}
    .nav{font-size:.75rem;gap:1.1rem}
  }
  /* the row keeps name + nav together to the last; the slogan drops first */
  @media (max-width:760px){
    .slogan{display:none}
  }
  /* Very narrow phones. A size step keeps the row on one line down to about
     300px; flex-wrap is only a safety net — it wraps if the content genuinely
     does not fit, and does nothing at widths where it does. Forcing the nav to
     its own line here would wrap it at 375px, where there is room to spare. */
  @media (max-width:400px){
    .wordmark{font-size:1.15rem}
    .siteheader .col{flex-wrap:wrap;row-gap:.3rem}
    .siteheader .nav{font-size:.6875rem}
  }

  @media (max-width:560px){
    :root{--pad:1.75rem}
    body{font-size:18px}
    .wordmark{font-size:1.3rem}
    /* small avatar beside the name; full portrait already appears on every essay */
    .about{grid-template-columns:1fr;row-gap:1.25rem}
    .about-banner-photo{aspect-ratio:16/9}
    .essay-figure{aspect-ratio:3/2}
    .intro{
      /* the avatar column: 84px, up from 52px, with the gap scaled to match */
      grid-template-columns:5.25rem 1fr;
      grid-template-areas:"fig name" "body body";
      column-gap:1.15rem;row-gap:1.1rem;align-items:center;
    }
    .intro-figure .portrait{aspect-ratio:1/1}
    .intro-figure .photo{font-size:0}
    .authorblock{grid-template-columns:4.5rem 1fr;gap:1rem}
    .subscribe{flex-wrap:wrap}
    .subscribe input{flex:1 1 100%}
    .divider{margin-block:2.5rem}
    .kicker{margin-top:var(--s6);margin-bottom:var(--s2)}
  }

  /* Windows High Contrast and similar: system colours replace the palette, so
     anything that carried meaning in hue needs a second channel. */
  /* Print: the marginalia become numbered notes in the flow, the interface
     disappears, and every link's destination is written out. */
  @media print{
    :root{--measure:100%;--margin-col:0;--gutter:0}
    body{background:#fff;color:#000;font-size:11pt;padding-top:0}
  
    .divider,.subscribe,.subscribe-note,.social,.skip,.photo,.portrait-note{display:none!important}
    .siteheader{position:static;padding:0;margin-bottom:12pt;border-bottom:1pt solid #000}
    .wordmark{font-size:14pt}
    .slogan{font-size:9pt}
    main{padding-top:0}
    .essay-body{font-size:11pt;line-height:1.5}
    .sidenote{
      float:none;width:auto;margin:6pt 0;padding:0 0 0 10pt;
      max-height:none;opacity:1;overflow:visible;
      border-left:1pt solid #000;font-size:9pt;color:#000;
    }
    .fn{color:#000}
    .essay-body a[href^="http"]::after{content:" (" attr(href) ")";font-size:9pt;word-break:break-all}
    .adjacent,.authorblock,.footer{border-top:1pt solid #000;color:#000}
    .entry{break-inside:avoid}
    .essay-title,.entry-title{break-after:avoid}
  }

  @media (forced-colors:active){
    .curveline path{stroke:CanvasText}
    .rule-fill path{stroke:Highlight;stroke-width:2.5}
    .claim{border-bottom:2px solid Highlight}
    .sidenote.is-linked,.peek-open{border-left:3px solid Highlight}
    .fn{color:LinkText;text-decoration:underline}
    .entry-title a:visited{color:VisitedText}
    .photo{border:1px solid CanvasText}
    .sn-bar{border:1px solid CanvasText}
    .sn-bar span{background:Highlight;forced-color-adjust:none}
    .grow-underline{background-image:none;text-decoration:underline}
    .filter input,.filter-clear,.subscribe input,.subscribe button{border:1px solid CanvasText}
  }

  @media (prefers-reduced-motion:reduce){
    html{scroll-behavior:auto}
    /* Durations collapse rather than transitions being removed: state changes
       still complete, so anything awaiting transitionend cannot hang, and
       colour changes that carry meaning still land. */
    *{
      animation-duration:1ms!important;animation-delay:0ms!important;
      animation-iteration-count:1!important;
      transition-duration:1ms!important;transition-delay:0ms!important;
    }
    .reveal{opacity:1;transform:none}
    .featured:hover .featured-photo img{transform:none}
    /* the reading indicator still tracks: it is state, not decoration, and it
       is driven by script rather than by an animation, so nothing here stops it */
  }
