@keyframes footerRingSpin { to { transform: rotate(360deg); } }
/* cyrillic-ext */
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url("fonts/ad99a4c1cf58.woff2") format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url("fonts/4f12a1a0ae41.woff2") format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* vietnamese */
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url("fonts/e3000ed81c87.woff2") format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url("fonts/a3b94a5a967d.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;
}
/* latin */
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url("fonts/52ea38a7d8d6.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;
}
/* cyrillic-ext */
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: italic;
  font-weight: 500;
  font-display: swap;
  src: url("fonts/ad99a4c1cf58.woff2") format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: italic;
  font-weight: 500;
  font-display: swap;
  src: url("fonts/4f12a1a0ae41.woff2") format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* vietnamese */
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: italic;
  font-weight: 500;
  font-display: swap;
  src: url("fonts/e3000ed81c87.woff2") format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: italic;
  font-weight: 500;
  font-display: swap;
  src: url("fonts/a3b94a5a967d.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;
}
/* latin */
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: italic;
  font-weight: 500;
  font-display: swap;
  src: url("fonts/52ea38a7d8d6.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;
}
/* cyrillic-ext */
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("fonts/c18dbd11ca5b.woff2") format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("fonts/ac78c6c2ff98.woff2") format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* vietnamese */
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("fonts/8a92de9f3770.woff2") format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("fonts/a9e49ee5f58f.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;
}
/* latin */
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("fonts/74db058fe06d.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;
}
/* cyrillic-ext */
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("fonts/c18dbd11ca5b.woff2") format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("fonts/ac78c6c2ff98.woff2") format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* vietnamese */
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("fonts/8a92de9f3770.woff2") format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("fonts/a9e49ee5f58f.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;
}
/* latin */
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("fonts/74db058fe06d.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;
}
/* cyrillic-ext */
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("fonts/c18dbd11ca5b.woff2") format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("fonts/ac78c6c2ff98.woff2") format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* vietnamese */
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("fonts/8a92de9f3770.woff2") format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("fonts/a9e49ee5f58f.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;
}
/* latin */
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("fonts/74db058fe06d.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;
}
/* cyrillic-ext */
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("fonts/c18dbd11ca5b.woff2") format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("fonts/ac78c6c2ff98.woff2") format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* vietnamese */
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("fonts/8a92de9f3770.woff2") format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("fonts/a9e49ee5f58f.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;
}
/* latin */
@font-face {
  font-family: 'Cormorant Garamond';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("fonts/74db058fe06d.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;
}

/* vietnamese */
@font-face {
  font-family: 'Newsreader';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url("fonts/4f8405520dc8.woff2") format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
  font-family: 'Newsreader';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url("fonts/fe6ea7dc48cb.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;
}
/* latin */
@font-face {
  font-family: 'Newsreader';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url("fonts/3f2b7e2b2ef8.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;
}
/* vietnamese */
@font-face {
  font-family: 'Newsreader';
  font-style: italic;
  font-weight: 500;
  font-display: swap;
  src: url("fonts/4f8405520dc8.woff2") format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
  font-family: 'Newsreader';
  font-style: italic;
  font-weight: 500;
  font-display: swap;
  src: url("fonts/fe6ea7dc48cb.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;
}
/* latin */
@font-face {
  font-family: 'Newsreader';
  font-style: italic;
  font-weight: 500;
  font-display: swap;
  src: url("fonts/3f2b7e2b2ef8.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;
}
/* vietnamese */
@font-face {
  font-family: 'Newsreader';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("fonts/9b967522bc5a.woff2") format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
  font-family: 'Newsreader';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("fonts/d30ef885441c.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;
}
/* latin */
@font-face {
  font-family: 'Newsreader';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("fonts/e599ceb478c4.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;
}
/* vietnamese */
@font-face {
  font-family: 'Newsreader';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("fonts/9b967522bc5a.woff2") format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
  font-family: 'Newsreader';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("fonts/d30ef885441c.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;
}
/* latin */
@font-face {
  font-family: 'Newsreader';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("fonts/e599ceb478c4.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;
}
/* vietnamese */
@font-face {
  font-family: 'Newsreader';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("fonts/9b967522bc5a.woff2") format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
  font-family: 'Newsreader';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("fonts/d30ef885441c.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;
}
/* latin */
@font-face {
  font-family: 'Newsreader';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("fonts/e599ceb478c4.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;
}
/* vietnamese */
@font-face {
  font-family: 'Newsreader';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("fonts/9b967522bc5a.woff2") format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
  font-family: 'Newsreader';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("fonts/d30ef885441c.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;
}
/* latin */
@font-face {
  font-family: 'Newsreader';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("fonts/e599ceb478c4.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;
}

/* latin-ext */
@font-face {
  font-family: 'Fragment Mono';
  font-style: normal;
  font-weight: 400;
  font-display: block;
  src: url("fonts/89d1b754bb47.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;
}
/* latin */
@font-face {
  font-family: 'Fragment Mono';
  font-style: normal;
  font-weight: 400;
  font-display: block;
  src: url("fonts/62caf587b650.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;
}
/* latin-ext */
@font-face {
  font-family: 'Fragment Mono';
  font-style: normal;
  font-weight: 500;
  font-display: block;
  src: url("fonts/89d1b754bb47.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;
}
/* latin */
@font-face {
  font-family: 'Fragment Mono';
  font-style: normal;
  font-weight: 500;
  font-display: block;
  src: url("fonts/62caf587b650.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;
}
/* latin-ext */
@font-face {
  font-family: 'Fragment Mono';
  font-style: normal;
  font-weight: 600;
  font-display: block;
  src: url("fonts/89d1b754bb47.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;
}
/* latin */
@font-face {
  font-family: 'Fragment Mono';
  font-style: normal;
  font-weight: 600;
  font-display: block;
  src: url("fonts/62caf587b650.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;
}

  :root {
    /* real WoS tokens, pulled from assets/css/site.css */
    --bg: #FFFFFF;
    --fg: #060606;
    --faded: #5B5F66;
    --border: #E5E5E5;
    --imgline: var(--fg);   /* image/input frame line — matches the homepage footer */
    --accent: #D12B1D;   /* text-safe vermilion; see public/index.css */
    --accent-strong: #B6352A;
    --hover: #F2F2F2;
    --serif: 'Newsreader', Georgia, serif;
    --display: 'Cormorant Garamond', Georgia, serif;
    --mono: 'Fragment Mono', 'Menlo', monospace;
    --wt-display: 700;
    --wt-body: 400;
    --art-w: 780px;         /* desktop article measure (default; the Width control overrides) */
    --toc-w: 280px;         /* desktop side-TOC width (reserved in the article measure) */
    color-scheme: light;    /* force a light UA canvas even on dark-mode systems */
  }

  /* dark = pure black; warm greens neutralised to greys so nothing dulls the black */
  html[data-theme="dark"] {
    --bg: #000000;
    --fg: #FFFFFF;
    --faded: rgba(255,255,255,0.56);
    --border: rgba(255,255,255,0.14);
    --imgline: rgba(255,255,255,0.32);
    --hover: rgba(255,255,255,0.06);
    /* Was inherited from :root, so dark articles were reading the LIGHT
       accent. It clears 4.5:1 on pure black but not on the raised card
       surfaces, so it is set here rather than left to fall through. */
    --accent: #E54E41;
    --accent-strong: #EA7469;
    color-scheme: dark;
  }

  /* WoS paper — warm ivory/parchment, distinct from the personal site's
     brown-accent paper. Keeps the vermilion accent (brand stays recognizable),
     just shifts the surface and ink warmer. */
  html[data-theme="paper"] {
    --bg: #F2E8D3;
    --fg: #2A1F12;
    --faded: #3D2B18;
    --border: #DDD0B4;
    --imgline: var(--fg);
    --hover: #EBDDBC;
  }

  /* paint the root canvas with our own token so a dark-mode browser never
     shows its default dark backdrop behind/around the body */
  html { background:var(--bg); }

  /* --vw is the width of the SURFACE, not the viewport. Everything the article
     positions off it — the body's left margin, the side TOC's left edge, the
     utility rail, the wide-figure measures — is an offset within #stage, and
     #stage is capped below. Left at 100vw those offsets would be computed
     against a viewport the surface no longer fills, and past 1600 the rail and
     Contents would drift off the column they belong to. */
  html { --vw: min(100vw, 1600px); }

  /* Cap the article surface at 1600 and centre it, matching #site-root on index
     pages (public/index.8e55c2aab5.css). Both surfaces run full-width below 1600 and become
     a centred column past it; the article was the one that never got the cap, so
     at 1920 its masthead and footer ran edge to edge while every index page's
     stopped at 1600 — the same chrome in two places depending on the route.
     The menu overlay is position:fixed, so it escapes this and still covers the
     viewport. */
  @media (min-width: 760px) {
    #stage { max-width: 1600px; margin-inline: auto; }
  }

  body { transition:background .25s ease, color .25s ease; }

  /* ===== article page surface ===== */
  /* #stage paints the theme background itself, so the surface stays themed even
     if the host forces html/body transparent. Identical at every width — the old
     device-preview framing (striped backdrop, preview padding, phone/tablet
     bezels) has been removed, not merely overridden. */
  #stage[data-site] { display:block; min-height:100vh; background:var(--bg); }

  * {box-sizing:border-box;}

  html, body {margin:0;padding:0;}

  body {
    background:var(--bg);
    color:var(--fg);
    font-family:var(--serif);
    font-weight:var(--wt-body);
    -webkit-font-smoothing:antialiased;
  }

  /* The article page's outermost box. This was a .device wrapper inside #stage
     — the last remnant of a simulated phone/tablet preview frame — which had
     already been flattened to width:100%/overflow:visible and did nothing but
     carry the two declarations below. The wrapper is gone; these move here.
     Deliberately NO transform: fixed panels (menu / settings / share) must
     resolve to the window, not to this element, or they would pin to the tall
     document instead of the viewport once scrolled. */
  #stage {
    min-height:100vh;
    background:var(--bg);
  }

  .note {
    max-width:430px;
    margin:0 auto 8px;
    font-family:var(--mono);
    font-size:11px;
    color:#777;
    letter-spacing:.02em;
    padding:0 4px;
  }

  /* ---- sticky stack: wordmark header + rail + TOC strip ---- */
  .stack {
    position:sticky;
    top:0;
    z-index:50;
    background:var(--bg);
    /* Rest 8px below the viewport top so the header breathes like the homepage
       (whose body has an 8px margin). Sticky top:0 still pins it flush when scrolled. */
    margin-top:8px;
  }

  /* framed with the masthead's bottom line instead of a shadow */

  .masthead {
    display:flex;
    align-items:center;
    justify-content:space-between;
    /* match the home header: globe hugs the left, top/bottom condense via JS */
    padding:16px 24px 16px 16px;
    border-bottom:1px solid var(--fg);
    transition:padding 0.3s;
  }

  /* condensed (scrolled) masthead: compact on phone/tablet, a taller ~82px bar on
     desktop so the sticky header reads with more presence while reading. */
  .masthead.mast-condensed { padding-top:14px; padding-bottom:14px; }

  @media (min-width: 1024px) { [data-site] .masthead.mast-condensed { padding-top:24px; padding-bottom:24px; } }

  /* The globe + wordmark lockup sizing is shared with the homepage — see
     masthead-lockup.css. Only the page-specific masthead padding lives here. */

  /* ---- PHONE masthead: static single-line lockup (matches the homepage phone
     header exactly). Globe on the left, wordmark centred to the viewport, menu
     button on the right — one fixed layout, no scroll morph: the .mast-condensed
     selectors carry identical values so scrolling changes nothing, and only the
     single-line lockup shows (the stacked variant is hidden). Desktop/tablet keep
     the shared stacked→condense lockup untouched. */
  @media (max-width: 759.98px) { #stage[data-site] .masthead [data-lockup="stacked"] { display:none !important; } }

  /* Globe policy: hidden by default (wordmark-only). Desktop/tablet keep it hidden
     — the wordmark is left-aligned and the right-side top-nav balances it. Phone
     RE-SHOWS the globe in the left corner with the wordmark centred (phone has no
     top-nav to fight, and the corner globe anchors the left). */
  .masthead #globe-single { display:none !important; }

  /* phone lockup container: neutral block spanning the header so the globe and
     wordmark position absolutely against the sticky header. */
  @media (max-width: 759.98px) { #stage[data-site] .masthead [data-lockup="single"] {
    display:block !important; position:static !important; transform:none !important;
  } }

  @media (max-width: 759.98px) { #stage[data-site] .masthead, #stage[data-site] .masthead.mast-condensed {
    padding:0 !important;
    min-height:66px;
    position:relative;
  } }

  /* globe: re-shown on phone, tucked into the left corner, vertically centred */
  @media (max-width: 759.98px) { #stage[data-site] .masthead #globe-single, #stage[data-site] .masthead.mast-condensed #globe-single {
    display:inline-flex !important;
    position:absolute !important; left:14px !important; top:50%;
    transform:translateY(-50%); margin:0 !important; flex:none;
    width:44px !important; height:44px !important;
  } }

  /* wordmark: centred across the full header width (globe under its left edge, menu
     under its right; the wordmark spans between them, centred). */
  @media (max-width: 759.98px) { #stage[data-site] .masthead [data-lockup="single"] > span:last-child, #stage[data-site] .masthead.mast-condensed [data-lockup="single"] > span:last-child {
    position:absolute !important; left:50% !important; right:auto !important;
    top:50%; transform:translate(-50%, -50%);
    font-size:28px !important; line-height:1.05 !important;
    text-align:center !important; white-space:nowrap;
  } }

  /* menu button: right, dropped so the icon's bottom line sits on the wordmark baseline */
  @media (max-width: 759.98px) { #stage[data-site] .masthead .mast-right, #stage[data-site] .masthead.mast-condensed .mast-right {
    position:absolute !important; right:20px;
    top:50%; transform:translateY(calc(-50% + 4px));
  } }

  /* ---- DESKTOP / TABLET masthead: single-line centred lockup (matches the phone
     header). The stacked hero is removed at every non-phone width; one fixed centred
     bar — globe left, wordmark centred, menu right — with no scroll morph. */
  @media (min-width: 760px) and (max-width: 1023.98px) { #stage[data-site] .masthead [data-lockup="stacked"] { display:none !important; } }
@media (min-width: 1024px) { #stage[data-site] .masthead [data-lockup="stacked"] { display:none !important; } }

  /* container: neutral block spanning the header so globe + wordmark position
     absolutely against the sticky header (no transform, or it re-traps them). */
  @media (min-width: 760px) and (max-width: 1023.98px) { #stage[data-site] .masthead [data-lockup="single"] { display:block !important; position:static !important; transform:none !important; } }
@media (min-width: 1024px) { #stage[data-site] .masthead [data-lockup="single"] { display:block !important; position:static !important; transform:none !important; } }

  @media (min-width: 760px) and (max-width: 1023.98px) { #stage[data-site] .masthead, #stage[data-site] .masthead.mast-condensed {
    position:relative;
    /* globe (60px) + wordmark are position:absolute, so floor the bar height or it
       collapses to the hamburger (~31px) and the globe/wordmark overflow with the
       divider cutting through them (worst with the nav hidden while the menu is open). */
    box-sizing:border-box !important;
    padding-top:8px !important; padding-bottom:8px !important; min-height:80px !important;
    /* Match the content gutter, which is 50px on this surface — the same place
       the footer's own padding puts its first and last character.
       NOT 58: that is the index masthead's number, and it is 50 + 8 because that
       masthead is full-bleed and breaks out of the body's 8px margin. #stage has
       no such margin, so carrying the 58 across put the globe and the hamburger
       8px inside the footer beneath them at every desktop width. */
    padding-left:50px !important; padding-right:50px !important;
  } }
@media (min-width: 1024px) { #stage[data-site] .masthead, #stage[data-site] .masthead.mast-condensed {
    position:relative;
    /* globe (60px) + wordmark are position:absolute, so floor the bar height or it
       collapses to the hamburger (~31px) and the globe/wordmark overflow with the
       divider cutting through them (worst with the nav hidden while the menu is open). */
    box-sizing:border-box !important;
    padding-top:8px !important; padding-bottom:8px !important; min-height:80px !important;
    /* Match the content gutter, which is 50px on this surface — the same place
       the footer's own padding puts its first and last character.
       NOT 58: that is the index masthead's number, and it is 50 + 8 because that
       masthead is full-bleed and breaks out of the body's 8px margin. #stage has
       no such margin, so carrying the 58 across put the globe and the hamburger
       8px inside the footer beneath them at every desktop width. */
    padding-left:50px !important; padding-right:50px !important;
  } }

  /* globe: re-shown, tucked into the left corner at the content gutter, vertically
     centred (overrides the wordmark-only display:none). */
  @media (min-width: 760px) and (max-width: 1023.98px) { #stage[data-site] .masthead #globe-single {
    display:inline-flex !important;
    position:absolute !important; left:50px !important; top:50%;
    transform:translateY(-50%); margin:0 !important;
    width:70px !important; height:70px !important;
  } }
@media (min-width: 1024px) { #stage[data-site] .masthead #globe-single {
    display:inline-flex !important;
    position:absolute !important; left:50px !important; top:50%;
    transform:translateY(-50%); margin:0 !important;
    width:70px !important; height:70px !important;
  } }

  /* iPad portrait: the reader article body is near full-width here with a 20px
     gutter, so drop the masthead's 58px gutter to 20 (globe on the left, menu on the
     right) to mirror the content — the 58px is for the desktop centred column.

     .mast-condensed is named here even though both states take 20px: the 58px rule
     above carries it in its selector list, so it is a class more specific than this
     one, and patching only the bare selector left the scrolled state on the desktop
     58px. The masthead transitions `padding`, so the globe and hamburger slid 38px
     inward on every scroll down and back out on every scroll up. */
  @media (min-width: 760px) and (max-width: 1023.98px) { #stage[data-site] .masthead, #stage[data-site] .masthead.mast-condensed { padding-left:20px !important; padding-right:20px !important; } }

  @media (min-width: 760px) and (max-width: 1023.98px) { #stage[data-site] .masthead #globe-single { left:20px !important; } }

  /* wordmark: centred on the header. Shrink-wrapped (left:50% + translate) so its
     box is only as wide as the text — a full-width box would sit on top of the
     top-nav and swallow its clicks. */
  @media (min-width: 760px) and (max-width: 1023.98px) { #stage[data-site] .masthead [data-lockup="single"] > span:last-child {
    position:absolute !important; left:50% !important; right:auto !important;
    /* optical raise ~2px so the wordmark's low-sitting lowercase mass centres on
       the shared midline with the nav/globe/hamburger (cap centres already match). */
    top:50%; transform:translate(-50%, calc(-50% - 2px));
    padding:0 !important;
    font-size:45px !important; line-height:1 !important; white-space:nowrap;
    text-align:center !important;
  } }
@media (min-width: 1024px) { #stage[data-site] .masthead [data-lockup="single"] > span:last-child {
    position:absolute !important; left:50% !important; right:auto !important;
    /* optical raise ~2px so the wordmark's low-sitting lowercase mass centres on
       the shared midline with the nav/globe/hamburger (cap centres already match). */
    top:50%; transform:translate(-50%, calc(-50% - 2px));
    padding:0 !important;
    font-size:45px !important; line-height:1 !important; white-space:nowrap;
    text-align:center !important;
  } }

  /* ---- desktop reading header ----
     Phone and tablet swap the whole masthead for a title row. Desktop does the
     smaller thing: only the centre changes. The globe stays, the nav stays, the
     height does not move, and the wordmark crossfades into the article title in
     the slot it already occupies — so the header tells you what you are reading
     without the page appearing to reflow around it.

     Title only. The rail already carries share and text settings on desktop, and
     duplicating them into the header would put the same two controls on screen
     twice. That is why the mobile row cannot simply be turned on here.

     Ellipsised rather than wrapped: this element is inside a fixed-height
     header, so a second line would either be clipped or would change the
     height, and changing the height is the one thing this is trying to avoid.

     Off by default, on at desktop. It was originally styled ONLY inside the
     desktop query, which meant that below 1024 it was not "not styled" — it was
     an ordinary inline span holding the headline, laid out in normal flow right
     on top of the wordmark. Phone and tablet both had the article title printed
     across "World of Systems". A rule that positions an element has to be
     paired with one that hides it, or the media query is only half a switch. */
  .mast-read-title { display:none; }
  @media (min-width: 1024px) {
    #stage[data-site] .masthead .mast-read-title {
      display:block;
      position:absolute; left:50%; top:50%;
      transform:translate(-50%, -50%);
      max-width:min(58vw, 640px);
      font-family:var(--display); font-weight:600;
      font-size:23px; line-height:1.2; letter-spacing:-.005em;
      color:var(--fg); text-align:center;
      white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
      opacity:0; visibility:hidden; pointer-events:none; z-index:1;
      transition:opacity .22s ease, visibility 0s linear .22s;
    }
    #stage[data-site] .stack.reading .masthead .mast-read-title {
      opacity:1; visibility:visible;
      transition:opacity .22s ease, visibility 0s;
    }
    #stage[data-site] .masthead [data-lockup="single"] > span:last-child { transition:opacity .22s ease; }
    #stage[data-site] .stack.reading .masthead [data-lockup="single"] > span:last-child { opacity:0; pointer-events:none; }

    /* The nav steps back while you are reading. It has to: the title is centred
       in the same slot as the wordmark, and a headline is far longer than
       "World of Systems" — capped narrow enough to clear the nav it would have
       been ellipsised down to a few words, which is worse than not showing it.
       Quieting the nav also makes this an actual reading header rather than the
       normal header with a different word in it; everything in it is still one
       hamburger away, and the globe still goes home. */
    #stage[data-site] .masthead [data-top-nav] { transition:opacity .18s ease, visibility 0s; }
    #stage[data-site] .stack.reading .masthead [data-top-nav] {
      opacity:0; visibility:hidden; pointer-events:none;
      transition:opacity .18s ease, visibility 0s linear .18s;
    }

    /* Hide going down, return going up.

       The .stack moves, not the .masthead inside it. Translating the masthead
       alone slid the header out of view and left its box behind: .stack is
       sticky at top:0 and still reserved 80px, so the article did not scroll up
       into the space — you got an empty white band across the top of the
       viewport with nothing in it. elementFromPoint at the top centre returned
       .stack rather than anything you could see.

       Moving the sticky element itself takes the reserved space with it, and on
       desktop .stack holds only the masthead — the rail and the Contents strip
       live in .overlay-layer and do not ride along. */
    #stage[data-site] .stack { transition:transform .26s cubic-bezier(.22,1,.36,1); }
    #stage[data-site] .stack.mast-away { transform:translateY(-100%); }
  }
  @media (prefers-reduced-motion: reduce) {
    #stage[data-site] .stack { transition:none; }
    #stage[data-site] .masthead .mast-read-title { transition:opacity .01s; }
  }

  /* Keep the right-side cluster above the centred wordmark so it always gets clicks. */
  .masthead .mast-right { position:relative; z-index:3; }

  .masthead [data-lockup="single"] > span:last-child { z-index:1; }

  /* size by real dimensions, not transform:scale (which thickens the lines and
     mis-aligns them on the pixel grid); keeps the 2px lines crisp and even. */
  @media (min-width: 760px) and (max-width: 1023.98px) { #stage[data-site] .masthead .hamburger {
    width:40px !important; height:23px !important;
  } }
@media (min-width: 1024px) { #stage[data-site] .masthead .hamburger {
    width:40px !important; height:23px !important;
  } }

  @media (min-width: 760px) and (max-width: 1023.98px) { #stage[data-site] .masthead .hamburger i {
    height:2px !important;
  } }
@media (min-width: 1024px) { #stage[data-site] .masthead .hamburger i {
    height:2px !important;
  } }

  /* Pin the middle line to a whole pixel (no translateY centring) so a fractional
     thickness doesn't land it on a half-pixel and render soft/bigger than the rest. */
  @media (min-width: 760px) and (max-width: 1023.98px) { #stage[data-site] .masthead .hamburger i:nth-child(2), #stage[data-site] .masthead .hamburger i:nth-child(3) {
    top:11px !important; transform:translateX(-50%) rotate(0deg) !important;
  } }
@media (min-width: 1024px) { #stage[data-site] .masthead .hamburger i:nth-child(2), #stage[data-site] .masthead .hamburger i:nth-child(3) {
    top:11px !important; transform:translateX(-50%) rotate(0deg) !important;
  } }

  @media (min-width: 760px) and (max-width: 1023.98px) { #stage[data-site] .masthead [data-menu-toggle][data-open] .hamburger i:nth-child(2) {
    transform:translateX(-50%) rotate(45deg) !important;
  } }
@media (min-width: 1024px) { #stage[data-site] .masthead [data-menu-toggle][data-open] .hamburger i:nth-child(2) {
    transform:translateX(-50%) rotate(45deg) !important;
  } }

  @media (min-width: 760px) and (max-width: 1023.98px) { #stage[data-site] .masthead [data-menu-toggle][data-open] .hamburger i:nth-child(3) {
    transform:translateX(-50%) rotate(-45deg) !important;
  } }
@media (min-width: 1024px) { #stage[data-site] .masthead [data-menu-toggle][data-open] .hamburger i:nth-child(3) {
    transform:translateX(-50%) rotate(-45deg) !important;
  } }

  /* Desktop top-nav + unfolding header search (mirrors the homepage). */
  .masthead [data-top-nav], .masthead [data-header-searchbar] { display:none; }

  @media (min-width: 760px) and (max-width: 1023.98px) { #stage[data-site] .masthead [data-top-nav] {
    display:flex; max-width:600px; opacity:1; overflow:hidden;
    transition:max-width .38s cubic-bezier(.16,1,.3,1), opacity .2s ease;
  } }
@media (min-width: 1024px) { #stage[data-site] .masthead [data-top-nav] {
    display:flex; max-width:600px; opacity:1; overflow:hidden;
    transition:max-width .38s cubic-bezier(.16,1,.3,1), opacity .2s ease;
  } }

  @media (min-width: 760px) and (max-width: 1023.98px) { #stage[data-site] .masthead [data-header-searchbar] {
    display:flex; max-width:0; opacity:0; overflow:hidden; pointer-events:none;
    transition:max-width .38s cubic-bezier(.16,1,.3,1), opacity .25s ease;
  } }
@media (min-width: 1024px) { #stage[data-site] .masthead [data-header-searchbar] {
    display:flex; max-width:0; opacity:0; overflow:hidden; pointer-events:none;
    transition:max-width .38s cubic-bezier(.16,1,.3,1), opacity .25s ease;
  } }

  @media (min-width: 760px) and (max-width: 1023.98px) { #stage[data-site] .masthead.header-search-open [data-top-nav] {
    max-width:0; opacity:0; pointer-events:none;
  } }
@media (min-width: 1024px) { #stage[data-site] .masthead.header-search-open [data-top-nav] {
    max-width:0; opacity:0; pointer-events:none;
  } }

  @media (min-width: 760px) and (max-width: 1023.98px) { #stage[data-site] .masthead.header-search-open [data-header-searchbar] {
    max-width:400px; opacity:1; pointer-events:auto;
  } }
@media (min-width: 1024px) { #stage[data-site] .masthead.header-search-open [data-header-searchbar] {
    max-width:400px; opacity:1; pointer-events:auto;
  } }

  @media (hover:hover) {
    /* transition on :hover only -> eases in, snaps out instantly (no lingering fade) */
    .masthead .topnav-link:not(.topnav-off):hover, .masthead [data-header-search-close]:hover { color:var(--accent); transition:color 0.15s; }

  }
  /* When the hamburger menu is open, hide the top-nav + header search bar so the menu
     isn't duplicated in the header behind it. */
  .masthead.menu-open [data-top-nav], .masthead.menu-open [data-header-searchbar] { display:none !important; }

  /* Below 1280px (iPad portrait, narrow/1024 desktop) the header can't fit the
     top-nav + unfolding search bar, so fall back to the mobile chrome: hamburger
     only, search inside the menu. >=1280 keeps the full top-nav. */
  @media (max-width: 1279px) {
    @media (min-width: 760px) and (max-width: 1023.98px) { #stage[data-site] .masthead [data-top-nav], #stage[data-site] .masthead [data-header-searchbar] { display:none !important; } }
@media (min-width: 1024px) { #stage[data-site] .masthead [data-top-nav], #stage[data-site] .masthead [data-header-searchbar] { display:none !important; } }

  }
  /* Menu topic tiles: single-column list at the homepage's phone label size (the
     desktop 2-column grid would overflow the phone width). Matches index.css. */

  /* Desktop/tablet: the top-nav "Search" already provides search, so hide the
     redundant search field inside the hamburger menu. Phone keeps it. */

  .wordmark {
    font-family:var(--display);
    font-weight:500;
    font-size:21px;
    letter-spacing:.01em;
  }

  .wordmark .of {
    font-style:italic;
    font-weight:500;
    color:var(--accent);
  }

  /* Hamburger, matching the homepage header: 4 bars where the two middle bars
     scissor into an X *inside* the hamburger footprint, where the middle line
     was (cf. worksinprogress.co). Morph is driven by [data-open] on the button. */
  .hamburger { display:inline-block; position:relative; width:26px; height:18px; }

  .hamburger i {
    position:absolute; height:2px; background:currentColor; border-radius:2px;
    transition:transform .32s cubic-bezier(.2,.72,.28,1), opacity .2s ease, width .28s ease;
  }

  .hamburger i:nth-child(1) { top:1px; left:0; width:100%; }

  .hamburger i:nth-child(4) { bottom:1px; left:0; width:100%; }

  .hamburger i:nth-child(2), .hamburger i:nth-child(3) { top:50%; left:50%; width:70%; transform:translate(-50%,-50%) rotate(0deg); }

  [data-menu-toggle][data-open] .hamburger i:nth-child(1) { opacity:0; transform:translateY(4px); }

  [data-menu-toggle][data-open] .hamburger i:nth-child(4) { opacity:0; transform:translateY(-4px); }

  [data-menu-toggle][data-open] .hamburger i:nth-child(2) { width:100%; transform:translate(-50%,-50%) rotate(45deg); }

  [data-menu-toggle][data-open] .hamburger i:nth-child(3) { width:100%; transform:translate(-50%,-50%) rotate(-45deg); }

  /* ---- utility rail ---- */
  .rail {
    display:flex;
    align-items:center;
    gap:14px;
    padding:9px 16px;
    border-bottom:1px solid var(--border);
    background:var(--bg);
  }

  .rail-track {
    position:relative;
    flex:1;
    height:2px;
    background:var(--border);
    border-radius:2px;
    overflow:hidden;
  }

  .rail-fill {
    position:absolute; left:0; top:0; bottom:0;
    width:var(--pct, 0%);
    background:var(--accent);
  }

  .rail-pct {
    font-family:var(--mono);
    font-size:11px;
    color:var(--faded);
    min-width:28px;
    text-align:right;
  }

  .rail-icon {
    appearance:none; background:none; border:none; padding:0; font:inherit; cursor:pointer;
    width:17px; height:17px;
    color:var(--faded);
    display:flex; align-items:center; justify-content:center;
  }

  .rail-icon svg {width:100%; height:100%;}

  .rail-icon.active { color:var(--accent); }

  /* the text-settings trigger is a boxed serif "A" (a chip) rather than a line
     icon — the same glyph the mobile reading header uses, so the control reads
     identically across mobile and desktop. Full black border marks it as the
     one typographic control on an otherwise line-icon rail. */
  /* The text-settings mark. It was a bare letter A, which names the family of
     things it opens ("type") without saying what you can do with them. Two
     rules under the A read as lines of text being set — the A is the type, the
     rules are the setting.

     Drawn as strokes rather than as a glyph plus decoration because this is
     rendered at 23px: a version with a second column of little marks beside
     the A looked richest at 2x and turned to mud at actual size, which is the
     usual way icon sets go wrong. Two rules is what survives 23px. */
  /* "Aa", not an A over ruled lines. The chip leaves the mark 15px, and letters
     plus three lines do not both survive that: drawn together the Aa drops to
     ~6px and the lines merge into a smudge (compared at true size before this
     change). One or the other, and letterforms carry more — "Aa" says
     typography, where ruled lines say text, or list, or menu. It is also the
     convention a reader already knows from Safari and Kindle.
     Three EQUAL lines were the other candidate and lose twice over: they read
     as a block rather than as copy, and three bars sit one short of this site's
     own four-bar hamburger, a few pixels away in the same chrome. */
  .rail-icon.rail-a {
    width:23px; height:23px;
    /* --faded, matching the share icon above it. It was --fg on both border and
       glyph to mark this as the one typographic control on a line-icon rail, but
       full black beside a muted line icon reads as a state — as though it were
       already on — rather than as a distinction. The chip alone carries the
       distinction; it does not need the weight too. .active still goes accent. */
    border:1.5px solid var(--faded); border-radius:3px;
    color:var(--faded);
    font-family:var(--display); font-weight:600; font-size:12.5px; line-height:1;
  }
  .rail-icon.rail-a.active { border-color:var(--accent); color:var(--accent); }
  @media (hover:hover) { .rail-icon.rail-a:hover { border-color:var(--fg); color:var(--fg); } }

  /* ---- reading settings panel ---- */
  /* ---- panel backdrop ---- */
  .panel-backdrop {
    position:fixed; inset:0; z-index:70;
    background:rgba(0,0,0,0.3);
    opacity:0; visibility:hidden;
    transition:opacity .2s ease, visibility 0s linear .2s;
    cursor:default;
  }

  .panel-backdrop.open { opacity:1; visibility:visible; transition:opacity .2s ease, visibility 0s; }

  /* the settings bottom sheet leaves the article fully visible behind it (live
     preview of size / spacing / theme) — no darkening at all on phone/tablet.
     The transparent backdrop still catches an outside-tap to dismiss. Share keeps
     the full dim, and the desktop card keeps it too. */
  @media (max-width: 1023.98px) {
    [data-site] .panel-backdrop.scrim-light { background:transparent; }
  }

  /* Reading settings is a tweak-and-observe surface, not a one-shot action like
     the share sheet — you adjust size / spacing / theme while watching the
     article respond. So on phone/tablet it's a BOTTOM SHEET: docked to the
     bottom under the thumb, sliding up from below, with the article visible
     above it (light scrim) so changes preview live. It keeps the share sheet's
     visual language (Cormorant header, black dividers, segmented controls) — same
     family, different geometry. Desktop restores the share-matching card below. */
  .settings-panel {
    position:fixed;
    left:0; right:0; bottom:0; top:auto;
    width:auto; margin-inline:0;   /* flush edge-to-edge bottom sheet on phone AND tablet portrait */
    box-sizing:border-box;
    background:var(--bg);
    /* a plain rectangle attached to the bottom edge: a single black top rule
       separates it from the article — no rounded corners, no shadow, no scrim. */
    border:none; border-top:1px solid var(--fg);
    border-radius:0;
    box-shadow:none;
    padding:0 0 env(safe-area-inset-bottom, 0);   /* clear the home indicator */
    display:flex;
    flex-direction:column;
    gap:0;
    overflow:hidden;
    z-index:80;
    transform:translateY(100%);
    visibility:hidden;
    transition:transform .3s cubic-bezier(.2,.8,.25,1), visibility 0s linear .3s;
  }

  .settings-panel.open {
    transform:translateY(0);
    visibility:visible;
    transition:transform .32s cubic-bezier(.2,.8,.25,1), visibility 0s;
  }

  @media (prefers-reduced-motion: reduce) {
    [data-site] .settings-panel { transition:visibility 0s linear .01s; }
    [data-site] .settings-panel.open { transition:visibility 0s; }
  }

  .share-panel {
    position:fixed;
    top:68px;
    left:50%;
    transform:translateX(-50%);   /* centre in the viewport — even gutters on both sides */
    width:372px;
    max-width:calc(100vw - 24px);
    background:var(--bg);
    border:1px solid var(--fg);
    box-shadow:0 24px 60px -24px rgba(0,0,0,0.5);
    padding:0;
    display:flex;
    flex-direction:column;
    gap:0;
    /* Fit the viewport. The panel had no height bound at all, so on a phone in
       landscape it ran off both ends with overflow:hidden trapping the action
       row out of reach — you could open Share and not see Copy link or Print.
       Cap it to the space below `top` and scroll the overflow; the x axis stays
       hidden so the edge-to-edge hero is still clipped to the border. */
    max-height:calc(100vh - 84px);
    max-height:calc(100dvh - 84px);
    overflow:hidden auto;
    overscroll-behavior:contain;
    z-index:80;
    /* expand-out on open / collapse-in on close. `scale` is an independent
       transform property so it composes with the centring translate above
       without clobbering it. transform-origin at the top so it grows down from
       under the header where the share button lives. */
    transform-origin:top center;
    opacity:0; visibility:hidden; scale:.92;
    transition:opacity .18s ease, scale .2s cubic-bezier(.4,0,1,1), visibility 0s linear .2s;
  }

  .share-panel.open {
    opacity:1; visibility:visible; scale:1;
    transition:opacity .16s ease, scale .24s cubic-bezier(.2,.8,.25,1), visibility 0s;
  }
  @media (prefers-reduced-motion: reduce) {
    [data-site] .share-panel { scale:1; transition:opacity .16s ease, visibility 0s linear .16s; }
    [data-site] .share-panel.open { transition:opacity .16s ease, visibility 0s; }
  }

  /* Short viewports (a phone in landscape, a small screen at large text): the
     68px drop is most of the room, so pull the panel up to a plain gutter and
     let it use the full height. */
  @media (max-height: 620px) {
    [data-site] .share-panel {
      top:16px;
      max-height:calc(100vh - 32px);
      max-height:calc(100dvh - 32px);
    }
  }

  /* desktop / landscape: rail, TOC, panels and backdrop all live inside the
     sticky .overlay-layer, so they pin to whichever scrollport is active
     (the window on desktop, the bezel in landscape) with no JS. */
  @media (min-width: 1024px) { [data-site] .settings-panel, [data-site] .share-panel {
    position:absolute;
    left:50%;
    right:auto;
    top:50%;
    transform:translate(-50%, -50%);   /* centred modal in the viewport */
    transform-origin:center;           /* scale-in grows from the centre */
  } }
  /* 392px was sized around a 120px square and a title measure of 188px, which
   forced the title to break where it did not need to and ran the dek to five
   lines in a column narrower than the icons under it. 520px is prominent
   without becoming a page: the art is legible, the title breaks on its own
   terms, and the dek reads as two or three lines rather than a column. */
  @media (min-width: 1024px) { [data-site] .share-panel { width:520px; } }
    @media (min-width: 1024px) { [data-site] .share-thumb { width:140px; height:140px; margin-left:20px; } }
  /* The dek now runs three lines instead of five, so the text block ends well
     above the square's bottom edge and the 32px gap that used to sit under a
     five-line column became a hole. The tooltip above the first icon still
     clears at 20px. */
  @media (min-width: 1024px) { [data-site] .share-social { margin-top:20px; } }
  @media (min-width: 1024px) { [data-site] .share-card-title { font-size:21px; } }
  @media (min-width: 1024px) { [data-site] .share-card-dek { font-size:14px; } }

  /* tablet: show the share panel as a centred modal (like desktop) rather than
     the phone's top-anchored sheet, and a touch wider to suit the larger screen.
     (It stays position:fixed here — panels aren't re-homed into the overlay layer
     until the 1024 desktop breakpoint — so top:50% centres it in the viewport.) */
  @media (min-width: 760px) and (max-width: 1023.98px) {
    [data-site] .share-panel {
      top:50%;
      transform:translate(-50%, -50%);
      transform-origin:center;
      width:540px;
      max-width:calc(100vw - 64px);
    }
  }

  /* desktop: the settings sheet DOCKS bottom-left instead of floating centred —
     its left edge flush with the reading column, its top aligned to the [A] rail
     trigger, at a constant height (set in JS). On normal laptops it sits snug
     above the page floor; taller screens just leave space below. The article
     stays fully visible (no dim); the scale-in unfolds from the top-left corner
     nearest the icon. */
  @media (min-width: 1024px) {
    [data-site] .settings-panel {
      left:calc(clamp(90px, calc(var(--vw) * .08), 130px) + 20px);
      right:auto;
      top:auto; bottom:24px;              /* JS sets top (icon) + bottom; this is the no-JS fallback */
      transform:none;
      transform-origin:top left;
      margin:0;
      width:464px; max-width:calc(100vw - 24px);
      border:1px solid var(--fg);
      border-radius:0;
      box-shadow:none;
      display:flex; flex-direction:column;
      opacity:0; scale:.96;
      transition:opacity .18s ease, scale .2s cubic-bezier(.4,0,1,1), visibility 0s linear .2s;
    }
    [data-site] .settings-panel.open {
      opacity:1; scale:1;
      transition:opacity .16s ease, scale .24s cubic-bezier(.2,.8,.25,1), visibility 0s;
    }
    /* four controls in a 2×2 grid; the grid grows to fill the docked panel so the
       tiles gain breathing room rather than leaving dead space.
       Text size | Line spacing over Width | Page theme, split by hairlines. */
    [data-site] .settings-grid {
      display:grid; grid-template-columns:1fr 1fr; grid-template-rows:1fr 1fr;
      flex:1 1 auto;
    }
    [data-site] .settings-grid .settings-row { padding:12px 20px 13px; justify-content:center; }
    [data-site] .settings-grid .settings-row + .settings-row { border-top:none; }
    [data-site] .settings-grid .settings-row:nth-child(1),
    [data-site] .settings-grid .settings-row:nth-child(3) { border-right:1px solid var(--fg); }
    [data-site] .settings-grid .settings-row:nth-child(3),
    [data-site] .settings-grid .settings-row:nth-child(4) { border-top:1px solid var(--fg); }
    [data-site] .settings-grid .settings-btn { padding:6px 5px; gap:4px; }
  }
  @media (min-width: 1024px) and (prefers-reduced-motion: reduce) {
    [data-site] .settings-panel { scale:1; transition:opacity .16s ease, visibility 0s linear .16s; }
    [data-site] .settings-panel.open { transition:opacity .16s ease, visibility 0s; }
  }

  @media (min-width: 1024px) { [data-site] .panel-backdrop {
    position:absolute;
  } }
  /* no dim for the docked settings sheet on desktop — the backdrop stays present
     (so an outside click still closes the panel) but fully transparent, leaving
     the article visible while you adjust. Share keeps its full dim. */
  @media (min-width: 1024px) { [data-site] .panel-backdrop.scrim-light {
    background:transparent;
  } }

  /* ---- share panel ---- */
  /* header bar above the image: "Share" label + close button */
  .share-topbar {
    display:flex; align-items:center; justify-content:space-between;
    padding:12px 14px 12px 18px;
    border-bottom:1px solid var(--fg);
  }
  .share-topbar-label {
    display:inline-flex; align-items:center; gap:10px;
    font-family:var(--display); font-style:italic; font-weight:600;
    font-size:20px; line-height:1; color:var(--fg);
  }

  /* The same accent square the article kicker and the footnotes heading use.
     The panel had no accent at all once the byline came out of the card, and
     this is where the site already puts it: marking a heading, not filling one.
     Colouring the label itself was the alternative and it fights the article
     title below for attention — the accent should point at things. */
  /* 10px, not the kicker's 6px: that square marks a 12px mono label and this one
     marks a 20px heading, so matching the pixel size would have matched the
     wrong thing. Matching the ratio instead keeps it reading as the same mark.
     12px reaches the cap-height of the italic S and turns into a block. */
  .share-topbar-dot {
    flex:none; width:10px; height:10px;
    background:var(--accent);
  }
  .share-close {
    width:28px; height:28px; padding:0;
    appearance:none; border:none; background:none; cursor:pointer;
    color:var(--fg);
    display:flex; align-items:center; justify-content:center;
    transition:color .12s;
  }
  @media (hover:hover) { .share-close:hover { color:var(--accent); } }
  .share-close:active { color:var(--accent); }
  .share-close svg { width:17px; height:17px; }

  /* edge-to-edge hero image */
  /* Square thumbnail beside the text rather than a full-bleed band above it,
     the same shape the archive rows use on phone. The band cost 184px of height
     to say what a 92px square says, and buying that height back is what lets the
     panel be narrower — the title now has room to wrap instead of needing the
     width to stay on one or two lines. */
  .share-card-row::after { content:""; display:block; clear:both; }

  /* Floated, not a grid column. At 92px the square ended high in the dek and a
     float bought nothing, so this was a two-column grid; at 120px its bottom
     edge lands mid-paragraph, and letting the remaining lines run the full
     measure takes the card from 368px back to 339px. The bigger square is worth
     that: at 92px the art was a coloured chip, at 120px you can read it. */
  .share-thumb {
    float:right;
    margin:3px 0 6px 16px;
    position:relative; width:120px; height:120px;
    overflow:hidden; background:var(--border);
    border:1px solid var(--imgline);
  }
  .share-thumb img { position:absolute; inset:0; width:100%; height:100%; object-fit:cover; display:block; }

  /* title + dek + social icons */
  .share-head { padding:18px 20px 16px; }
  /* Unclamped for the same reason as the dek: the column is narrower now, so a
     3-line cap that never fired against a full-width card would start clipping
     the longer titles. The card grows instead. */
  /* 19px, not the 21px this started at: the 120px square leaves the title a
     188px measure, and at 21px the longest of the nine ("...Future of Credit
     Scores") broke to three lines with one word stranded on the last. */
  .share-card-title {
    font-family:var(--display); font-weight:600;
    font-size:19px; line-height:1.16; letter-spacing:-.01em; color:var(--fg);
  }
  /* The dek, not a byline. This card is a preview of what the recipient gets,
     and og:description is the dek — the author appears nowhere in the shared
     card, and on a single-author site the name is the same on all nine pieces,
     so it identified nothing for the one person who sees this panel. Quiet
     serif so the title still leads. Not clamped: a truncated dek reads as a
     mistake in a card whose whole job is to show you the thing you're about to
     post. The panel absorbs the extra height instead (see its max-height). */
  .share-card-dek {
    font-family:var(--serif);
    font-size:13.5px; line-height:1.45; letter-spacing:.01em;
    color:var(--faded); margin-top:8px;
  }

  /* social icons — meta-line treatment (no container), larger. justify-between
     with the container pulled out by the icons' inner padding so the first and
     last glyphs sit flush with the title's left/right edges. The [data-site]
     prefix keeps these winning over the mobile .ml-share size override. */
  .share-social {
    display:flex; align-items:center; justify-content:space-between;
    /* generous top gap so the hover tooltip clears the dek above */
    margin:32px -7px 0;
  }
  /* touch has no hover tooltip, so the row can sit closer to the byline */
  @media (hover: none) { [data-site] .share-social { margin-top:16px; } }
  [data-site] .share-social .ml-share { width:auto; height:42px; padding:0 7px; }
  [data-site] .share-social .ml-share svg { width:26px; height:26px; }
  /* the hover tooltips are centred on each icon, so the first (X) and last
     (More) would overflow the panel and get clipped by its overflow:hidden.
     Anchor the edge tooltips to their own edge instead of the centre. */
  [data-site] .share-social .ml-share:first-child::after { left:0; right:auto; transform:translateX(0) translateY(3px); }
  [data-site] .share-social .ml-share:first-child:hover::after { transform:translateX(0) translateY(0); }
  [data-site] .share-social .ml-share:last-child::after { left:auto; right:0; transform:translateX(0) translateY(3px); }
  [data-site] .share-social .ml-share:last-child:hover::after { transform:translateX(0) translateY(0); }
  /* touch devices have no real hover — tapping an icon acts immediately, so the
     tooltip only flashes and clips. Drop it there; keep it for mouse/desktop. */
  @media (hover: none) { [data-site] .share-social .ml-share::after { display:none; } }

  /* action row — four edge-to-edge cells split by black dividers */
  .share-actions {
    display:flex; align-items:stretch;
    border-top:1px solid var(--fg);
  }
  .share-cell {
    /* Basis auto, not 0, and no min-width:0.
       flex:1 is flex:1 1 0% — the basis is zero, so the whole row is carved into
       equal shares and a label's own width counts for nothing. That gave "Print"
       (50px of text) the same cell as "Download PDF" (102px), and clipped the
       latter mid-word on narrow phones once "Copy .md" made it four cells.
       With the basis at auto each cell starts at its label and the leftover is
       shared on top, so the cells read in proportion to what they say and no
       label can be clipped — including by a fifth cell, which is how this
       started. */
    flex:1 1 auto;
    display:flex; align-items:center; justify-content:center;
    padding:15px 6px;
    font-family:var(--mono); font-size:12px; letter-spacing:.01em; line-height:1;
    /* Fragment Mono's space is a full advance — 7.55px at this size — so
       "Copy .md" and "Copy link" read as two words with a gap rather than one
       label. Pull it back to roughly a proportional font's space. */
    word-spacing:-2px;
    color:var(--fg); background:none; border:none; cursor:pointer;
    text-decoration:none; white-space:nowrap;
    transition:color .12s, background .12s;
  }
  /* Below ~375 the four labels genuinely do not fit at 12px with 6px of side
     padding — 307px of content into a 294px panel. Trim the padding, not the
     words: "Download PDF" says what it does and "PDF" would not. */
  @media (max-width: 374.98px) { .share-cell { padding-left:3px; padding-right:3px; } }
  /* A floor for the short cell. Basis auto hands every cell the same slack on
     top of its label, so the widths track the text exactly — which left "Print"
     (37.7px of text against "Download PDF"'s 90.4) looking starved next to
     neighbours twice its size. The floor lifts only Print; the others shrink
     into it in proportion. Not below 375, where there is no spare width to give
     and forcing it would push a longer label back into clipping. */
  @media (min-width: 375px) { .share-cell { min-width:6.2em; } }
  .share-cell + .share-cell { border-left:1px solid var(--fg); }
  @media (hover:hover) { .share-cell:hover { color:var(--accent); background:color-mix(in srgb, var(--accent) 7%, transparent); } }
  .share-cell:active { color:var(--accent); background:color-mix(in srgb, var(--accent) 7%, transparent); }
  .share-cell.copied { color:var(--accent); }

  /* header bar — share sheet's top-bar frame (Cormorant label, matching close
     button, black underline), but UPRIGHT: settings is a utilitarian control
     surface closer in spirit to the reader bar than to the editorial share
     sheet, whose header stays italic. */
  .settings-panel-head {
    display:flex; align-items:center; justify-content:space-between;
    padding:12px 14px 12px 20px;
    border-bottom:1px solid var(--fg);
  }

  .settings-panel-title {
    font-family:var(--display); font-style:normal; font-weight:600;
    font-size:20px; line-height:1; letter-spacing:normal;
    color:var(--fg);
  }

  .settings-panel-close {
    width:28px; height:28px; padding:0;
    appearance:none; background:none; border:none; cursor:pointer;
    color:var(--fg);
    display:flex; align-items:center; justify-content:center;
    transition:color .12s;
  }
  @media (hover:hover) { .settings-panel-close:hover { color:var(--accent); } }
  .settings-panel-close:active { color:var(--accent); }

  .settings-panel-close svg { width:17px; height:17px; }

  /* rows — generous padding, split by edge-to-edge black hairlines (the share
     sheet's divider language) rather than short inset grey rules. */
  .settings-row {
    display:flex; align-items:center; justify-content:space-between; gap:16px;
    padding:14px 20px;
  }
  .settings-row + .settings-row { border-top:1px solid var(--fg); }

  .settings-label {
    font-family:var(--mono);
    font-size:11px; letter-spacing:.02em;
    color:var(--faded);
    flex-shrink:0;
  }

  /* Size / Line spacing / Width / Theme — a joined segmented control in the black line
     language; the selected segment fills solid (fg ground, bg text) like a
     pressed key, echoing the share sheet's edge-to-edge black cells. */
  .settings-btns { display:flex; border:1px solid var(--fg); }

  .settings-btn {
    appearance:none; background:none; cursor:pointer;
    font-family:var(--mono); font-size:10px; letter-spacing:.03em;
    color:var(--faded);
    border:none; padding:6px 11px;
    transition:background .15s, color .15s;
  }
  .settings-btn + .settings-btn { border-left:1px solid var(--fg); }
  @media (hover:hover) { .settings-btn:hover { color:var(--fg); } }
  .settings-btn:active { color:var(--fg); }

  .settings-btn.active, .settings-btn.active:hover { background:var(--fg); color:var(--bg); }

  /* Visual-aid tiles (A glyph / line-gap icon / width measure / theme swatch) are
     shown at ALL widths — desktop modal and mobile sheet alike. Each control is a
     full-width row of preview tiles: the label sits above, the tiles below show
     what the option does. */
  [data-site] .settings-row { flex-direction:column; align-items:stretch; gap:8px; }
  [data-site] .settings-label { align-self:flex-start; }
  [data-site] .settings-btns { width:100%; }
  [data-site] .settings-btn {
    flex:1 1 0; min-width:0;
    display:flex; flex-direction:column; align-items:center; justify-content:center; gap:6px;
    padding:9px 5px;
  }
  .sb-cap { font-family:inherit; font-size:9px; letter-spacing:.04em; }
  .sb-visual { display:flex; align-items:center; justify-content:center; min-height:19px; color:inherit; }

  /* Text size — the letter A at each option's actual step */
  [data-site] .sb-a { font-family:var(--display); font-weight:600; line-height:1; }
  [data-site] .sb-a-s { font-size:13px; }
  [data-site] .sb-a-m { font-size:16px; }
  [data-site] .sb-a-l { font-size:20px; }

  /* Line spacing (widening rules) and Width (widening column of rules) share the
     line-glyph treatment. Page theme — a chip of the theme's own paper colour. */
  [data-site] .sb-lines { width:22px; height:18px; stroke:currentColor; stroke-width:2; stroke-linecap:round; }
  /* Width — a capped dimension line (measure) that widens across the options,
     distinct from Line spacing's stacked rules. */
  [data-site] .sb-measure { width:24px; height:18px; stroke:currentColor; stroke-width:1.8; stroke-linecap:round; }
  [data-site] .sb-sw { width:24px; height:16px; border-radius:3px; border:1px solid currentColor; }

  /* mobile bottom sheet: tighten padding so the sheet stays short */
  @media (max-width: 1023.98px) {
    [data-site] .settings-panel-head { padding:10px 12px 10px 18px; }
    [data-site] .settings-row { gap:7px; padding:9px 18px; }
    [data-site] .settings-btn { padding:7px 5px; gap:5px; }
  }

  .settings-switch {
    appearance:none; width:38px; height:22px; border-radius:11px;
    border:1px solid var(--fg); background:transparent;
    position:relative; cursor:pointer; flex-shrink:0;
    transition:background .15s, border-color .15s;
  }

  .settings-switch::after {
    content:''; position:absolute; top:2px; left:2px;
    width:16px; height:16px; border-radius:50%;
    background:var(--fg); transition:transform .15s, background .15s;
  }

  .settings-switch[aria-checked="true"] { background:var(--accent); border-color:var(--accent); }

  .settings-switch[aria-checked="true"]::after { transform:translateX(16px); background:var(--bg); }

  /* ---- TOC strip (docked under rail) ---- */
  .toc-strip {
    border-bottom:1px solid var(--border);
    background:var(--bg);
  }

  .toc-strip-head {
    appearance:none; background:none; border:none; font:inherit; color:inherit;
    width:100%; text-align:left;
    display:flex;
    align-items:center;
    justify-content:space-between;
    /* mobile/tablet: match the utility rail's compact height (the head is
       display:none on desktop). Tight vertical padding drops the section bar
       from ~48px to ~36px, level with the rail above it. */
    padding:7px 20px;
    cursor:pointer;
  }

  .toc-strip-active {
    font-family:var(--serif);
    font-size:16px;
    font-weight:700;
    color:var(--fg);
    line-height:1.3;
    /* never wrap: force one line and ellipsis a too-long section name. display:block
       so text-overflow applies; the flex parent (min-width:0) constrains the width. */
    display:block;
    white-space:nowrap;
    overflow:hidden;
    text-overflow:ellipsis;
  }

  .toc-chevron {
    width:14px; height:14px;
    color:var(--faded);
    flex-shrink:0;
    margin-left:12px;
    transition:transform .2s ease;
  }

  .toc-strip.open .toc-chevron {transform:rotate(180deg);}

  /* Mobile: the progress rail + section bar live in the sticky header but stay
     hidden while you're still on the intro (kicker / hero / title / byline).
     They slide + fade in only once the byline has scrolled up under the
     masthead — the same gate as desktop, and the IFP-style "the section bar
     appears as you begin reading" pattern. onScroll() toggles the -visible
     classes on every viewport now. */
  /* Hidden section bar (phone/tablet): display:none so it appears and disappears
     INSTANTLY, in lockstep with the display-swapped reading header above it —
     no max-height/opacity/transform reveal that would let the black bar slide in
     over the masthead or visibly recede on scroll-up. The tap-to-expand animation
     lives on the shown state (max-height only). */
  @media (max-width: 759.98px) { [data-site] .rail, [data-site] .toc-strip {
    display:none;
  } }
@media (min-width: 760px) and (max-width: 1023.98px) { [data-site] .rail, [data-site] .toc-strip {
    display:none;
  } }

  /* the rail carries its own vertical padding (content-box), so collapse that
     too or it leaves an ~18px sliver when "hidden". */
  @media (max-width: 759.98px) { [data-site] .rail {
    padding-top:0;
    padding-bottom:0;
  } }
@media (min-width: 760px) and (max-width: 1023.98px) { [data-site] .rail {
    padding-top:0;
    padding-bottom:0;
  } }

  @media (max-width: 759.98px) { [data-site] .rail.rail-visible {
    max-height:60px;
    opacity:1;
    transform:none;
    border-bottom-width:1px;
    border-bottom-color:var(--border);
    pointer-events:auto;
    padding-top:9px;
    padding-bottom:9px;
  } }
@media (min-width: 760px) and (max-width: 1023.98px) { [data-site] .rail.rail-visible {
    max-height:60px;
    opacity:1;
    transform:none;
    border-bottom-width:1px;
    border-bottom-color:var(--border);
    pointer-events:auto;
    padding-top:9px;
    padding-bottom:9px;
  } }

  @media (max-width: 759.98px) { [data-site] .toc-strip.toc-visible {
    max-height:66px;
    opacity:1;
    transform:none;
    border-bottom-width:1px;
    border-bottom-color:var(--border);
    pointer-events:auto;
  } }
@media (min-width: 760px) and (max-width: 1023.98px) { [data-site] .toc-strip.toc-visible {
    max-height:66px;
    opacity:1;
    transform:none;
    border-bottom-width:1px;
    border-bottom-color:var(--border);
    pointer-events:auto;
  } }

  @media (max-width: 759.98px) { [data-site] .toc-strip.toc-visible.open {
    max-height:480px;
  } }
@media (min-width: 760px) and (max-width: 1023.98px) { [data-site] .toc-strip.toc-visible.open {
    max-height:480px;
  } }

  /* reading percentage reads as an editorial figure, not a mono readout */
  @media (max-width: 759.98px) { [data-site] .rail-pct {
    font-family:var(--serif);
    font-size:13px;
    letter-spacing:0;
  } }
@media (min-width: 760px) and (max-width: 1023.98px) { [data-site] .rail-pct {
    font-family:var(--serif);
    font-size:13px;
    letter-spacing:0;
  } }

  /* ============================================================================
     Reading-state header (phone/tablet, <=1023.98px)
     Scroll down into the body -> the wordmark gives way to the article title +
     inline share, with a reading-settings "Aa" on the right. The section bar
     below carries the current section, the % readout and a vermilion hairline.
     Scroll up -> the wordmark returns. Desktop is untouched (side rail + TOC).
     ============================================================================ */
  .read-head { display:none; }            /* off on desktop and until "reading" */

  @media (max-width: 1023.98px) {
    /* the progress rail is dissolved on phone/tablet: its track becomes the
       hairline, its % moves into the section bar, its tools move to the header */
    [data-site] .rail { display:none !important; }

    /* reading state: swap the wordmark for the title/tools row */
    [data-site] .stack.reading .masthead { display:none !important; }
    [data-site] .stack.reading .read-head { display:flex; }

    /* When the share sheet dissolves the reading header back to the wordmark,
       crossfade the two in place. They're the same height (~66px), so the
       masthead fades in in flow while the read-head fades out overlaid on top of
       it — both on screen at once, so it reads as the text/symbols swapping
       rather than a blink through empty. (see restingReturnWordmark) */
    @keyframes wos-hdr-in  { from { opacity:0; } to { opacity:1; } }
    @keyframes wos-hdr-out { from { opacity:1; } to { opacity:0; } }
    [data-site] .stack.hdr-xfade .masthead { display:flex; animation:wos-hdr-in .24s ease both; }
    [data-site] .stack.hdr-xfade .read-head {
      display:flex !important;
      position:absolute; left:0; right:0; top:0; z-index:2;
      pointer-events:none;
      animation:wos-hdr-out .24s ease both;
    }
    @media (prefers-reduced-motion: reduce) {
      [data-site] .stack.hdr-xfade .masthead,
      [data-site] .stack.hdr-xfade .read-head { animation-duration:.01s; }
    }

    /* title (left, one line — reader.js shrinks the font so the whole title fits
       without wrapping or an ellipsis) · share + [A] grouped at the right. Its
       1px bottom divider is also the reading-progress TRACK — see the ::after
       below. */
    [data-site] .read-head {
      align-items:center;
      gap:12px;
      /* positioned host for the progress bar. .hdr-xfade sets position:absolute
         on this same element and outranks this rule; that is still a positioned
         ancestor, so the bar survives the crossfade. */
      position:relative;
      /* match the phone masthead exactly (min-height:66px, no vertical padding) so
         the wordmark<->read-head morph swap never changes the sticky header's
         height — a mismatch shows through as jitter on scroll. */
      min-height:66px;
      padding:0 16px;
      background:var(--bg);
      border-bottom:1px solid var(--fg);
    }
    /* tablet masthead is taller (min-height:80px) — match it so the morph swap
       stays height-stable there too. */
    @media (min-width: 760px) { [data-site] .read-head { min-height:80px; } }
    [data-site] .read-title {
      flex:1; min-width:0;
      /* the site's display serif (Cormorant Garamond), upright. Fixed size —
         reader.js trims the trailing space before the ellipsis but never
         rescales; the CSS clip below is the no-JS fallback. */
      font-family:var(--display); font-weight:600; font-style:normal; font-size:19px; line-height:1.15;
      color:var(--fg);
      white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
    }
    /* the two controls, grouped and compact */
    [data-site] .read-actions { flex:none; display:flex; align-items:center; gap:6px; }
    [data-site] .read-share, [data-site] .read-settings {
      appearance:none; background:none; cursor:pointer; padding:0;
      flex:none; width:26px; height:26px;
      border:1.5px solid var(--fg); border-radius:3px; color:var(--fg);
      display:flex; align-items:center; justify-content:center;
    }
    [data-site] .read-share svg { width:13px; height:13px; display:block; }
    /* see .rail-a — 13.5px here for the taller 26px chip. */
    [data-site] .read-settings { font-family:var(--display); font-weight:600; font-size:13.5px; line-height:1; }
    /* .active is toggled while the settings sheet is open */
    [data-site] .read-settings.active { border-color:var(--accent); color:var(--accent); }

    /* section bar: gated on POSITION (in the article body), independent of the wordmark<->title morph, so it stays a constant height while the header swaps */
    [data-site] .stack.in-body .toc-strip {
      display:block;
      position:relative;
      max-height:66px;
      /* visible, so the expanded list can hang BELOW this box instead of
         growing it — see the next rule for why that matters */
      overflow:visible;
      pointer-events:auto;
    }
    /* THE EXPANDED LIST OVERLAYS, IT DOES NOT PUSH.
       This used to open by taking the strip's max-height from 66 to 480, which
       grew the sticky header by ~250px IN FLOW. Two things followed, and the
       second one was the visible bug:
         - the browser scroll-anchored to keep the text still, so the scroll
           POSITION moved by the same ~250px every time you tapped the chevron;
         - closing it shrank the header again, which anchored the scroll UPWARD,
           and the reading header reads sustained upward travel as "the reader
           wants to navigate" — so it dropped out of reading mode, the section
           bar vanished and the wordmark came back, until you scrolled again.
       Measured before the fix: tap open, scrollY 2037 -> 2284; tap closed,
       2284 -> 2000 with `reading in-body` gone from the stack.
       Overlaid, the header's flow height never changes, so there is nothing for
       the browser to anchor against and nothing for the direction logic to
       misread. The open/close animation is unchanged — .toc-full already
       transitions its own max-height, which is what was doing the visible work
       all along. */
    [data-site] .stack.in-body .toc-strip .toc-full {
      position:absolute; left:0; right:0; top:100%;
      background:var(--bg);
      z-index:40;
      /* No borders while closed. Overlaid at top:100% the panel sits ON the
         strip's own bottom rule, and a max-height:0 box still draws its
         borders — so a closed bar rendered the strip's 1px plus the panel's
         top AND bottom, three rules deep, against one at the top of the bar.
         That is the "bottom line is thicker" report, and it arrived with the
         overlay. The strip's border-bottom is the boundary; the panel only
         needs an edge of its own once it is open and has height. */
      border-top:0; border-bottom:0;
    }
    [data-site] .stack.in-body .toc-strip.open .toc-full {
      border-bottom:1px solid var(--fg);
    }
    /* the section bar's own bottom rule matches the black reading-header lines,
       not the gray divider */
    [data-site] .stack.in-body .toc-strip { border-bottom-color:var(--fg); }

    /* section name fills the row, chevron sits at the right */
    [data-site] .toc-strip-head { position:relative; gap:10px; justify-content:flex-start; }
    [data-site] .toc-strip-head > div { flex:1; min-width:0; }
    /* Reading progress: an accent bar CENTRED ON the rule under the reading
       header, so it thickens a line the header already draws instead of adding
       a band of its own.
       bottom:-2px with height:3px puts 1px above the 1px border and 1px below
       it — absolute offsets are relative to the padding box, so bottom:0 is the
       border's top edge. The border is the track: red as far as progress has
       run, hairline the rest of the way, and the two meet on the same optical
       centre. Nothing is added to the header's height and there is no second
       element to keep in step.
       Hung off .read-head rather than #toc-strip on purpose: the section bar is
       overflow:hidden for its tap-to-expand max-height animation, which would
       clip a bar sitting on its top edge.
       This replaced an inverted fill of the whole section bar — a clipped
       duplicate of the row painted in --bg on --fg and revealed to --pct. It
       read as the loudest thing in the header and inverted the section label
       mid-sentence while you were using it to see where you were. The bar says
       the same thing without taking the label hostage.
       --pct is set on .stack by reader.js (it used to be set on #toc-strip,
       which is this element's SIBLING and so could not reach it). */
    [data-site] .stack.reading .read-head::after {
      content:''; position:absolute; left:0; bottom:-2px;
      width:var(--pct, 0%); height:3px;
      background:var(--accent);
      z-index:6; pointer-events:none;
      transition:width .12s linear;
    }
    @media (prefers-reduced-motion: reduce) {
      [data-site] .stack.reading .read-head::after { transition:none; }
    }
  }

  .toc-full {
    max-height:0;
    overflow:hidden;
    transition:max-height .25s ease;
    border-top:1px solid var(--border);
  }

  .toc-strip.open .toc-full {
    max-height:360px;
    overflow-y:auto;
  }

  .toc-rail-wrap {
    position:relative;
    padding:14px 20px 18px 18px;
  }

  .toc-rail-segments {
    position:absolute;
    inset:0;
    pointer-events:none;
  }

  .toc-rail-tick {
    position:absolute;
    width:2px;
    background:var(--border);
  }

  .toc-list {
    list-style:none;
    margin:0;
    padding:0;
    position:relative;
  }

  .toc-list li {
    font-family:var(--serif);
    font-size:15px;
    color:var(--faded);
    padding:7px 0;
    display:flex;
    align-items:center;
    gap:7px;
    cursor:pointer;
    position:relative;
  }

  /* the TOC item is a real anchor for native/keyboard navigation; display:contents
     removes its own box so the dot/label/chevron keep participating in the li's
     flex layout exactly as before. */
  .toc-list li > a {
    display:contents;
    color:inherit;
    text-decoration:none;
  }

  .toc-list li.overview {
    font-weight:700;
    color:var(--fg);
    padding-bottom:13px;
    padding-left:0;
    margin-bottom:5px;
    border-bottom:1px solid var(--border);
  }

  .toc-list li.sub {
    align-items:center;
    padding-left:30px;
    font-size:14px;
    overflow:hidden;
    max-height:0;
    padding-top:0;
    padding-bottom:0;
    transition:max-height .22s ease, padding .22s ease, opacity .18s ease;
    opacity:0;
  }

  .toc-list li.sub.open {
    max-height:60px;
    padding-top:7px;
    padding-bottom:7px;
    opacity:1;
  }

  .toc-list li.active {
    color:var(--fg);
    font-weight:600;
  }

  .toc-dot {
    width:7px; height:7px;
    border-radius:50%;
    border:1.5px solid var(--faded);
    background:var(--bg);
    flex-shrink:0;
    align-self:center;
    transition:background .15s ease, border-color .15s ease;
    z-index:2;
  }

  .toc-list li.sub .toc-dot {
    position:static;
    width:5px; height:5px;
    border-width:1.2px;
  }

  .toc-list li.active .toc-dot {
    background:var(--accent);
    border-color:var(--accent);
  }

  .toc-mini-chevron {
    width:11px; height:11px;
    color:var(--faded);
    margin-left:auto;
    flex-shrink:0;
    transition:transform .2s ease;
  }

  .toc-list li.group.has-subs.expanded .toc-mini-chevron {
    transform:rotate(180deg);
  }

  .toc-desktop-label {
    display:none;
  }

  .overlay-layer {}

  /* the sticky overlay layer: full-scrollport tall, but its height is cancelled
     by an equal negative margin so it takes no flow space. It pins to the top
     of the active scrollport; its absolutely-positioned children (rail, TOC,
     panels) ride along, pinned, with zero JS. */
  @media (min-width: 1024px) { [data-site] .overlay-layer {
    position:sticky;
    top:0;
    height:var(--port-h);
    margin-bottom:calc(-1 * var(--port-h));
    z-index:60;
    pointer-events:none;
  } }

  @media (min-width: 1024px) { [data-site] .overlay-layer > * {
    pointer-events:auto;
  } }

  @media (min-width: 1024px) { [data-site] .overlay-layer { --port-h:100vh; } }

  @media (min-width: 1024px) { [data-site] .note { max-width:none; } }

  @media (min-width: 1024px) { [data-site] .article {
    max-width:min(var(--art-w), calc(var(--vw) - clamp(90px, calc(var(--vw) * .08), 130px) - var(--toc-w) - 40px - 28px));
    margin-left:clamp(90px, calc(var(--vw) * .08), 130px);
    margin-right:0;
    padding-top:56px;
  } }

  /* On narrow desktop the reading column can't reach its full 780px once the
     280px side-TOC is reserved, so trim the reserved TOC width there to widen
     the reading measure (the two longest TOC headings then wrap to two lines,
     which is fine). Above ~1226px the column is already full width, so keep
     the roomier TOC. --toc-w feeds both the measure and the TOC itself, so
     this stays geometrically consistent. */
  @media (max-width: 1226px) {
    @media (min-width: 1024px) { [data-site] { --toc-w: 240px; } }

  }
  /* one logical block: the masthead globe and the footer content start at the
     same left edge as the reading column's text (article margin + its 20px
     inner padding). The footer keeps a comfortable full width on the right. */
  @media (min-width: 1024px) { [data-site] .masthead {
    padding-left:calc(clamp(90px, calc(var(--vw) * .08), 130px) + 20px) !important;
  } }

  @media (min-width: 1024px) { [data-site] .rail {
    display:flex;
    flex-direction:column;
    align-items:center;
    gap:14px;
    position:absolute;
    left:calc(clamp(90px, calc(var(--vw) * .08), 130px) - 50px);
    top:50%;
    transform:translateY(-50%);
    width:auto;
    padding:0;
    border:none;
    background:transparent;
    z-index:60;
    opacity:0;
    pointer-events:none;
  } }

  @media (min-width: 1024px) { [data-site] .rail.rail-visible {
    opacity:1;
    pointer-events:auto;
    transition:opacity .3s ease;
  } }

  @media (min-width: 1024px) { [data-site] .rail-track {
    width:2px;
    height:90px;
    flex:none;
  } }

  @media (min-width: 1024px) { [data-site] .rail-fill {
    left:0; right:0; bottom:auto; top:0;
    width:auto;
    height:var(--pct, 0%);
  } }

  @media (min-width: 1024px) { [data-site] .rail-pct {
    /* fixed width so the % readout (widest child in the centred rail) doesn't
       grow the rail and re-centre everything as the digits change (5% -> 100%) —
       that re-centring was the horizontal "shake" on scroll. */
    min-width:34px;
    width:34px;
    text-align:center;
    order:-1;
  } }

  @media (min-width: 1024px) { [data-site] .toc-strip {
    position:absolute;
    left:min(calc(clamp(90px, calc(var(--vw) * .08), 130px) + var(--art-w) + 36px), calc(var(--vw) - var(--toc-w) - 28px));
    top:112px;
    transform:none;
    width:var(--toc-w);
    max-height:calc(var(--port-h, 100vh) - 150px);
    overflow-y:auto;
    overflow-x:hidden;
    border:none;
    background:transparent;
    z-index:60;
    opacity:0;
    pointer-events:none;
  } }

  @media (min-width: 1024px) { [data-site] .toc-strip.toc-visible {
    opacity:1;
    pointer-events:auto;
    transition:opacity .3s ease;
  } }

  @media (min-width: 1024px) { [data-site] .toc-strip-head { display:none; } }

  @media (min-width: 1024px) { [data-site] .toc-desktop-label {
    display:block;
    font-family:var(--mono);
    font-size:13px;
    letter-spacing:.04em;
    color:var(--faded);
    margin-bottom:12px;
  } }

  @media (min-width: 1024px) { [data-site] .toc-list li.overview { display:none; } }

  /* ---- collapsed contents ----
     The Contents column is useful and permanent, which on a long read also means
     it is permanently taking a column of attention beside the prose. Collapsed,
     it becomes one notch per section: still a map of the piece, still showing
     where you are, in a fraction of the ink. Hovering brings the list back over
     the top without moving the article, so nothing reflows to read it and
     nothing reflows when you leave.

     Notches rather than an invisible edge: a hover target you cannot see is a
     control only someone who already knows about it can use.

     The notches are spans, not buttons. Hovering the strip is what reveals the
     list, so a clickable notch is a target that opens something over itself the
     instant you reach for it — the list is the thing you click. */
  .toc-desktop-head { display:none; }
  @media (min-width: 1024px) {
    [data-site] .toc-desktop-head { display:flex; align-items:center; gap:10px; margin-bottom:12px; }
    [data-site] .toc-desktop-label { margin-bottom:0 !important; }
  }

  @media (min-width: 1024px) { [data-site] .toc-full {
    max-height:none !important;
    overflow:visible !important;
    border-top:none;
    transition:none;
  } }

  @media (min-width: 1024px) { [data-site] .toc-rail-wrap { padding:0 0 0 18px; } }

  /* Below ~820px there isn't room for an 800px article measure plus the side
     TOC, so hide the desktop TOC rather than let it overflow — mirrors the real
     WoS Article, which drops the rail below 920px. */
  @media (max-width:820px) {
    @media (min-width: 1024px) { [data-site] .toc-strip { display:none !important; } }

  }

  /* ---- article body ---- */
  .article {
    padding:28px 20px 80px;
  }

  /* category kicker: an outlined box that links to its topic page and fills to a
     solid accent chip on hover (the site's outline->fill language, as on the
     settings segments and the Subscribe button). Sentence case, not uppercase. */
  .kicker {
    display:inline-flex;
    align-items:center;
    gap:8px;
    font-family:var(--mono);
    font-size:12px;
    font-weight:500;
    letter-spacing:.01em;
    color:var(--fg);
    text-decoration:none;
    border:1px solid var(--fg);
    padding:2.5px 12px;
    margin-bottom:16px;
    transition:background .15s ease, color .15s ease, border-color .15s ease;
  }
  /* accent node beside the label; turns white on the filled chip so it stays visible */
  .kicker-dot {
    flex:none; width:6px; height:6px;
    background:var(--accent);
    transition:background .15s ease;
  }
  @media (hover:hover) {
    .kicker:hover { background:var(--accent); border-color:var(--accent); color:#fff; outline:none; }
    .kicker:hover .kicker-dot { background:#fff; }
  }
  .kicker:active, .kicker:focus-visible {
    background:var(--accent);
    border-color:var(--accent);
    color:#fff;
    outline:none;
  }
  .kicker:active .kicker-dot, .kicker:focus-visible .kicker-dot { background:#fff; }

  h1.headline { font-family:var(--reader-heading-font, var(--display));
    font-family:var(--display);
    font-size:34px;
    font-weight:var(--wt-display);
    line-height:1.12;
    margin:0 0 18px;
  }

  /* larger title on desktop/landscape; give it the full content-block width
     (same breakout as the hero) so long titles stay on one line, left-aligned. */
  @media (min-width: 1024px) { [data-site] h1.headline {
    font-size:48px;
    width:min(
      calc(var(--art-w) + var(--toc-w) + 16px),
      calc(var(--vw) - clamp(90px, calc(var(--vw) * .08), 130px) - 48px)
    );
    max-width:none;
  } }

  /* subtitle/dek: larger on desktop and matched to the title's width */
  @media (min-width: 1024px) { [data-site] #h-overview {
    font-size:28px !important;
    line-height:1.35 !important;
    width:min(
      calc(var(--art-w) + var(--toc-w) + 16px),
      calc(var(--vw) - clamp(90px, calc(var(--vw) * .08), 130px) - 48px)
    );
    max-width:none;
  } }

  .byline {
    font-family:var(--serif);
    font-size:16px;
    letter-spacing:0.03em;
    color:var(--faded);
    margin-bottom:28px;
    display:flex;
    align-items:center;
    flex-wrap:wrap;
    gap:8px 14px;
  }

  /* On wider screens give the byline the same breakout canvas as the hero and
     headline (it sits above the fold, before the TOC reveals) so the full
     facts + share row stays on one line even in a narrow reading column. */
  @media (min-width: 1024px) { [data-site] .byline {
    width:min(
      calc(var(--art-w) + var(--toc-w) + 16px),
      calc(var(--vw) - clamp(90px, calc(var(--vw) * .08), 130px) - 48px)
    );
    max-width:none;
    flex-wrap:nowrap;
  } }

  /* the fact line never breaks; each field holds one row */
  .byline-author, .byline-date, .byline-read { min-width:0; white-space:nowrap; }

  /* tablet portrait has no breakout; tighten the inter-field gap so the extra
     date/read fields still hold one line at ~768px. */
  @media (min-width: 760px) and (max-width: 1023.98px) { [data-site] .byline { column-gap:8px; } }

  /* the date/read group flows inline on desktop/tablet, and becomes its own
     row on phone (see below). */
  /* The date and the read time are metadata, and the site says metadata in
     mono — kicker, TOC labels, search, the share panel's action cells. The name
     stays in the serif because it is authorship, not a data field. */
  .byline-dt {
    display:contents;
    font-family:var(--mono);
    font-size:13px;
    letter-spacing:.01em;
  }

  /* the author name links to the author's site; a red text-underline hugs it. */
  /* No standing underline. It was a permanent accent mark on the one thing in
     the byline that needs no marking — the reader knows who wrote it — and it
     competed with the chip a few lines below. Hover and touch carry the link
     affordance instead, the way every other link on the site does. */
  .author-link {
    color:var(--fg);
    text-decoration:underline;
    text-decoration-color:transparent;
    text-decoration-thickness:1.5px;
    text-underline-offset:2px;
    transition:color .15s ease, text-decoration-color .15s ease;
  }

  @media (hover:hover) {
    .author-link:hover { color:var(--accent); text-decoration-color:var(--accent); }
  }

  .author-link:active { color:var(--accent); text-decoration-color:var(--accent); }

  /* ---- byline separators ----
     Drawn, not typed. These were a U+2022 bullet set at 14px with a -1px
     nudge, and a bullet is a glyph: its diameter, weight and height above the
     baseline are decisions Newsreader made for running text, not for a
     separator between three facts. That is why it read small and sat low, and
     why the nudge was there at all.

     A box has no opinion. 4.5px square, centred by the flex row rather than by
     a magic offset, in the accent — which also puts it in the same family as
     the end-of-article mark, the footnote node, the kicker node and the menu
     hover dot, all of which are already accent squares. One shape, used
     consistently, instead of a dot that happens to be nearby.

     The glyph stays in the markup for copy/paste and for the no-CSS case; it is
     hidden with font-size:0 rather than removed. */
  .byline .sep {
    font-size:0;
    line-height:0;
    margin:0 9px;
    align-self:center;
    flex:none;
  }
  .byline .sep::before {
    content:"";
    display:block;
    width:4.5px;
    height:4.5px;
    background:var(--accent);
  }

  /* A quiet vertical rule between the reading meta and the action. NOT a .sep —
     those are the accent squares that separate one meta item from the next, and
     the button is not a meta item. Hidden below 760, where the byline stacks
     into rows and a divider between rows would be noise. */
  .byline-rule { display:none; }
  @media (min-width: 760px) {
    [data-site] .byline-rule {
      display:block; flex:none; align-self:center;
      width:1px; height:15px; background:var(--faded); opacity:.4;
      /* Pulled toward the meta and away from the button. The button carries its
         own 1px frame, so a rule sitting midway between them reads as a doubled
         edge rather than a separator — measured 10px from the text and 17px
         from the frame before this. */
      margin:0 8px 0 -4px;
    }
  }

  /* The byline's column-gap is 8px here and 14px at ≥1024, so the same 8px
     margin-right puts the rule 16px from the chip on tablet and 22px on
     desktop. Desktop used to hide that gap behind a second, paler rule; with
     that gone it was a hole. 2px + the wider gap lands desktop on the same 16px
     tablet already draws, so the one divider sits identically at both sizes. */
  @media (min-width: 1024px) { [data-site] .byline-rule { margin-right:2px; } }

  /* Mobile byline: three rows — "By {author}" (the name is a red-underlined
     link), then "{date} • {read}", then the share icon row, with a generous
     even rhythm. */
  @media (max-width: 759.98px) { [data-site] .byline {
    flex-direction:column;
    align-items:flex-start;
    row-gap:14px;
    column-gap:0;
    margin-bottom:30px;
  } }

  @media (max-width: 759.98px) { [data-site] .byline .sep { margin:0 9px; } }

  @media (max-width: 759.98px) { [data-site] .byline-author { font-size:17px; line-height:1.2; color:var(--fg); } }

  @media (max-width: 759.98px) { [data-site] .byline-author::before { content:"By "; } }

  @media (max-width: 759.98px) { [data-site] .byline-lead-sep { display:none; } }

  @media (max-width: 759.98px) { [data-site] .byline-dt { display:flex; align-items:center; font-size:13px; } }

  @media (max-width: 759.98px) { [data-site] .ml-share { width:24px; height:24px; } }

  @media (max-width: 759.98px) { [data-site] .ml-share svg { width:14px; height:14px; } }

  .byline-actions {
    display:inline-flex;
    align-items:center;
    gap:2px;
  }

  /* No divider here. .byline-rule above is the one that separates the reading
     meta from the action, at every width that draws one. This slot used to hold
     a second 1px rule in --border, which meant desktop drew two verticals 22px
     apart — one in --faded at 40% (≈rgb(165,167,170)), one in #E5E5E5 — reading
     as a doubled edge rather than a separator. The darker one stayed. */

  /* One labelled control instead of a row of glyphs. Seven undifferentiated
     marks read as decoration and made the byline a toolbar; a named action sits
     with the date and read-time as another piece of the same editorial line, and
     says out loud that PDF and print are in there. Everything it used to expose
     lives in the share panel, which presents it better. Set in the byline serif
     rather than the panel's mono cells — this is type in a line of type, not a
     widget hanging under it. */
  /* The kicker chip's idiom, reused: same border, same mono, same proportions.
     Set as a chip rather than plain text because this is the only share
     affordance at the top of the article now — in the byline serif it read as a
     third piece of metadata next to the date and the read time, with nothing
     saying it could be pressed. Mono also ties it to the panel's own action
     cells, which is what it opens. */
  .byline-share {
    display:inline-flex;
    align-items:center;
    gap:8px;
    font-family:var(--mono);
    font-size:12px;
    letter-spacing:.01em;
    color:var(--fg);
    background:none;
    border:1px solid var(--fg);
    padding:3.5px 12px;
    cursor:pointer;
    white-space:nowrap;
    transition:background .15s ease, color .15s ease, border-color .15s ease;
    -webkit-tap-highlight-color:transparent;
  }

  /* The arrow from the personal site, same geometry: a 14-unit box, 1.5 stroke,
     round caps, and a head drawn as two strokes radiating from the tip rather
     than one mitred polyline — that's what gives it the drawn, slightly open
     corner instead of a tooled bracket. Nothing else on this site uses a
     diagonal arrow yet; when something does, this is the one to reuse.

     It trails the label so it closes the phrase the way an arrow trails a link
     in running text, rather than sitting in front as a button icon. A bare
     diagonal also avoids putting a second enclosed shape (a tray, a bracket)
     inside a chip that is already a box. */
  .byline-share .arrow-icon { width:12px; height:12px; display:block; flex:none; }

  @media (hover:hover) {
    .byline-share:hover { background:var(--accent); border-color:var(--accent); color:#fff; }
  }

  .byline-share:active { background:var(--accent); border-color:var(--accent); color:#fff; }

  .ml-share {
    position:relative;
    display:inline-flex;
    align-items:center;
    justify-content:center;
    width:30px;
    height:30px;
    padding:0;
    border:none;
    background:none;
    color:var(--faded);
    cursor:pointer;
    border-radius:6px;
    transition:color .15s, background .15s;
    -webkit-tap-highlight-color:transparent;
  }

  .ml-share svg { width:17px; height:17px; display:block; }

  .ml-share svg.filled { fill:currentColor; stroke:none; }

  .ml-share svg:not(.filled) { fill:none; stroke:currentColor; stroke-width:1.7; stroke-linecap:round; stroke-linejoin:round; }

  @media (hover:hover) { .ml-share:hover { color:var(--accent); background:color-mix(in srgb, var(--accent) 12%, transparent); } }
  .ml-share:active { color:var(--accent); background:color-mix(in srgb, var(--accent) 12%, transparent); }

  .ml-share::after {
    content:attr(data-tip);
    position:absolute;
    bottom:calc(100% + 6px);
    left:50%;
    transform:translateX(-50%) translateY(3px);
    background:var(--fg);
    color:var(--bg);
    font-family:var(--mono);
    font-size:10px;
    letter-spacing:.02em;
    line-height:1;
    white-space:nowrap;
    padding:5px 7px;
    border-radius:5px;
    opacity:0;
    pointer-events:none;
    transition:opacity .14s, transform .14s;
    z-index:5;
  }

  @media (hover:hover) { .ml-share:hover::after { opacity:1; transform:translateX(-50%) translateY(0); } }
  .ml-share:focus-visible::after { opacity:1; transform:translateX(-50%) translateY(0); }

  .article p {
    /* the reader Settings panel drives these via CSS custom properties set on
       <html> (persisted; re-applied pre-paint by the head bootstrap) */
    font-family:var(--reader-body-font, inherit);
    font-size:var(--reader-size, 17px);
    line-height:var(--reader-line, 1.65);
    color:var(--fg);
    margin:0 0 20px;
  }

  /* end-of-article mark: a small vermilion square set right after the last word
     (the classic printed "fin" mark), echoing the kicker node and footer sigil.
     em-sized so it tracks the reader's text size. */
  #articleBody > p:last-child::after {
    content:"";
    display:inline-block;
    width:.6em; height:.6em;
    margin-left:.5em;
    background:var(--accent);
    vertical-align:.02em;
  }

  /* red dropcap on the first body paragraph (desktop / landscape only) */
  @media (min-width: 1024px) { [data-site] #articleBody > p:first-of-type::first-letter {
    float:left;
    font-family:var(--display);
    font-weight:600;
    font-size:3.5em;
    line-height:0.72;
    padding:6px 12px 0 0;
    color:var(--accent);
  } }

  /* ---- heading scale ----
     Sized off --reader-size rather than in fixed pixels, so the hierarchy is a
     set of ratios instead of a set of numbers that only hold at one setting.
     It used to be h2:24px h3:19px against a 17px body — 1.41x and 1.12x, which
     is barely a hierarchy at the default, and at the Large body setting (19px)
     an h3 was EXACTLY the size of the paragraph under it. The structure of a
     long piece disappeared for anyone who turned the text up.

     1.76 / 1.29 / 0.72 gives roughly 30 / 22 / 12 against a 17px body, and
     holds those proportions at every stop. h4 drops to the UI mono in --faded:
     below h3 the useful signal is "this is a label", not "this is smaller
     display type", and Cormorant at 12px would just look like shrunken prose.

     The duplicate font-family declaration is gone — the second one won, so
     --reader-heading-font was dead the moment it was written. */
  .article h2 {
    font-family:var(--reader-heading-font, var(--display));
    font-size:calc(var(--reader-size, 17px) * 1.76);
    font-weight:var(--wt-display);
    line-height:1.12;
    margin:46px 0 14px;
  }

  .article h3 {
    font-family:var(--reader-heading-font, var(--display));
    font-size:calc(var(--reader-size, 17px) * 1.29);
    font-weight:var(--wt-display);
    line-height:1.2;
    margin:32px 0 9px;
  }

  .article h4 {
    font-family:var(--mono);
    font-size:calc(var(--reader-size, 17px) * 0.72);
    font-weight:400;
    letter-spacing:0.06em;
    line-height:1.4;
    color:var(--faded);
    margin:28px 0 8px;
  }

  /* A heading immediately after another heading is a subtitle, not a new
     section — close the gap so the pair reads as one unit. */
  .article h2 + h3, .article h3 + h4 { margin-top:14px; }

  /* keep native anchor jumps (TOC links, copied heading anchors) clear of the
     sticky masthead. Dynamically-loaded headings carry an inline scroll-margin-top;
     this covers the static "credit" headings, which do not. */
  .article h2[id], .article h3[id], .article h4[id] { scroll-margin-top:120px; }

  .overview-marker { font-family:var(--reader-body-font, inherit); font-size:var(--reader-size, 17px);
    font-style:italic;
    color:var(--faded);
    margin-bottom:28px;
  }

  /* ============================ inline footnotes ============================
     Marker: a tight, raised accent numeral that flows like normal prose — it
     takes only its own glyph width, so punctuation hugs it with no forced gap.
     Hover/touch reveals a short underline that stays up in the superscript zone
     and never touches the surrounding text. Clicking opens the note in a
     popover (desktop) or a bottom sheet (phone/tablet); built by reader.js from
     the fallback endnotes list, which is the JS-off degrade path. */
  /* The numeral is set in the BODY SERIF at a real 700, not the mono used for
     kickers and datelines: a footnote reference is typography inside a sentence,
     not chrome bolted onto it, and it matches the serif numerals in the notes
     list below. Fragment Mono also ships a single weight, so font-weight there
     is a no-op and the only way to add weight would be a synthetic stroke, which
     closes up the counters at this size. Newsreader carries a drawn 700.
     (The loaded subset has no true superscript glyphs, so it is scaled+raised.) */
  .fn-ref {
    font-family:var(--serif);
    font-weight:700;
    font-size:.62em;
    line-height:0;
    vertical-align:super;
    color:var(--accent);
    text-decoration:none;
    position:relative;
    cursor:pointer;
    margin:0;
    padding:0;
    /* Same 120px the headings and .fn-list items reserve. Without it, following
       .fn-back scrolled the marker to y=0 — under the sticky reading header —
       so the return landed a paragraph short of the marker it aimed at. */
    scroll-margin-top:120px;
  }
  /* enlarged, layout-neutral tap target so the tiny numeral is easy to hit */
  .fn-ref::before {
    content:"";
    position:absolute;
    inset:-10px -6px;
  }
  /* short underline confined to the numeral's own width, up in the super zone */
  .fn-ref::after {
    content:"";
    position:absolute;
    left:0; right:0; bottom:-2px;
    height:1.5px;
    background:var(--accent);
    opacity:0;
    transition:opacity .12s ease;
  }
  @media (hover:hover) { .fn-ref:hover::after { opacity:1; } }
  .fn-ref:active::after, .fn-ref:focus-visible::after, .fn-ref.fn-open::after { opacity:1; }
  /* Arriving back from .fn-back. Every other state that paints this underline is
     momentary — :active ends on mouseup, :focus-visible on the next scroll or
     click — so the reader was returned to a dense paragraph with nothing saying
     which numeral they had left. :target holds until another fragment is
     followed, which is exactly as long as the answer is useful. */
  /* Two paths to the same underline, and they must not overlap. .fn-returned is
     the scripted one and clears itself on the next scroll or click (reader.47e50cd469.js).
     :target is the fallback, scoped to :not(.fn-js) — with the script running it
     would never clear, because Chrome recomputes :target only on a real fragment
     navigation, so nothing JS does to the URL can release it. */
  html:not(.fn-js) .fn-ref:target::after,
  .fn-ref.fn-returned::after { opacity:1; }
  .fn-ref:focus-visible { outline:none; }

  /* Hollow accent square node, used in both the popover and the sheet. It marks
     the note as a standalone object rather than an item in a list (there is no
     list — you opened exactly one note). Hollow rather than filled: a solid
     vermilion block on a small panel pulls focus from the note it introduces. */
  .fn-node {
    flex:none;
    width:21px; height:21px;
    background:transparent;
    border:1.5px solid var(--accent);
    border-radius:2px;
    display:inline-flex;
    align-items:center;
    justify-content:center;
    font-family:var(--mono);
    font-size:10px;
    line-height:1;
    color:var(--accent);
  }
  .fn-node-lg { width:27px; height:27px; font-size:11px; position:relative; top:2px; }

  /* Desktop popover — opens in the article, anchored to its marker. Deliberately
     not a margin note: the side TOC owns the right gutter, and a note out there
     pulls the eye sideways off the column it belongs to. No shadow — the 1px rule
     is enough separation, and the drop shadow read as an app surface floating
     over the page rather than a note on it. Wide enough that the text does not
     bunch into a narrow ragged column. */
  .fn-pop {
    position:absolute;
    z-index:1200;
    display:flex;
    gap:13px;
    align-items:flex-start;
    width:380px;
    max-width:min(380px, 86vw);
    background:var(--bg);
    border:1px solid var(--fg);
    padding:14px 16px 15px;
    opacity:0;
    visibility:hidden;
    transform:translateY(-5px);
    /* visibility is switched, never interpolated. Hiding waits for the fade to
       finish; showing flips immediately, because a transitioned visibility is
       still `hidden` on the tick the class lands and the close button inside a
       hidden box cannot take focus — which is exactly the tick a keyboard pin
       needs to move focus into the note. */
    transition:opacity .16s ease, transform .18s cubic-bezier(.16,1,.3,1), visibility 0s linear .18s;
  }
  .fn-pop.show {
    opacity:1; visibility:visible; transform:none;
    transition:opacity .16s ease, transform .18s cubic-bezier(.16,1,.3,1), visibility 0s;
  }

  /* Close button — pinned popover and mobile sheet only. Its presence is the
     signal that the note is sticky: a hover preview never shows one, because a
     preview dismisses itself and offering a control to do that invites a click
     the reader did not need to make. */
  .fn-close {
    position:absolute; top:8px; right:8px; width:26px; height:26px;
    display:none; align-items:center; justify-content:center;
    appearance:none; background:none; border:none; padding:0; margin:0;
    color:var(--faded); cursor:pointer; border-radius:2px;
    transition:color .15s ease, background .15s ease;
  }
  .fn-close svg { width:13px; height:13px; }
  .fn-close:hover, .fn-close:focus-visible { color:var(--fg); background:var(--border); }
  .fn-close:focus-visible { outline:1px solid var(--accent); outline-offset:1px; }
  .fn-pop.fn-pinned .fn-close { display:inline-flex; }
  /* Keep the body clear of the button so a long first line cannot run under it. */
  .fn-pop.fn-pinned .fn-pop-body { padding-right:22px; }

  /* The sheet is always dismissible-by-intent, so its button is always shown —
     the backdrop alone is invisible and nobody discovers it. Sized larger than
     the desktop one to clear the 44px touch target guidance. */
  .fn-sheet .fn-close { display:inline-flex; top:12px; right:12px; width:40px; height:40px; }
  .fn-sheet .fn-close svg { width:15px; height:15px; }
  .fn-sheet-body { padding-right:34px; }
  .fn-pop-body {
    font-family:var(--serif);
    font-size:15px;
    line-height:1.55;
    color:var(--fg);
  }
  .fn-pop-body a { color:var(--accent); }

  /* mobile bottom sheet — flat top, no scrim (an invisible catcher dismisses) */
  .fn-sheet { position:fixed; inset:0; z-index:1200; display:none; }
  .fn-sheet.open { display:block; }
  .fn-sheet-backdrop { position:absolute; inset:0; background:transparent; }
  .fn-sheet-panel {
    position:fixed;
    left:0; right:0; bottom:0;
    background:var(--bg);
    border-top:1px solid var(--fg);
    padding:22px 22px calc(30px + env(safe-area-inset-bottom));
    transform:translateY(100%);
    transition:transform .32s cubic-bezier(.16,1,.3,1);
    max-height:72vh;
    overflow:auto;
  }
  .fn-sheet.open .fn-sheet-panel { transform:translateY(0); }
  .fn-sheet-row { display:flex; gap:14px; align-items:flex-start; }
  /* 15px, matching the popover: a note reads as subordinate to the article, and
     at the article's own size it competed with the prose it annotates */
  .fn-sheet-body {
    font-family:var(--serif);
    font-size:15px;
    line-height:1.55;
    color:var(--fg);
  }
  .fn-sheet-body a { color:var(--accent); }

  /* fallback endnotes list (visible only with JS off; reader.js hides it once it
     has built the inline notes, so a JS failure never loses the notes). Kept
     compact and muted — reference material, set below the body copy, not level
     with it: small serif, hanging accent numerals, a node-marked label. */
  .fn-list {
    margin:44px 0 0;
    padding-top:18px;
    border-top:1px solid var(--border);
  }
  .fn-list h2 {
    display:flex;
    align-items:center;
    gap:9px;
    font-family:var(--mono);
    font-weight:500;
    font-size:14px;
    letter-spacing:.06em;
    color:var(--faded);
    margin:0 0 15px;
  }
  .fn-list h2::before {
    content:"";
    width:7px; height:7px;
    background:var(--accent);
  }
  /* Both selectors, because `.article ol { list-style:decimal }` further down
     this file has the same specificity and wins on order — the endnotes live
     inside .article, so the native marker came back and every note rendered as
     "1. 1." with the ::before counter below. Nobody saw it: the section was
     display:none whenever JavaScript ran, so the only way to hit it was to
     disable JS and scroll to the bottom. Unhiding the list is what surfaced it.
     Qualifying with .article beats the prose rule wherever either sits. */
  .fn-list ol,
  .article .fn-list ol { margin:0; padding:0; list-style:none; counter-reset:fn; }
  /* classic print numbering: a muted "N." set flush with the note, wrapped lines
     returning to the margin — dense and editorial */
  .fn-list li {
    counter-increment:fn;
    margin:0 0 9px;
    font-family:var(--serif);
    font-size:14.5px;
    line-height:1.5;
    color:var(--faded);
    scroll-margin-top:120px;
  }
  .fn-list li::before {
    content:counter(fn)".";
    font-family:var(--serif);
    font-size:14.5px;
    color:var(--faded);
    margin-right:7px;
  }
  /* Flat, wide return mark rather than the ↩ glyph — see build/footnotes.mjs for
     the path and why it carries no riser. Sized in em so it tracks the note's
     14.5px. */
  .fn-back { color:var(--accent); text-decoration:none; white-space:nowrap;
    display:inline-block; vertical-align:baseline; }
  .fn-back svg { width:1.24em; height:.62em; display:inline-block; vertical-align:-.02em; }
  @media (hover:hover) { .fn-back:hover { text-decoration:none; opacity:.68; } }
  /* The endnotes section stays. It used to be `.fn-js .fn-list { display:none }`
     — hidden the moment JavaScript loaded, on the reasoning that the inline
     popover replaced it. The popover is a better way to read ONE note in
     passing; it is not a replacement for the list, and hiding the list cost
     more than it saved:

       - Printing. The print block never re-showed it, so every printed article
         and every generated PDF carried nine superscript markers and no notes.
         That is the actual bug here, and it was invisible because the no-JS
         path — the one anybody would think to test — worked fine.
       - Reader modes and article extractors (Google's, Pocket, Instapaper) run
         AFTER scripts, so they saw display:none and dropped the section.
       - Ctrl+F could not find a note, and no note had a linkable URL, despite
         every one of them having an id.

     So the popover is now what it should always have been: a convenience on top
     of the record, not instead of it. Both work — the marker opens the note in
     place, and the note is also at the bottom where a reader can scan all nine
     and follow .fn-back to return. */

  .annot {
    margin:40px auto 0;
    max-width:430px;
    background:var(--hover);
    border:1px solid var(--border);
    padding:16px 18px;
    font-family:var(--mono);
    font-size:12px;
    line-height:1.6;
    color:var(--faded);
  }

  .annot b {color:var(--fg);}

  /* Width sets the desktop article measure, which is meaningless on the full-bleed
     phone article — so the row is hidden on phone/tablet and shown on desktop. */
  #settingsWidthRow { display:none; }

  @media (min-width: 1024px) { [data-site] #settingsWidthRow { display:flex; } }

  /* ---- heading anchors ---- */
  .heading-anchor {
    display:inline-flex; align-items:center; vertical-align:middle;
    background:none; border:none; cursor:pointer; padding:0;
    margin-left:.4em; color:var(--faded);
    opacity:0; transition:opacity .15s, color .15s;
    user-select:none; line-height:1; position:relative; top:-.04em;
  }

  .heading-anchor svg {
    width:.7em; height:.7em;
    stroke:currentColor; fill:none; stroke-width:2;
    stroke-linecap:round; stroke-linejoin:round;
  }

  @media(hover:hover) {
    .article h2:hover .heading-anchor, .article h3:hover .heading-anchor, .article h4:hover .heading-anchor { opacity:1; }

    .heading-anchor:hover { color:var(--accent); opacity:1; }

  }
  @media(hover:none) { .heading-anchor { opacity:.3; }
 }

  /* ---- copy toast ---- */
  .copy-toast {
    position:fixed;
    font-family:var(--mono); font-size:11px;
    pointer-events:none; opacity:0; transition:opacity .2s;
    z-index:9999; white-space:nowrap;
  }

  .copy-toast.show { opacity:1; }

  /* desktop: bare text next to heading */
  @media (min-width: 1024px) { [data-site] .copy-toast { color:var(--faded); } }

  /* phone / tablet: pill near bottom of the screen */
  @media (max-width: 759.98px) { [data-site] .copy-toast {
    background:var(--fg); color:var(--bg);
    padding:.32rem .65rem;
    bottom:1.75rem;
    left:50%;
    transform:translateX(-50%);
  } }
@media (min-width: 760px) and (max-width: 1023.98px) { [data-site] .copy-toast {
    background:var(--fg); color:var(--bg);
    padding:.32rem .65rem;
    bottom:1.75rem;
    left:50%;
    transform:translateX(-50%);
  } }

  /* ---- article start rule ---- */
  /* "The article starts here" rule — a thin ink line between the byline and the
     first paragraph, closing the header block so the body has a clear top edge. */
  .article-start-rule {
    border:none;
    border-top:1px solid var(--fg);
    margin:18px 0 16px;
    width:100%;
  }

  /* span the header-block width (matching the hero/title/byline breakout) so the
     rule reads as the bottom edge of the article header, closing the header
     block; a reading-column-width rule looks indented under the wider byline. */
  @media (min-width: 1024px) { [data-site] .article-start-rule {
    width:min(
      calc(var(--art-w) + var(--toc-w) + 16px),
      calc(var(--vw) - clamp(90px, calc(var(--vw) * .08), 130px) - 48px)
    );
    margin:20px 0 18px;
  } }

  /* ---- lead gauge hero ---- */
  .lead-media { width:100%; aspect-ratio:1600 / 1080; overflow:hidden; background:#FAB81F; border:1px solid var(--border); margin:6px 0 22px; position:relative; }

  .lead-media canvas { position:absolute; inset:0; width:100%; height:100%; display:block; }

  /* desktop: 5:4 hero that breaks out of the reading column to the right so it
     spans the body column AND the TOC region (no landing whitespace). The TOC is
     hidden until you scroll past the summary, by which point the hero is gone, so
     they never overlap. Width = content-left -> TOC-right, clamped to the viewport. */
  /* 21:9 rather than 16:9. At 16:9 the hero was 605px on a 1440x900 screen —
     67% of the viewport, and 80% on a 760px-tall laptop — which put the byline
     AND the first line of prose below the fold on every article. The reader
     arrived at a publication and could not see a word of the piece.
     21:9 brings it to ~51vh, so the byline and the opening line land above the
     fold while the image is still unmistakably a lead. Safe to reshape only
     because the art now contains rather than covers (see leadArtHtml): the
     drawing scales down inside the frame instead of being cropped by it. */
  @media (min-width: 1024px) { [data-site] .lead-media {
    aspect-ratio:21 / 9;
    width:min(
      calc(var(--art-w) + var(--toc-w) + 16px),
      calc(var(--vw) - clamp(90px, calc(var(--vw) * .08), 130px) - 48px)
    );
    max-width:none;
  } }

  /* ============================== figures ==============================
     An image in the body sits in the reading column like a paragraph, in a
     hairline frame that matches the lead media's, with the caption hanging
     below it in the UI mono — small, faded, and clearly not part of the prose.

     The frame is the thing you click. A zoom button in its corner is the
     affordance, rather than making the <img> itself clickable: the image is
     not focusable, so a mouse-only zoom is a control most people can reach and
     some cannot. The button only becomes visible as a control once reader.js
     has wired the viewer (data-fig-ready) — with JS off there is nothing to
     click, so nothing offers to be clicked. */
  .fig { margin:34px 0; }
  .fig-frame {
    position:relative;
    border:1px solid var(--imgline, var(--border));
    background:var(--hover, #F6F6F6);
    overflow:hidden;
    line-height:0;
  }
  .fig-frame img { display:block; width:100%; height:auto; }

  .fig-zoom {
    position:absolute; right:10px; bottom:10px;
    width:34px; height:34px;
    display:none; align-items:center; justify-content:center;
    appearance:none; padding:0; margin:0;
    border:1px solid var(--fg);
    background:var(--bg); color:var(--fg);
    cursor:zoom-in;
    opacity:0; transform:translateY(3px);
    transition:opacity .16s ease, transform .16s ease, background .16s ease, color .16s ease;
  }
  .article[data-fig-ready] .fig-zoom { display:inline-flex; }
  .fig-zoom svg { width:16px; height:16px; }
  @media (hover:hover) {
    .article[data-fig-ready] .fig-frame:hover .fig-zoom { opacity:1; transform:none; }
    .fig-zoom:hover { background:var(--fg); color:var(--bg); }
  }
  /* No hover on touch, so the control is simply always there. */
  @media (hover:none) { .article[data-fig-ready] .fig-zoom { opacity:1; transform:none; width:40px; height:40px; } }
  .fig-zoom:focus-visible { opacity:1; transform:none; outline:2px solid var(--accent); outline-offset:2px; }

  /* Set in the reading serif, not mono. A caption is prose about the picture —
     a sentence, often two — and mono is the site's label voice: kickers, chips,
     meta, the read time. At caption length it stopped reading as a label and
     started reading as running text set in the wrong face.
     The accent rule and --faded already mark it as apart from the body, so the
     face does not also have to.
     0.80 rather than 0.68: mono and serif at one px size are not one apparent
     size, and 0.68 in Newsreader is small enough to strain. */
  .fig-cap {
    font-family:var(--serif);
    font-size:calc(var(--reader-size, 17px) * 0.80);
    line-height:1.45;
    color:var(--faded);
    margin:10px 0 0;
    padding-left:11px;
    border-left:2px solid var(--accent);
  }
  .fig-cap cite { font-style:normal; color:var(--faded); opacity:.8; }

  /* ---- zoom view ----
     Full-screen, but built out of the same tokens as the page, so it reads as
     the article opening up rather than as a viewer from somewhere else. */
  body.fig-zv-lock { overflow:hidden; }
  .fig-zoomview {
    position:fixed; inset:0; z-index:3400;
    display:flex; flex-direction:column; align-items:center; justify-content:center;
    gap:16px; padding:28px 28px 20px;
    opacity:0; visibility:hidden;
    transition:opacity .2s ease, visibility 0s linear .2s;
  }
  .fig-zoomview.open { opacity:1; visibility:visible; transition:opacity .2s ease, visibility 0s; }
  .fig-zv-scrim { position:absolute; inset:0; background:var(--bg); cursor:zoom-out; }
  .fig-zv-stage {
    position:relative; z-index:1;
    flex:1 1 auto; min-height:0; width:100%;
    display:flex; align-items:center; justify-content:center;
    overflow:hidden;
  }
  .fig-zv-img {
    max-width:100%; max-height:100%;
    width:auto; height:auto;
    display:block;
    transform-origin:center center;
    will-change:transform;
  }
  .fig-zoomview.zoomed .fig-zv-stage { cursor:grab; }
  .fig-zoomview.dragging .fig-zv-stage { cursor:grabbing; }
  /* The zoom view shows the same caption as the figure, so it takes the same
     face — it would be odd for one picture's words to change voice on opening. */
  .fig-zv-cap {
    position:relative; z-index:1;
    flex:none; max-width:min(760px, 100%);
    font-family:var(--serif); font-size:14px; line-height:1.5;
    color:var(--faded); text-align:center;
  }
  .fig-zv-bar {
    position:relative; z-index:1; flex:none;
    display:flex; align-items:center; gap:6px;
    border:1px solid var(--fg); background:var(--bg);
    padding:6px 8px;
  }
  .fig-zv-btn {
    appearance:none; background:none; border:none; padding:0; margin:0;
    width:34px; height:34px;
    display:inline-flex; align-items:center; justify-content:center;
    color:var(--fg); cursor:pointer;
    transition:background .14s ease, color .14s ease, opacity .14s ease;
  }
  .fig-zv-btn svg { width:17px; height:17px; }
  @media (hover:hover) { .fig-zv-btn:hover:not([disabled]) { background:var(--fg); color:var(--bg); } }
  .fig-zv-btn:focus-visible { outline:2px solid var(--accent); outline-offset:-2px; }
  .fig-zv-btn[disabled] { opacity:.3; cursor:default; }
  .fig-zv-pct {
    font-family:var(--mono); font-size:11.5px; letter-spacing:0.04em;
    color:var(--faded); min-width:44px; text-align:center;
  }
  .fig-zv-div { width:1px; height:20px; background:var(--border); margin:0 4px; }
  .fig-zv-toast {
    position:absolute; z-index:2; left:50%; bottom:84px; transform:translate(-50%, 6px);
    background:var(--fg); color:var(--bg);
    font-family:var(--mono); font-size:11.5px; letter-spacing:0.03em;
    padding:7px 12px; opacity:0; pointer-events:none;
    transition:opacity .18s ease, transform .18s ease;
  }
  .fig-zv-toast.show { opacity:1; transform:translate(-50%, 0); }

  @media (max-width: 759.98px) {
    .fig-zoomview { padding:16px 12px 14px; gap:12px; }
    .fig-zv-btn { width:40px; height:40px; }
    .fig-zv-cap { font-size:13px; }
  }
  @media (prefers-reduced-motion: reduce) {
    .fig-zv-img, .fig-zv-toast, .fig-zoom { transition:none !important; }
  }

  /* ========================= selection actions =========================
     A small bar above the selection. Ink-filled rather than outlined: it sits
     on top of text that is itself sitting in an accent selection wash, and an
     outlined box in that company reads as another piece of the paragraph
     instead of as something floating over it. */
  .sel-bar {
    position:absolute; z-index:1250;
    display:flex; align-items:stretch;
    background:var(--fg); color:var(--bg);
    border:1px solid var(--fg);
    opacity:0; visibility:hidden; transform:translateY(3px);
    transition:opacity .14s ease, transform .14s ease, visibility 0s linear .14s;
    box-shadow:0 6px 20px rgba(0,0,0,.14);
  }
  .sel-bar.show {
    opacity:1; visibility:visible; transform:none;
    transition:opacity .14s ease, transform .14s ease, visibility 0s;
  }
  .sel-btn {
    appearance:none; background:none; border:none; margin:0;
    padding:8px 13px;
    display:inline-flex; align-items:center; gap:7px;
    font-family:var(--mono); font-size:11.5px; letter-spacing:0.03em;
    color:inherit; cursor:pointer; white-space:nowrap;
    transition:background .14s ease;
  }
  .sel-btn svg { width:14px; height:14px; flex:none; }
  @media (hover:hover) { .sel-btn:hover { background:var(--accent); } }
  .sel-btn:active { background:var(--accent); }
  .sel-btn:focus-visible { outline:2px solid var(--accent); outline-offset:-2px; }
  .sel-div { width:1px; background:rgba(255,255,255,.22); flex:none; }
  :root[data-theme="dark"] .sel-div { background:rgba(0,0,0,.25); }

  /* Touch devices put their own selection menu over the selection, and two
     stacked toolbars is worse than either. Ours sits below there instead. */
  @media (max-width: 759.98px) {
    .sel-btn { padding:11px 15px; font-size:12px; }
  }
  @media (prefers-reduced-motion: reduce) { .sel-bar { transition:opacity .14s ease, visibility 0s; } }

  /* ---- lists ----
     A list is body copy that happens to be enumerated, so it takes exactly the
     same three custom properties as <p> — same face, same size, same leading —
     and changing the reading settings has to move it by exactly as much.

     It did not. .article ol took two of the three (no --reader-body-font, so
     the body-face setting skipped lists entirely) at a different default
     leading, 1.62 against the paragraph's 1.65. And there was no .article ul
     rule at all, so a bulleted list inherited whatever it landed on and
     ignored the settings panel completely. Nothing in the current nine
     articles is bulleted, which is why it went unseen.

     One selector for both now, and the values are the paragraph's values. */
  .article ul,
  .article ol {
    font-family:var(--reader-body-font, inherit);
    font-size:var(--reader-size, 17px);
    line-height:var(--reader-line, 1.65);
    color:var(--fg);
    margin:0 0 20px;
    padding-left:22px;
  }
  .article ul { list-style:disc; }
  .article ol { list-style:decimal; }
  .article li { margin:0 0 10px; }
  .article li::marker { color:var(--faded); }

  /* Nested lists continue the line they are attached to rather than restarting
     the block rhythm, so they get the tighter top margin and no bottom one.
     The marker changes at each level, because indentation alone stops being
     legible once a list wraps across lines at reading width. */
  .article li > ul,
  .article li > ol { margin:10px 0 0; }
  .article li > ul { list-style:circle; }
  .article li > ul > li > ul { list-style:square; }
  .article li > ol { list-style:lower-alpha; }
  .article li > ol > li > ol { list-style:lower-roman; }

/* Shrink the Built-by chip on phones so the "Built by" label never wraps. */
@media (max-width: 759.98px) { [data-site] a[href*="marscodefactory"] { font-size: 8px !important; letter-spacing: 0.05em !important; padding: 4px 7px !important; gap: 5px !important; white-space: nowrap !important; } }

@media (max-width: 759.98px) { [data-site] a[href*="marscodefactory"] > span { width: 6px !important; height: 6px !important; } }

/* ==========================================================================
   Links in article prose (citations, sources, context).

   There was no SCREEN styling for these at all — only the @media print rules
   further down, which spell out the href in parentheses so a printed copy stays
   useful. So the first source link added to a body would have rendered in the
   browser's default blue, in the middle of Newsreader at 19px.

   Matches [data-site] .prose-link in index.css exactly (accent, no underline
   until hover) rather than inventing a second convention for the same thing.

   Colour alone carries the link, which WCAG 1.4.1 permits only under G183:
   link vs background >= 4.5:1 AND link vs surrounding text >= 3:1, plus a
   non-colour cue on hover and focus. Measured: light 5.15 / 3.93, dark
   5.50 / 3.82 — both pass, but only since the accent was darkened for
   contrast; the previous #E34234 gave 4.12:1 against white and would have
   failed the first half. Hover and active add the underline; focus is covered
   by the :where() focus floor in chrome.css.
   ========================================================================== */
/* The endnote list is a SIBLING of #articleBody, not inside it, so the rule
   below never reached its links and they fell back to browser blue. */
.fn-list a[href]:not(.fn-back),
#articleBody a[href]:not(.fn-ref):not(.fn-back):not(.heading-anchor) {
  color: var(--accent);
  text-decoration: none;
}
@media (hover: hover) {
  .fn-list a[href]:not(.fn-back):hover,
  #articleBody a[href]:not(.fn-ref):not(.fn-back):not(.heading-anchor):hover {
    text-decoration: underline; text-decoration-thickness: 1.5px; text-underline-offset: 2px;
  }
}
.fn-list a[href]:not(.fn-back):active,
#articleBody a[href]:not(.fn-ref):not(.fn-back):not(.heading-anchor):active {
  text-decoration: underline; text-decoration-thickness: 1.5px; text-underline-offset: 2px;
}

/* Print-only elements: hidden on screen, shown by the @media print block below. */
.print-masthead, .print-url { display: none; }

/* ==========================================================================
   PRINT  ·  Send-to-Kindle  ·  read-later capture
   Strip every piece of app chrome and lay the article out as a clean,
   black-on-white document. This is what browsers' Print / "Save as PDF" and
   Amazon's Send-to-Kindle capture use. Theme tokens are forced to a light,
   ink-friendly palette so printing from dark or paper mode still comes out on
   white paper.
   ========================================================================== */
@media print {
  /* Light, ink-friendly palette regardless of the on-screen theme. */
  :root, html[data-theme="dark"], html[data-theme="paper"] {
    --bg: #ffffff !important;  --fg: #111111 !important;  --faded: #555555 !important;
    --border: #cccccc !important; --imgline: #111111 !important; --hover: #ffffff !important;
    /* keep the vermilion accent so print still reads as the site */
    --accent: #D12B1D !important; --accent-strong: #B6352A !important; color-scheme: light !important;
  }

  html, body { background: #fff !important; color: #111 !important; }

  /* The bottom margin is taller than the sides because Chromium's footer
     template draws in it — the wordmark, the rule and the folio all live in
     that band (see tools/pdf.mjs). 24mm holds 6mm of air, the rule, and a line
     of type with room to spare. Text can never reach into it, which is the
     whole point: the running foot used to be an in-document fixed bar sharing
     the content box with the prose, and struck through the last line on any
     page that filled. */
  @page { margin: 18mm 16mm 24mm; }

  /* Hide app chrome: header, rails, TOC, menu, panels, footer, the byline share
     row, the in-heading anchor buttons, and the figure zoom control — the last
     is a screen affordance that otherwise prints as a stray magnifier sitting
     on top of every figure. */
  .overlay-layer, .masthead, .rail, .toc-strip, #toc-strip, .copy-toast, .share-panel, .settings-panel, footer, .byline-actions, .byline-rule, .byline-share-label, .heading-anchor, .article-start-rule, .fig-zoom, .fig-zoomview, .sel-bar {
    display: none !important;
  }

  /* Flatten the sticky stack for print. The .device frame this used to undo no
     longer exists, so the rule no longer names it. */
  #stage, .stack {
    display: block !important; position: static !important; overflow: visible !important;
    width: auto !important; max-width: none !important; height: auto !important;
    margin: 0 !important; padding: 0 !important; border: 0 !important;
    box-shadow: none !important; background: #fff !important; border-radius: 0 !important;
  }

  /* Article: full width, comfortable measure. */
  .article {
    max-width: none !important; width: auto !important; margin: 0 !important; padding: 0 !important;
  }

  /* Use the full page width: the on-screen reading measure / side-TOC breakout
     constrains the title, subtitle and body narrower than the page, so reset them
     to auto — they then wrap only when they truly reach the page edge. */
  .headline, h1.headline, #h-overview, .byline, #articleBody {
    width: auto !important; max-width: none !important;
    margin-left: 0 !important; margin-right: 0 !important; left: auto !important; right: auto !important;
  }

  /* Masthead lockup so the print reads as the site: wordmark left, article URL
     right (opposite the wordmark, on the same line). */
  .print-masthead {
    display: flex !important; align-items: center; justify-content: space-between; gap: 16px;
    padding-bottom: 10pt; margin-bottom: 16pt; border-bottom: 1.5pt solid var(--accent);
  }

  .print-brand { display: flex !important; align-items: center; gap: 10px; }

  .print-globe { width: 26px; height: 26px; flex-shrink: 0; }

  .print-wordmark {
    font-family: var(--display); font-weight: 600; font-size: 15pt; letter-spacing: 0.01em; color: #111;
  }

  .print-wordmark .of { font-style: italic; font-weight: 500; color: var(--accent); }

  .print-url {
    display: inline-block !important; font-family: var(--mono); font-size: 9pt;
    letter-spacing: 0.01em; color: #777 !important; white-space: nowrap; text-align: right;
  }

  .kicker {
    font-family: var(--mono); font-size: 10pt; letter-spacing: 0.02em;
    text-transform: none !important; color: var(--accent) !important; margin: 0 0 6pt;
    border: none !important; padding: 0 !important;   /* plain accent text on paper, no box */
  }
  .kicker-dot { display: none !important; }

  /* Hero: keep it — it's a big part of the site's feel. Print its colours
     (canvas pixels always print; force any CSS background through too). */
  .lead-media {
    display: block !important; position: relative !important;
    width: 100% !important; max-width: 100% !important; aspect-ratio: 16 / 9 !important;
    height: auto !important; overflow: hidden !important; margin: 0 0 16pt !important;
    break-inside: avoid; page-break-inside: avoid;
    print-color-adjust: exact; -webkit-print-color-adjust: exact;
  }

  /* Heroes sit in a fixed 16/9 field, contained so they never distort or crop —
     whether an image hero, or a canvas scene the reader script freezes to an <img>
     for print (freezing captures the exact on-screen composition and prints a plain
     PNG, which every PDF engine handles). */
  .lead-media img {
    position: absolute !important; inset: 0 !important;
    width: 100% !important; height: 100% !important; object-fit: contain !important;
    print-color-adjust: exact; -webkit-print-color-adjust: exact;
  }

  /* A frozen canvas scene fills its frame more boldly than the sparse line-art
     image heroes; pad it in so it reads with the same airiness. The surrounding
     area shows the article colour (the hero background), so it stays seamless. */
  .lead-media img.wos-hero-frozen { padding: 10% 13% !important; box-sizing: border-box !important; }

  /* Fallback for a canvas that wasn't frozen (e.g. JS disabled): fill, no stretch. */
  .lead-media canvas {
    print-color-adjust: exact; -webkit-print-color-adjust: exact;
  }

  .headline {
    font-family: var(--display); font-weight: 700; font-size: 26pt; line-height: 1.08;
    margin: 0 0 8pt; color: #111 !important; break-after: avoid;
  }

  #h-overview {
    font-size: 13pt !important; line-height: 1.4 !important; opacity: 1 !important;
    color: #333 !important; margin: 0 0 10pt !important;
  }

  /* 14pt below, not 4. At 4 the meta row sat on top of the first line of body
     copy with no breath at all — they read as one block rather than as a label
     above an article.

     The row must stay on ONE line. It is a flex row that stacks into three rows
     below 760, and Chromium evaluates print media queries against the VIEWPORT
     rather than the paper — so it stays a single line only because pdf.mjs
     passes no `margin` option to page.pdf(). Passing one re-lays the document at
     the printable width and the byline stacks. Verified both ways.

     An earlier version of this note claimed the page footer required `margin`
     and was therefore incompatible with the single line. That was wrong:
     displayHeaderFooter alone draws the footer, which is why the PDFs now carry
     page numbers AND this row still fits on one line. See tools/pdf.mjs. */
  .byline { font-family: var(--mono); font-size: 10pt; color: #555 !important; margin: 0 0 14pt; }

  /* The name in the reading serif rather than the mono the meta is set in: it is
     a person, not a datum, and it is the one part of this row that is not a
     measurement. 11pt against the row's 10 because Newsreader runs smaller than
     Fragment Mono at a shared size. */
  .byline-author, .byline-author a { font-family: var(--serif) !important; font-size: 11pt !important; }

  .byline a { color: #555 !important; text-decoration: none !important; }

  /* Tighter interpuncts than the on-screen 8px. */
  /* Print keeps the drawn square, sized for paper. The accent survives on a
     colour printer and drops to grey on a mono one, same as every other mark. */
  .byline .sep { font-size: 0 !important; margin: 0 5px !important; top: 0 !important; }
  .byline .sep::before { width: 3pt !important; height: 3pt !important; }

  /* Body prose. */
  #articleBody { font-family: var(--serif); font-size: 11.5pt; line-height: 1.5; color: #111 !important; }

  #articleBody p, #articleBody li { font-size: 11.5pt !important; line-height: 1.5 !important; orphans: 3; widows: 3; }

  #articleBody h2 { font-family: var(--display); font-size: 17pt; margin: 16pt 0 6pt; break-after: avoid; page-break-after: avoid; }

  #articleBody h3 { font-family: var(--display); font-size: 13.5pt; margin: 12pt 0 5pt; break-after: avoid; page-break-after: avoid; }

  #articleBody figure, #articleBody img, #articleBody blockquote, #articleBody table {
    break-inside: avoid; page-break-inside: avoid; max-width: 100% !important;
  }

  #articleBody img { height: auto !important; }

  /* Links stay LINKS. page.pdf() carries every <a href> through as a real
     clickable annotation, so a PDF reader opens them — the URL does not need
     spelling out to be usable.

     It used to be spelled out: a[href^="http"]::after printed " (https://…)"
     in 9pt grey after every link. That is the convention for paper, where a
     link is inert, and it cost a lot — mid-sentence URLs broke the measure,
     word-break:break-all let them split anywhere, and a paragraph with three
     references became unreadable. This is a screen document that can also be
     printed, not the reverse.

     Marked by a red underline rather than red text: the accent on the rule
     alone keeps the body's colour and rhythm intact, and at 11pt coloured link
     text would flicker through the paragraph. --accent's LIGHT value, literal
     because the rest of this print block is literal and paper is a light
     ground either way.

     Both containers, with the screen rule's own exclusions. The endnote list is
     a SIBLING of #articleBody, so a rule naming only #articleBody misses every
     footnote link — and this article's external links are ALL in footnotes,
     which is how they kept printing as bare accent-coloured text with no rule
     under them while the body links were already fixed. .fn-back (the return
     arrows) and .fn-ref (the superscript markers) stay as they are: they are
     navigation glyphs, not references. */
  .fn-list a[href]:not(.fn-back),
  #articleBody a[href]:not(.fn-ref):not(.fn-back):not(.heading-anchor) {
    color: #111 !important;
    text-decoration: underline;
    text-decoration-color: #D12B1D;
    text-decoration-thickness: 0.9pt;
    text-underline-offset: 2.5pt;
  }

  /* Dividers print BLACK, like the site's — its structural rules are
     `1px solid var(--fg)` (footer, menu), and paper was the one surface still
     drawing them in a soft grey. Only the DIVIDERS: --border stays #ccc so the
     hero frame and boxed asides keep their light hairline, and the red link
     underlines are untouched. The page-foot bar is the other divider; it lives
     in tools/pdf.mjs, at the same weight. */
  .fn-list { border-top: 0.5pt solid #111 !important; }

  /* No brand line at the end any more: the page footer carries the wordmark on
     every page, so a second one after the last paragraph said it twice on the
     final sheet. See footerTemplate in tools/pdf.mjs. */

}

/* ===== Accessibility: prefers-reduced-motion =====
   Same blanket rule as index.css: all animations and transitions on the reader
   are cosmetic, so snap everything to its final state under reduced motion.
   !important overrides inline styles too (menu overlay, TOC accordion, the
   JS-set footerRingSpin on the footer globe). The subscribe subscribe-ring's SMIL
   animateTransform is guarded in the reader's inline script instead. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation: none !important;
    transition: none !important;
  }

  html { scroll-behavior: auto !important; }

}

/* ===== Static-site chrome animation (baked art, CSS-driven) ===== */
/* Footer globe: the strip group translates exactly one strip-width per 30s
   revolution (473.14 SVG user units == the copy offset baked into the SVG by
   tools/build/globe.mjs — the same constant, so the loop is seamless). */
.wos-globe-roll { animation: wosGlobeRoll 30s linear infinite; }

/* Easter egg: the masthead strip-earth marks (wos-globe-hroll — excluded from
   the continuous roll above) roll while hovered. JS adds .egg-roll on
   mouseenter and removes it at the next animationiteration after mouseleave,
   so the roll always stops on the seam/rest frame (no snap). Taps get one
   full revolution. */
.egg-roll .wos-globe-hroll {
  animation: wosGlobeRoll 5s linear infinite;
}
@keyframes wosGlobeRoll { to { transform: translateX(-473.14px); } }

/* ===== Cross-document view transitions (no JS; unsupported browsers just
   navigate normally). Disabled under prefers-reduced-motion below. ===== */
@view-transition { navigation: auto; }
::view-transition-old(root), ::view-transition-new(root) { animation-duration: 0.18s; }

/* Reduced motion: the blanket animation/transition kill in the block above
   covers the globe and ring groups; view-transition pseudo-elements live
   outside the DOM tree, so they get their own explicit kill. */
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*), ::view-transition-image-pair(*),
  ::view-transition-old(*), ::view-transition-new(*) { animation: none !important; }
}

/* No-JS fallback: the head bootstrap adds .js to <html>; without it the TOC
   strip renders expanded so every heading link is reachable, and the
   JS-only chrome (progress rail, share/settings buttons) stays hidden. */
html:not(.js) .toc-full { max-height:none; overflow:visible; }
html:not(.js) .toc-strip-head { pointer-events:none; }
html:not(.js) .toc-chevron { display:none; }
html:not(.js) .rail { display:none !important; }

/* ===== Desktop drawer: article-only integration =====
   The drawer markup + styling is shared chrome (see public/chrome.css). Only the
   reading-view integration lives here: while the desktop panel is open, veil the
   side TOC + utility rail so the Contents list does not poke out from under it. */
@media (min-width: 760px) {
  #stage.drawer-open #toc-strip, #stage.drawer-open .rail { opacity: 0 !important; visibility: hidden !important; }
}

/* ===== Document scrollbar (classic gutter, theme-aware) =====
   Same rationale as index.css: opt out of the overlay thumb, reserve a real
   gutter, keep the width constant, follow the theme (incl. the paper theme). */
html { --sb-track: #2A2A2A; --sb-line: rgba(0,0,0,0.35); --sb-thumb: rgba(255,255,255,0.35); --sb-thumb-hover: rgba(255,255,255,0.6); }
html[data-theme="dark"] { --sb-track: #101010; --sb-line: rgba(255,255,255,0.14); --sb-thumb: rgba(255,255,255,0.3); --sb-thumb-hover: rgba(255,255,255,0.55); }
/* Firefox only: the standard property (in Chrome it would disable the
   ::-webkit-scrollbar styles below, putting the overlay thumb back) */
@supports not selector(::-webkit-scrollbar) {
  html { scrollbar-color: var(--sb-thumb) var(--sb-track); }
}
html::-webkit-scrollbar { width: 13px; }
html::-webkit-scrollbar-track { background: var(--sb-track); border-left: 1px solid var(--sb-line); }
html::-webkit-scrollbar-thumb { background: var(--sb-thumb); border: 3px solid var(--sb-track); }
html::-webkit-scrollbar-thumb:hover { background: var(--sb-thumb-hover); }
