@font-face{font-family:'Source Sans 3';font-style:normal;font-weight:400 700;font-display:swap;src:url('/fonts/sourcesans-400_700-latinext.woff2') format('woff2');unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF}
@font-face{font-family:'Source Sans 3';font-style:normal;font-weight:400 700;font-display:swap;src:url('/fonts/sourcesans-400_700-latin.woff2') format('woff2');unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}
@font-face{font-family:'Inter';font-style:normal;font-weight:400 700;font-display:swap;src:url('/fonts/inter-normal-latinext.woff2') format('woff2');unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF}
@font-face{font-family:'Inter';font-style:normal;font-weight:400 700;font-display:swap;src:url('/fonts/inter-normal-latin.woff2') format('woff2');unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}
@font-face{font-family:'Source Code Pro';font-style:normal;font-weight:400;font-display:swap;src:url('/fonts/sourcecodepro-normal-latin.woff2') format('woff2');unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}

/* Light first, dark under prefers-color-scheme, the way dhh.dk does it: the
   page follows the reader's system setting and there is no toggle to remember.
   Every colour below is a token, so the two palettes are the only place a
   colour is chosen. Contrast was measured, not eyeballed: on the light paper
   body text is 14.9:1, muted 5.9:1, links 6.2:1; the dark palette is the one
   the site shipped with. */
:root {
  color-scheme: light dark;
  --ink: #1F2535;
  --paper: #FCFCFD;
  --brand: #7C49F5;
  --brand-strong: #6B35E8;
  --link: #6B35E8;
  --brand-soft: rgba(124, 73, 245, 0.35);
  --muted: #5B6376;
  --rule: rgba(31, 37, 53, 0.16);
  --code-bg: #F2F3F7;
}
@media (prefers-color-scheme: dark) {
  :root {
    --ink: #EDEDF2;
    --paper: #1F2535;
    --brand: #8755FF;
    --brand-strong: #7C49F5;
    --link: #A98BFF;
    --brand-soft: rgba(169, 139, 255, 0.35);
    --muted: #9BA1B3;
    --rule: rgba(237, 237, 242, 0.14);
    --code-bg: #1B2130;
  }
}
* { box-sizing: border-box; }
body {
  margin: 0;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  background: var(--paper);
  color: var(--ink);
  font-family: 'Source Sans 3', system-ui, sans-serif;
  font-size: 1.06rem;
  line-height: 1.7;
}
.skip { position: absolute; left: -9999px; }
.skip:focus {
  left: 1rem; top: 1rem; z-index: 10;
  background: var(--paper); color: var(--ink);
  padding: 0.5rem 0.9rem; border-radius: 3px;
}

.shell {
  display: grid;
  grid-template-columns: 11rem min(39rem, 100%);
  gap: 3.25rem;
  justify-content: center;
  padding: 5.5rem 1.5rem 0;
}
.column { min-width: 0; }

.side { position: sticky; top: 5.5rem; align-self: start; }
.portrait { display: block; line-height: 0; text-decoration: none; }
.portrait img {
  width: 150px; height: 150px;
  object-fit: cover;
  border-radius: 3px;
  display: block;
}
.side nav ul { list-style: none; margin: 1.7rem 0 0; padding: 0; }
.side nav li { margin: 0 0 0.4rem; position: relative; }
.side nav a {
  color: var(--muted);
  text-decoration: none;
  font-size: 0.93rem;
}
.side nav a:hover, .side nav a:focus-visible { color: var(--link); }
.side nav li { position: relative; }
.side nav a[aria-current] { color: var(--ink); }
.side nav a[aria-current]::before {
  content: "";
  position: absolute;
  left: -0.85rem;
  top: 50%;
  transform: translateY(-50%);
  width: 5px; height: 5px;
  border-radius: 50%;
  background: var(--brand);
}

@media (max-width: 62rem) {
  .shell {
    grid-template-columns: min(39rem, 100%);
    gap: 0;
    padding-top: 3rem;
  }
  .side {
    position: static;
    margin-bottom: 2.75rem;
    display: flex; align-items: center;
    flex-wrap: wrap;
    gap: 1rem 1.25rem;
  }
  .portrait { flex: 0 0 auto; }
  .portrait img { width: 88px; height: 88px; }
  /* 25rem is the nav's one-line width: it sits beside the portrait when that
     much room exists, and drops to its own line when it does not. */
  .side nav { min-width: 0; flex: 1 1 25rem; }
  /* row gap leaves room for the active dot hanging below its label */
  .side nav ul { margin: 0; display: flex; flex-wrap: wrap; gap: 0.6rem 1.15rem; }
  .side nav li { margin: 0; }
  /* bigger tap target without moving anything visually */
  .side nav a { display: inline-block; padding: 0.3rem 0; margin: -0.3rem 0; }
  /* the dot has no left gutter to sit in here, so it hangs centred below
     the active label - item widths stay identical to inactive ones */
  .side nav a[aria-current]::before {
    left: 50%;
    top: calc(100% + 0.1rem);
    transform: translateX(-50%);
  }
}
h1, h2, h3, h4 {
  font-family: 'Source Sans 3', system-ui, sans-serif;
  font-weight: 650;
  line-height: 1.25;
  letter-spacing: -0.01em;
}
h1 {
  font-size: clamp(1.75rem, 4vw, 2.25rem);
  line-height: 1.15;
  letter-spacing: -0.015em;
  margin: 0 0 1.5rem;
}
.post h1 {
  font-size: clamp(1.9rem, 5vw, 2.5rem);
  letter-spacing: -0.02em;
  margin: 0 0 0.6rem;
}
h2 { font-size: 1.35rem; margin: 3.4rem 0 0.8rem; }
h3 { font-size: 1.15rem; margin: 2.2rem 0 0.5rem; letter-spacing: 0; }
h4 { font-size: 1.06rem; margin: 1.8rem 0 0.4rem; letter-spacing: 0; }
p, ul, ol { margin: 0 0 1.1rem; }
strong { font-weight: 600; }
p, li, h1, h2, h3, h4, a { overflow-wrap: break-word; }
ul, ol { padding-left: 1.3rem; }
li { margin-bottom: 0.35rem; }
a {
  color: var(--link);
  text-decoration: underline;
  text-decoration-color: var(--brand-soft);
  text-decoration-thickness: 2px;
  text-underline-offset: 3px;
  transition: text-decoration-color 120ms ease;
}
a:hover, a:focus-visible { text-decoration-color: var(--brand); }
a:focus-visible { outline: 2px solid var(--brand); outline-offset: 3px; border-radius: 2px; }
img { max-width: 100%; height: auto; }
figure { margin: 1.5rem 0; }
figure img { max-width: 100%; height: auto; }   /* never upscale past natural size */
figcaption { color: var(--muted); font-size: 0.9em; margin-top: 0.4rem; }

blockquote {
  margin: 1.5rem 0;
  padding-left: 1.2rem;
  border-left: 3px solid var(--brand-soft);
  color: var(--muted);
}
blockquote.note { font-size: 0.95em; }
blockquote p:last-child { margin-bottom: 0; }
code, pre {
  font-family: 'Source Code Pro', ui-monospace, SFMono-Regular, 'Cascadia Mono', Consolas, monospace;
  font-size: 0.9em;
}
code { background: var(--code-bg); padding: 0.1em 0.35em; border-radius: 3px; }
pre {
  background: var(--code-bg);
  padding: 1rem 1.2rem;
  border-radius: 4px;
  overflow-x: auto;
  line-height: 1.55;
  margin: 0 0 1.4rem;
}
pre code { background: none; padding: 0; }
table { border-collapse: collapse; width: 100%; margin: 0; font-size: 0.95em; }
.table-wrap { overflow-x: auto; margin: 0 0 1.4rem; }
th, td { white-space: normal; }
th, td { text-align: left; padding: 0.4rem 0.6rem; border-bottom: 1px solid var(--rule); vertical-align: top; }
hr { border: 0; border-top: 1px solid var(--rule); margin: 2.5rem 0; }

.meta { color: var(--muted); margin-top: 0; margin-bottom: 2rem; font-size: 0.95rem; }
.year { font-size: 1.1rem; color: var(--muted); margin-top: 2.6rem; }
.when { color: var(--muted); font-size: 0.9em; white-space: nowrap; }
.in-lang { color: var(--muted); font-size: 0.9em; white-space: nowrap; }
.index, .pubs, .talks { list-style: none; padding: 0; }
.index li { margin-bottom: 1.1rem; }
/* Title left, date right on one line. Run inline, the date collides with titles
   that end in a number - "the Sarajevo Olympic Games in 1984 Aug 11" reads as
   one string. The title keeps min-width 0 so it wraps inside its own space
   instead of pushing the date away. */
.index-head {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: baseline;
  column-gap: 1rem;
}
/* two cells, not three, so a title long enough to fill the line wraps inside
   its own column instead of pushing the date onto a line of its own */
.index-title { min-width: 0; }
.index li p { margin: 0.15rem 0 0; color: var(--muted); font-size: 0.95em; line-height: 1.55; }
.pubs li, .talks li { margin-bottom: 0.9rem; line-height: 1.5; }
/* Old per-paper URLs 301 to /research/#slug, so the entry a citation points at
   has to be findable in a long list without hunting for it. */
.pubs li:target {
  scroll-margin-top: 2rem;
  background: var(--code-bg);
  box-shadow: 0 0 0 0.45rem var(--code-bg);
  border-radius: 2px;
}
.authors, .venue { display: block; color: var(--muted); font-size: 0.9em; }
.talk-title { font-weight: 600; }
.talk-meta { display: block; color: var(--muted); font-size: 0.9em; }

footer {
  margin-top: 4rem;
  padding: 1.6rem 0 4rem;
  border-top: 1px solid var(--brand-soft);
  color: var(--muted);
  font-size: 0.95rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem 1.5rem;
  flex-wrap: wrap;
}
footer p { margin: 0; }
.elsewhere { display: flex; align-items: center; gap: 0.9rem; }
.elsewhere a { color: var(--muted); display: inline-flex; }
.elsewhere a:hover, .elsewhere a:focus-visible { color: var(--link); }
.elsewhere svg { width: 17px; height: 17px; fill: currentColor; display: block; }

.portrait { cursor: zoom-in; }
/* Wide figures in posts open in the same dialog as the portrait. The anchor is
   emitted by layouts/shortcodes/figure.html, which also keeps it off images
   that are no wider than the column. */
.zoom { display: block; line-height: 0; cursor: zoom-in; }
body:has(dialog[open]) { overflow: hidden; }
.lightbox {
  border: 0;
  padding: 5vmin;
  background: transparent;
  width: 100%; height: 100%;
  max-width: 100vw; max-height: 100vh;
  overflow: hidden;
  display: flex; align-items: center; justify-content: center;
  cursor: zoom-out;
}
.lightbox:not([open]) { display: none; }
.lightbox::backdrop { background: rgba(15, 18, 27, 0.93); }
.lightbox img {
  cursor: zoom-out;
  max-width: 100%; max-height: 100%;
  border-radius: 3px;
  box-shadow: 0 24px 70px rgba(0, 0, 0, 0.45);
}

.postscript {
  margin-top: 3.2rem;
  padding-top: 1.4rem;
  border-top: 1px solid var(--rule);
  color: var(--muted);
  font-size: 0.95rem;
}
.postscript p { margin: 0; }

.subscribe {
  margin: 2.2rem 0 2.6rem;
}
.subscribe p { margin: 0 0 0.9rem; color: var(--muted); font-size: 0.98rem; }
.subscribe form { display: flex; gap: 0.5rem; flex-wrap: wrap; }
.subscribe input {
  flex: 1 1 15rem;
  font: inherit; font-size: 1rem;
  padding: 0.55rem 0.8rem;
  color: var(--ink);
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: 4px;
}
.subscribe input::placeholder { color: var(--muted); opacity: 0.75; }
.subscribe input:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 1px;
  border-color: transparent;
}
.subscribe button {
  font: inherit; font-size: 1rem;
  padding: 0.55rem 1.3rem;
  color: #fff; background: var(--brand-strong);
  border: 0; border-radius: 4px; cursor: pointer;
}
.subscribe button:hover { filter: brightness(1.12); }
.subscribe button:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { a { transition: none; } }

/* Syntax highlighting, one palette per scheme. Both were checked against their
   own code background: the lightest role on the light palette is the comment at
   5.2:1, on the dark one it is also the comment at 4.8:1. */
.highlight pre { margin: 0; }
.highlight { margin: 0 0 1.4rem; }
.chroma { color: #2A2F3D; }
.chroma .c, .chroma .ch, .chroma .cm, .chroma .c1, .chroma .cs,
.chroma .cp, .chroma .cpf { color: #5E6678; font-style: italic; }
.chroma .k, .chroma .kc, .chroma .kd, .chroma .kn, .chroma .kp,
.chroma .kr, .chroma .kt { color: #6D28D9; }
.chroma .s, .chroma .sa, .chroma .sb, .chroma .sc, .chroma .dl, .chroma .sd,
.chroma .s2, .chroma .se, .chroma .sh, .chroma .si, .chroma .sx, .chroma .sr,
.chroma .s1, .chroma .ss { color: #1F6B45; }
.chroma .m, .chroma .mb, .chroma .mf, .chroma .mh, .chroma .mi,
.chroma .il, .chroma .mo { color: #8F4E12; }
.chroma .nf, .chroma .fm, .chroma .nd { color: #175FA8; }
.chroma .nc, .chroma .nn, .chroma .ne, .chroma .no, .chroma .nt { color: #0F6E6A; }
.chroma .na, .chroma .nv, .chroma .vc, .chroma .vg, .chroma .vi,
.chroma .py, .chroma .nl { color: #2A2F3D; }
.chroma .o, .chroma .ow, .chroma .p { color: #5B6376; }
.chroma .gd { color: #A32E22; }
.chroma .gi { color: #1F6B45; }
.chroma .err { color: #A32E22; }

@media (prefers-color-scheme: dark) {
  .chroma { color: #D7DBE6; }
  .chroma .c, .chroma .ch, .chroma .cm, .chroma .c1, .chroma .cs,
  .chroma .cp, .chroma .cpf { color: #848CA1; }
  .chroma .k, .chroma .kc, .chroma .kd, .chroma .kn, .chroma .kp,
  .chroma .kr, .chroma .kt { color: #B197FF; }
  .chroma .s, .chroma .sa, .chroma .sb, .chroma .sc, .chroma .dl, .chroma .sd,
  .chroma .s2, .chroma .se, .chroma .sh, .chroma .si, .chroma .sx, .chroma .sr,
  .chroma .s1, .chroma .ss { color: #8FD1A6; }
  .chroma .m, .chroma .mb, .chroma .mf, .chroma .mh, .chroma .mi,
  .chroma .il, .chroma .mo { color: #E0AF7C; }
  .chroma .nf, .chroma .fm, .chroma .nd { color: #84B8F0; }
  .chroma .nc, .chroma .nn, .chroma .ne, .chroma .no, .chroma .nt { color: #79CFCB; }
  .chroma .na, .chroma .nv, .chroma .vc, .chroma .vg, .chroma .vi,
  .chroma .py, .chroma .nl { color: #D7DBE6; }
  .chroma .o, .chroma .ow, .chroma .p { color: #9BA1B3; }
  .chroma .gd { color: #E88C7D; }
  .chroma .gi { color: #8FD1A6; }
  .chroma .err { color: #E88C7D; }
}

/* Footnotes: a quiet block after the article, markers small but clickable. */
.footnote-ref {
  font-size: 0.75em;
  font-variant-numeric: lining-nums;
  text-decoration: none;
  padding: 0 0.1em;
}
.footnote-ref::before { content: "["; }
.footnote-ref::after  { content: "]"; }
.footnotes {
  margin-top: 3rem;
  padding-top: 1.4rem;
  border-top: 1px solid var(--rule);
  color: var(--muted);
  font-size: 0.95rem;
}
.footnotes ol { padding-left: 1.2rem; }
.footnotes li { margin-bottom: 0.5rem; }
.footnotes p { margin: 0; }
.footnote-backref { text-decoration: none; margin-left: 0.35rem; }

/* CV timeline. Two columns: the span, then the body, with a rule running down
   the page beside it. Both the rule and the dot are absolutely positioned inside
   the body, so they share one coordinate system and line up exactly: the rule
   sits at 0-2px, the dot spans -4px to 6px, and both centre on 1px. Drawing the
   rule as a border instead would measure it from the border box while the dot
   measured from the padding box, which puts the dot half its width off. */
.tl-entry {
  display: grid;
  grid-template-columns: 6rem minmax(0, 1fr);
}
.tl-when {
  font-size: 0.82rem;
  line-height: 1.5;
  color: var(--muted);
  text-align: right;
  padding: 0.5rem 1rem 0 0;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.tl-body {
  position: relative;
  padding: 0 0 1.6rem 1.5rem;
}
.tl-body::after {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 2px;
  background: var(--rule);
}
.tl-body::before {
  content: "";
  position: absolute;
  z-index: 1;
  /* `*` does not match pseudo-elements, so this one would otherwise be
     content-box: 10px of dot plus 2px of ring on each side, 14px in all, which
     puts the visible circle 2px right of the rule. Stated explicitly here. */
  box-sizing: border-box;
  left: -6px;
  top: 0.6rem;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--brand);
  border: 2px solid var(--paper);
}
/* The first entry in a section follows its heading, so start its rule at the
   centre of the dot rather than at the top of the body: otherwise a short stub
   of line hangs above the first dot with nothing above it to join. */
h2 + .tl-entry .tl-body::after { top: calc(0.6rem + 7px); }
/* The oldest entry in each section ends the line. Hiding its rule outright left
   the previous segment stopping at the top of this body while the dot sits
   16px lower, so the last entry read as detached; instead the rule runs down to
   the centre of that dot and stops there. The dot keeps the brand colour: a
   muted one looked disabled rather than final. */
.tl-entry:not(:has(+ .tl-entry)) .tl-body::after {
  bottom: auto;
  height: calc(0.6rem + 7px);
}
.tl-body > :first-child { margin-top: 0; }
.tl-body > :last-child { margin-bottom: 0; }
.tl-body p { margin: 0 0 0.35rem; }
.tl-body ul { margin: 0.35rem 0 0; padding-left: 1.1rem; }
.tl-body li { margin: 0 0 0.15rem; }

/* Below this width the nav can no longer sit beside the portrait, so the two
   stack. Centre them together: centring the portrait alone would break the left
   edge that the nav, the heading and the body text all share. */
@media (max-width: 34rem) {
  .side { justify-content: center; }
  /* nav reads as one toolbar: slightly smaller type and tighter gaps keep all
     six items on a single row down to ~360px viewports */
  .side nav ul { justify-content: center; gap: 0.6rem 0.75rem; }
  .side nav a { font-size: 0.88rem; }
}
/* museum-narrow screens where one row cannot fit six items: cap the width so
   they break as a deliberate 3+3 instead of 5+1 with Bio hanging alone */
@media (max-width: 21rem) {
  .side nav ul { max-width: 13rem; margin-inline: auto; }
}

@media (max-width: 34rem) {
  .tl-entry { grid-template-columns: 3.9rem minmax(0, 1fr); }
  .tl-when { font-size: 0.72rem; padding-right: 0.6rem; padding-top: 0.55rem; }
  .tl-body { padding-left: 1.1rem; }
  .tl-body ul { padding-left: 1rem; }
}

/* Appearances: thumbnail left, title and meta right. One column at every width -
   this is a list, not a grid. A two-column grid would show the art at ~296px,
   where eight shows' competing branding becomes the loudest thing on the page;
   at ~136px the same art settles into a quiet visual index. Talks and podcasts
   share these rows so the page reads as one system. */
.appearances { list-style: none; padding: 0; margin: 0; }
.appearances li {
  --thumb-w: clamp(6.75rem, 30vw, 8.5rem);
  position: relative;
  display: grid;
  grid-template-columns: var(--thumb-w) minmax(0, 1fr);
  column-gap: 1rem;
  align-items: start;
  margin: 0 0 1.5rem;
  line-height: 1.5;
}
.appearances img,
.appearances .thumb-tile {
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  border-radius: 3px;
  /* a hairline so dark artwork does not dissolve into the page; an inset
     box-shadow would sit under the image's own pixels, an outline does not */
  outline: 1px solid var(--rule);
  outline-offset: -1px;
  /* the one line that tames eight different shows into a single system */
  filter: saturate(0.82);
  transition: filter 120ms ease;
}
.appearances .thumb-tile {
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 0 0.5rem;
  background: var(--code-bg);
  color: var(--muted);
  font-family: 'Source Code Pro', ui-monospace, monospace;
  font-size: 0.62rem;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  line-height: 1.35;
  filter: none;
}
.appearances .app-title {
  font-weight: 600;
  color: var(--ink);
  text-decoration: none;
}
/* the title's own link covers the thumbnail too: one tab stop per row, one
   accessible name, and the picture still clickable */
.appearances .app-title::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  width: var(--thumb-w);
  aspect-ratio: 16 / 9;
}
.appearances li:hover img { filter: saturate(1); }
.appearances li:hover .app-title,
.appearances .app-title:focus-visible {
  text-decoration: underline;
  text-decoration-color: var(--brand-soft);
  text-decoration-thickness: 2px;
  text-underline-offset: 3px;
}
/* Slides and Code stay ordinary links inside the meta line */
.appearances .talk-meta a { color: var(--link); position: relative; }

@media (prefers-reduced-motion: reduce) {
  .appearances img { transition: none; }
}

/* Share row. It takes the rule that the postscript used to draw, and the
   postscript then follows without one, so the end of a post has a single
   horizontal line rather than two stacked a centimetre apart. */
.share {
  margin-top: 3.2rem;
  padding-top: 1.4rem;
  border-top: 1px solid var(--rule);
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.35rem 1rem;
  font-size: 0.95rem;
}
/* `~`, not `+`: the partial emits its <script> between the two divs, so the
   adjacent-sibling selector never matched and both drew a rule. */
.share ~ .postscript {
  margin-top: 1rem;
  padding-top: 0;
  border-top: 0;
}
.share-label { color: var(--muted); }
/* A <button> sitting between two anchors has to imitate them exactly, or the
   row reads as one broken link: same underline weight, same hover colour, and
   a focus ring, since without one tabbing gives two brand outlines and one
   browser default. */
.share-btn {
  font: inherit;
  color: var(--link);
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  text-decoration: underline;
  text-decoration-color: var(--brand-soft);
  text-decoration-thickness: 2px;
  text-underline-offset: 3px;
  transition: text-decoration-color 120ms ease;
}
.share-btn:hover, .share-btn:focus-visible { text-decoration-color: var(--brand); }
.share-btn:focus-visible { outline: 2px solid var(--brand); outline-offset: 3px; border-radius: 2px; }
.share-status { color: var(--muted); font-size: 0.9em; }
