/* ==========================================================================
   site.css — the shared layer for every page on alexandroskalogrianitis.com

   Everything here used to be copy-pasted into each of the 27 HTML files, which
   meant 27 places to edit an accent colour and no caching between pages. Page
   files now carry only what is genuinely their own.

   Order: tokens, base, header and navigation, footer, shared page furniture,
   print.
   ========================================================================== */

/* --- Tokens ------------------------------------------------------------- */

:root{
  color-scheme:light dark;

  /* One source for the page gutter, so the sticky header can bleed out to the
     screen edges by exactly the amount its container is indented. */
  --pad:clamp(1.25rem,5vw,2rem);
  --head-h:54px;

  --bg:#faf6ee;
  --text:#242424;
  --text-dim:#646c77;
  --rule:#E8E1D6;
  --edge:#96866D;
  --accent:#2F6F9F;
  --navy:#163A5F;
  --pale:#E7F0F6;
  --on-navy:#ffffff;

  --serif:'Gentium Plus',Georgia,'Iowan Old Style','Times New Roman','Noto Serif','Liberation Serif','DejaVu Serif',serif;
  --sans:-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif;

  --section-gap:4.4rem;
  --entry-gap:1.9rem;

  --ease-out:cubic-bezier(0.23,1,0.32,1);
}

/* --- Display serif (Gentium Plus, self-hosted) -------------------------
   Replaces Georgia, a system font with no real typographic identity. This
   one has to cover three alphabets: the name/headings in English, and every
   Greek/Latin passage on the site via [lang="grc"]/[lang="la"] below - so
   the choice was gated on verified polytonic Greek support (U+1F00-1FFF,
   the breathings/accents/iota-subscript block), not on English looks alone.
   Split by unicode-range so a page only downloads the subsets it actually
   uses (an essay page never touches greek-ext; a Greek reading page does).
   Files and their OFL license are in fonts/. */
@font-face{
  font-family:'Gentium Plus';font-style:normal;font-weight:400;font-display:swap;
  src:url('fonts/gentiumplus-400-latin.woff2') format('woff2');
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}
@font-face{
  font-family:'Gentium Plus';font-style:normal;font-weight:400;font-display:swap;
  src:url('fonts/gentiumplus-400-latin-ext.woff2') format('woff2');
  unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
}
@font-face{
  font-family:'Gentium Plus';font-style:normal;font-weight:400;font-display:swap;
  src:url('fonts/gentiumplus-400-greek.woff2') format('woff2');
  unicode-range:U+0370-0377,U+037A-037F,U+0384-038A,U+038C,U+038E-03A1,U+03A3-03FF;
}
@font-face{
  font-family:'Gentium Plus';font-style:normal;font-weight:400;font-display:swap;
  src:url('fonts/gentiumplus-400-greek-ext.woff2') format('woff2');
  unicode-range:U+1F00-1FFF;
}
@font-face{
  font-family:'Gentium Plus';font-style:italic;font-weight:400;font-display:swap;
  src:url('fonts/gentiumplus-italic-latin.woff2') format('woff2');
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}
@font-face{
  font-family:'Gentium Plus';font-style:italic;font-weight:400;font-display:swap;
  src:url('fonts/gentiumplus-italic-latin-ext.woff2') format('woff2');
  unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
}
@font-face{
  font-family:'Gentium Plus';font-style:italic;font-weight:400;font-display:swap;
  src:url('fonts/gentiumplus-italic-greek.woff2') format('woff2');
  unicode-range:U+0370-0377,U+037A-037F,U+0384-038A,U+038C,U+038E-03A1,U+03A3-03FF;
}
@font-face{
  font-family:'Gentium Plus';font-style:italic;font-weight:400;font-display:swap;
  src:url('fonts/gentiumplus-italic-greek-ext.woff2') format('woff2');
  unicode-range:U+1F00-1FFF;
}
@font-face{
  font-family:'Gentium Plus';font-style:normal;font-weight:700;font-display:swap;
  src:url('fonts/gentiumplus-700-latin.woff2') format('woff2');
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}
@font-face{
  font-family:'Gentium Plus';font-style:normal;font-weight:700;font-display:swap;
  src:url('fonts/gentiumplus-700-latin-ext.woff2') format('woff2');
  unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
}
@font-face{
  font-family:'Gentium Plus';font-style:normal;font-weight:700;font-display:swap;
  src:url('fonts/gentiumplus-700-greek.woff2') format('woff2');
  unicode-range:U+0370-0377,U+037A-037F,U+0384-038A,U+038C,U+038E-03A1,U+03A3-03FF;
}
@font-face{
  font-family:'Gentium Plus';font-style:normal;font-weight:700;font-display:swap;
  src:url('fonts/gentiumplus-700-greek-ext.woff2') format('woff2');
  unicode-range:U+1F00-1FFF;
}

/* Light and dark live side by side in one declaration per token, through
   light-dark(), so a colour has one place to change. Which side applies
   follows color-scheme: the reader's system setting by default
   (color-scheme:light dark on :root), or the toggle's explicit choice, which
   theme.js records as data-theme in <head> before the first paint. Every
   dark value is checked against #181c22: text 14.5:1, dim text 5.6:1,
   accent 7.3:1, all past AA; --edge and --rule are borders, judged at the
   3:1 UI threshold. A browser without light-dark() keeps the light values
   from :root above. */
@supports (color:light-dark(#000,#fff)){
  :root{
    --bg:light-dark(#faf6ee,#181c22);
    --text:light-dark(#242424,#e9edf1);
    --text-dim:light-dark(#5a626d,#9aa4b0);
    --rule:light-dark(#E8E1D6,#3A3630);
    --edge:light-dark(#96866D,#7A7468);
    --accent:light-dark(#2F6F9F,#6FB0DE);
    --navy:light-dark(#163A5F,#5C8FBF);
    --pale:light-dark(#E7F0F6,#1C2833);
    --on-navy:light-dark(#ffffff,#111a22);
  }
}
:root[data-theme="dark"]{color-scheme:dark;}
:root[data-theme="light"]{color-scheme:light;}

/* A reader who has asked their system for more contrast gets firmer borders
   and darker secondary text. Dim text is 7:1 or better; --edge and --rule
   are borders, at 3:1 or better (light --rule 3.8:1, dark 4.6:1). */
@supports (color:light-dark(#000,#fff)){
  @media (prefers-contrast: more){
    :root{
      --text-dim:light-dark(#444c56,#b4bcc6);
      --edge:light-dark(#6b5d47,#a39d90);
      --rule:light-dark(#8a7b62,#8a8478);
    }
  }
}

/* Switching theme fades colour over a quarter of a second instead of
   flashing. theme.js sets this class for the length of the fade only, so
   nothing else on the page pays for the transition. */
html.theme-fading,html.theme-fading *{
  transition:background-color .25s ease,color .25s ease,border-color .25s ease,fill .25s ease,stroke .25s ease;
}
@media (prefers-reduced-motion: reduce){
  html.theme-fading,html.theme-fading *{transition:none;}
}

/* --- Base --------------------------------------------------------------- */

html{
  background:var(--bg);
  scrollbar-gutter:stable both-edges;

  /* Every in-page anchor (the contents panel, Ion's Stephanus jump list)
     eased rather than an instant cut to the target. */
  scroll-behavior:smooth;

  /* Anchor jumps and keyboard focus land clear of the sticky bars. */
  scroll-padding-top:1rem;

  /* Nothing on the site is wider than the screen, and this makes sure a stray
     element can never make the page slide sideways on a phone. clip, not
     hidden: hidden would turn the page into a scroll container and break the
     sticky header. */
  overflow-x:clip;

  /* iOS inflates text on its own unless told not to, which can push a layout
     wider than the screen with no element obviously at fault. */
  -webkit-text-size-adjust:100%;
  text-size-adjust:100%;
}

body{
  margin:0;
  overflow-x:clip;
  background:var(--bg);
  color:var(--text);
  font-family:var(--sans);
  font-size:1.0625rem;
  line-height:1.65;
}

a{color:var(--accent);text-decoration:none;}
a:hover{color:var(--text);}

img{max-width:100%;height:auto;}

/* App screenshots (remeta.html): a swipeable scroll-snap row. Native touch
   scrolling on phones, arrow buttons (added by carousel.js) elsewhere. */
.shots{display:flex;gap:1.2rem;margin:1.4rem 0 0;padding:0 0 0.4rem;overflow-x:auto;scroll-snap-type:x mandatory;scroll-behavior:smooth;scrollbar-width:none;overscroll-behavior-x:contain;}
.shots::-webkit-scrollbar{display:none;}
.shots:focus-visible{outline:2px solid var(--accent);outline-offset:4px;}
.shots figure{flex:0 0 13rem;margin:0;scroll-snap-align:start;}
.shots img{display:block;width:100%;height:auto;border:1px solid var(--rule);border-radius:1.1rem;}
.shots img{transition:opacity 200ms var(--ease-out);}
.shots img.pending{opacity:0;}
.shots figcaption{margin-top:0.55rem;font-size:0.82rem;line-height:1.4;color:var(--text-dim);}
.shots-nav{display:flex;align-items:center;gap:0.6rem;margin-top:0.9rem;}
.shots-nav[hidden]{display:none;}
.shots-nav button{display:inline-flex;align-items:center;justify-content:center;width:2.75rem;height:2.75rem;padding:0;border:1px solid var(--edge);border-radius:4px;background:transparent;color:var(--text);cursor:pointer;}
@media (hover:hover) and (pointer:fine){.shots-nav button:hover:not(:disabled){border-color:var(--accent);color:var(--accent);}}
.shots-nav button:disabled{opacity:0.35;cursor:default;}
.shots-nav svg{width:1rem;height:1rem;}
.shots-bar{flex:1;height:2px;background:var(--rule);border-radius:1px;overflow:hidden;}
.shots-bar i{display:block;height:100%;background:var(--accent);transform-origin:left;transform:scaleX(0);transition:transform 120ms ease-out;}
.shots-count{font-size:0.85rem;color:var(--text-dim);font-variant-numeric:tabular-nums;}
@media (max-width:520px){.shots figure{flex-basis:62vw;}.shots{gap:0.9rem;}}
@media (prefers-reduced-motion: reduce){.shots{scroll-behavior:auto;}}
/* Wider screens get larger phones, about three in view, so each screenshot
   is readable; the arrows and counter work the same as on a phone. */
@media (min-width:761px){
  .shots{gap:1.6rem;}
  .shots figure{flex-basis:17rem;}
}
.shots-note{margin:1.1rem 0 0;font-size:0.85rem;color:var(--text-dim);}

/* A reader who has turned text size up on their phone can make a single long
   word wider than the screen. Headings may break a word rather than run off
   the side; body copy already wraps normally. */
h1,h2,h3{overflow-wrap:break-word;text-wrap:balance;}
p,li,dd,blockquote{text-wrap:pretty;}

/* Digits that line up: dates, line numbers, metadata. */
.num,.entry-meta,.colophon dd,.when,time,table{font-variant-numeric:tabular-nums;}

/* Touch: no double-tap zoom delay, and a deliberate (quiet) tap highlight. */
a,button,input,select,textarea,summary,label{touch-action:manipulation;}
html{-webkit-tap-highlight-color:rgb(47 111 159 / 0.14);}

[lang="grc"],[lang="la"]{font-family:var(--serif);}

.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0;}

.skip{position:absolute;left:-9999px;top:0;z-index:60;}
.skip:focus{
  left:0;top:0;
  background:var(--bg);
  color:var(--text);
  padding:0.75rem 1.15rem;
  border:2px solid var(--accent);
  border-radius:0 0 6px 0;
  font-size:0.9rem;
  font-weight:600;
}

a:focus-visible,button:focus-visible,summary:focus-visible,
select:focus-visible,textarea:focus-visible,
[tabindex]:not([tabindex="-1"]):focus-visible{
  outline:2px solid var(--accent);
  outline-offset:3px;
}
a:focus-visible{border-radius:4px;}
input:focus-visible{outline:2px solid var(--accent);outline-offset:2px;}

/* --- Page containers ---------------------------------------------------- */

/* Reading measure, for essays and translations. */
.wrap{
  max-width:760px;
  margin:0 auto;
  padding:clamp(3rem,8vw,5rem) var(--pad) clamp(4rem,10vw,6rem);
}

/* Wider measure, for the index pages that list things. */
.page-wrap{
  max-width:880px;
  margin:0 auto;
  padding:clamp(3rem,8vw,5rem) var(--pad) clamp(5.5rem,13vw,7.5rem);
  font-size:1.09375rem;
  line-height:1.68;
}

@media (max-width:640px){
  .page-wrap{padding-top:2.2rem;}
}

/* --- Header and navigation ---------------------------------------------- */

.sitehead{
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:1rem;
  margin-bottom:2.4rem;
  padding-bottom:0.9rem;
  border-bottom:1px solid var(--rule);
}

.mark{
  font-family:var(--serif);
  font-weight:700;
  font-size:1.05rem;
  line-height:1;
  color:var(--text);
  text-decoration:none;
  letter-spacing:0.14em;
  text-indent:0.14em;
  text-transform:uppercase;
  padding:0.4rem 0.55rem;
  border:1px solid var(--navy);
  border-radius:4px;
  flex-shrink:0;
}
.mark:hover{color:var(--accent);}

.sitenav{display:flex;align-items:center;gap:0.05rem;flex-wrap:wrap;}
.sitenav a{
  font-size:0.74rem;
  font-weight:600;
  text-transform:uppercase;
  letter-spacing:0.07em;
  padding:0.55rem 0.45rem;
  color:var(--text-dim);
  text-decoration:none;
  /* Every link reserves the line the current page draws, so they all share
     one height and baseline. */
  border-bottom:1px solid transparent;
}
.sitenav a:hover{color:var(--accent);}
.sitenav a.current,.sitenav a[aria-current="page"]{color:var(--text);border-bottom:1px solid var(--navy);}

.nav-toggle{
  display:none;
  width:44px;height:44px;
  align-items:center;
  justify-content:center;
  background:transparent;
  border:1px solid var(--edge);
  border-radius:8px;
  font-size:1.2rem;
  color:var(--text);
  cursor:pointer;
  padding:0;
  line-height:1;
}
@media (hover:hover) and (pointer:fine){.nav-toggle:hover{border-color:var(--accent);}}

/* Kept in the flow and collapsed rather than display:none, so opening and
   closing can be animated. visibility keeps the links out of the tab order
   while it is shut, and is delayed on the way out so the fade is visible. */
.nav-mobile-menu{
  display:flex;
  flex-direction:column;
  max-height:0;
  opacity:0;
  visibility:hidden;
  margin-bottom:0;
  border:0 solid var(--edge);
  border-radius:8px;
  overflow:hidden;
  overscroll-behavior:contain;
  transition:max-height .28s ease, opacity .2s ease, margin-bottom .28s ease,
             border-width .28s step-end, visibility 0s linear .28s;
}
.nav-mobile-menu.open{
  max-height:32rem;
  opacity:1;
  visibility:visible;
  margin-bottom:2.4rem;
  border-width:1px;
  transition:max-height .28s ease, opacity .2s ease .04s, margin-bottom .28s ease,
             border-width 0s, visibility 0s;
}
.nav-mobile-menu a{
  padding:0.9rem 1.1rem;
  font-size:0.85rem;
  font-weight:600;
  text-transform:uppercase;
  letter-spacing:0.06em;
  color:var(--text-dim);
  text-decoration:none;
  border-bottom:1px solid var(--rule);
  min-height:44px;
  display:flex;
  align-items:center;
  box-sizing:border-box;
}
.nav-mobile-menu a:last-child{border-bottom:none;}
.nav-mobile-menu a.current,.nav-mobile-menu a[aria-current="page"]{color:var(--text);}
@media (hover:hover) and (pointer:fine){.nav-mobile-menu a:hover{color:var(--accent);}}

/* Splits site pages from the links that leave the site, so the menu isn't
   one flat list mixing two different kinds of action. */
.nav-mobile-menu .nav-group-label{
  display:block;
  margin:0;
  padding:0.75rem 1.1rem 0.35rem;
  font-size:0.75rem;
  font-weight:700;
  text-transform:uppercase;
  letter-spacing:0.07em;
  color:var(--text-dim);
}
.nav-mobile-menu .nav-group-label:not(:first-child){
  margin-top:0.3rem;
  border-top:1px solid var(--rule);
}

@media (max-width:760px){
  .sitenav{display:none;}
  .nav-toggle{display:flex;}
}

/* The wordmark and the menu button follow you down the page. The negative
   margin lets the bar reach the screen edges from inside a padded column, so
   nothing scrolls past it in the gutter.
   A phone held sideways has about 375px of height, and a 64px bar plus the
   reading tabs would eat a third of it, so below that the header scrolls away
   like anything else. */
@media (max-width:760px) and (min-height:500px){
  .sitehead{
    position:sticky;
    top:0;
    z-index:40;
    background:var(--bg);
    margin:0 calc(-1 * var(--pad)) 2.4rem;
    padding:0.4rem var(--pad);
    min-height:var(--head-h);
    box-sizing:border-box;
  }
  /* The menu hangs off the bar rather than scrolling away from it. */
  .nav-mobile-menu{
    position:sticky;
    top:var(--head-h);
    z-index:39;
    background:var(--bg);
  }
  html{scroll-padding-top:calc(var(--head-h) + 1rem);}
}

/* Phone menu: a full-height panel under the sticky bar instead of an
   accordion that shoves the page down. Large serif destinations, small caps
   group labels, the page scroll locked while it is open (nav.js sets
   html.nav-open). Short landscape screens keep the inline list above, since
   the bar is not sticky there. */
@media (max-width:760px) and (min-height:500px){
  .nav-toggle span{
    position:relative;display:block;width:18px;height:2px;
    font-size:0;background:currentColor;border-radius:1px;
    transition:background-color .26s ease;
  }
  .nav-toggle span::before,.nav-toggle span::after{
    content:"";position:absolute;left:0;width:18px;height:2px;
    background:currentColor;border-radius:1px;
    transition:transform .42s cubic-bezier(.4,.1,.2,1);
  }
  /* Matching transform lists, so the bars slide in and rotate rather than
     being matrix-interpolated along a curve. */
  .nav-toggle span::before{transform:translateY(-6px) rotate(0);}
  .nav-toggle span::after{transform:translateY(6px) rotate(0);}
  .nav-toggle[aria-expanded="true"] span{background:transparent;}
  .nav-toggle[aria-expanded="true"] span::before{transform:translateY(0) rotate(45deg);}
  .nav-toggle[aria-expanded="true"] span::after{transform:translateY(0) rotate(-45deg);}

  .nav-mobile-menu{
    position:fixed;
    top:var(--head-h);left:0;right:0;bottom:0;
    z-index:39;
    max-height:none;
    margin:0;
    border:0;border-radius:0;
    overflow-y:auto;
    padding:0.5rem var(--pad) calc(2rem + env(safe-area-inset-bottom,0px));
    background:var(--bg);
    transform:translateY(-8px);
    /* Closing snaps (the user is already leaving); opening takes the longer beat. */
    transition:opacity .15s cubic-bezier(.23,1,.32,1), transform .15s cubic-bezier(.23,1,.32,1),
               visibility 0s linear .15s;
  }
  .nav-mobile-menu.open{
    max-height:none;margin:0;border:0;
    transform:none;
    transition:opacity .18s cubic-bezier(.23,1,.32,1), transform .22s cubic-bezier(.23,1,.32,1),
               visibility 0s;
  }
  .nav-mobile-menu a{
    min-height:50px;
    padding:0.3rem 0;
    font-family:var(--serif);
    font-size:1.5rem;
    font-weight:400;
    letter-spacing:0;
    text-transform:none;
    color:var(--text);
    border-bottom:1px solid var(--rule);
  }
  .nav-mobile-menu a.current,.nav-mobile-menu a[aria-current="page"]{color:var(--accent);}
  .nav-mobile-menu a:active{color:var(--accent);background:transparent;}
  .nav-mobile-menu .nav-group-label{
    padding:1rem 0 0.2rem;
    font-size:0.72rem;
    letter-spacing:0.14em;
  }
  .nav-mobile-menu .nav-group-label:not(:first-child){
    margin-top:1rem;border-top:0;padding-top:0;
  }
  /* Contact links are secondary to the pages above them. */
  .nav-mobile-menu .nav-group-label ~ .nav-group-label ~ a{font-size:1.15rem;min-height:44px;}
  /* The bars alone are easy to miss: say what the button does. */
  .nav-toggle{width:auto;min-width:44px;padding:0 0.85rem;gap:0.6rem;}
  .nav-toggle::after{
    content:"Menu";font-family:var(--serif);font-size:0.95rem;letter-spacing:0.02em;
  }
  .nav-toggle[aria-expanded="true"]::after{content:"Close";}
  /* Theme switch lives in the menu on a phone (theme.js moves it here)
     rather than floating over the text. */
  .nav-mobile-menu .themetoggle{
    position:static;margin-top:1rem;transform:none;opacity:1;pointer-events:auto;
  }
  .nav-mobile-menu .themetoggle button{
    min-height:44px;padding:0.4rem 0.9rem;gap:0.7rem;box-shadow:none;border-radius:8px;
  }
  html.nav-open{overflow:hidden;overscroll-behavior:none;}

  /* Page contents, as a collapsible group at the top of the menu (toc.js). */
  html.toc-in-menu .toc{display:none;}
  .menu-toc summary{
    display:flex;align-items:center;justify-content:space-between;
    min-height:48px;margin-top:0.6rem;
    font-size:0.72rem;font-weight:700;letter-spacing:0.14em;text-transform:uppercase;
    color:var(--text-dim);cursor:pointer;list-style:none;
    border-bottom:1px solid var(--rule);
  }
  .menu-toc summary::-webkit-details-marker{display:none;}
  .menu-toc summary::after{content:"+";font-size:1.2rem;font-weight:400;letter-spacing:0;}
  .menu-toc[open] summary::after{content:"\2212";}
  .menu-toc div a{
    font-family:var(--serif);font-size:1.15rem;min-height:46px;padding-left:0.9rem;
    color:var(--text-dim);border-bottom:1px solid var(--rule);
  }
}

@media (prefers-reduced-motion: reduce){
  html{scroll-behavior:auto;}
  .nav-mobile-menu,.nav-mobile-menu.open{transition:none;}
  .nav-toggle span::before,.nav-toggle span::after{transition:none;}
  /* Gentler, not zero: keep a short fade on the phone panel, drop the movement. */
  @media (max-width:760px) and (min-height:500px){
    .nav-mobile-menu{transform:none;transition:opacity .12s linear, visibility 0s linear .12s;}
    .nav-mobile-menu.open{transition:opacity .12s linear, visibility 0s;}
  }
}

/* On a touch screen the small controls were 32 to 39px tall, which clears the
   WCAG minimum but is under the 44px a thumb actually wants. Applied by
   pointer type as well as width, so a touch tablet gets it too. */
@media (max-width:760px), (pointer:coarse){
  .mark{
    display:inline-flex;
    align-items:center;
    min-height:44px;
    box-sizing:border-box;
  }
  .back{
    min-height:44px;
    display:inline-flex;
    align-items:center;
  }
  .sitefoot .links a{
    min-height:44px;
    min-width:44px;
    display:inline-flex;
    align-items:center;
    padding:0;
  }
  .sitefoot .links{gap:1.2rem;}

  .pill{
    min-height:44px;
  }

  /* The "Related" list at the foot of the essays. */
  /* inline-block, not flex: a link with an <i> or lang span inside it would
     lose the spaces between its pieces if it were a flex container. */
  .related li a{
    min-height:44px;
    display:inline-block;
    box-sizing:border-box;
  }

  /* The in-page jump list (Enoda, Ion, the longer essays). */
  /* Block with padding rather than a flex container, so a link that mixes
     text with an <i> keeps the spaces between its pieces. */
  .toc li a{
    display:block;
    box-sizing:border-box;
    min-height:44px;
    padding-top:0.7rem;
    padding-bottom:0.7rem;
  }
}

/* --- Footer ------------------------------------------------------------- */

.sitefoot{
  margin-top:2rem;
  font-size:0.85rem;
  color:var(--text-dim);
}
.sitefoot .who{font-weight:600;color:var(--text);margin-bottom:0.3rem;}
.sitefoot .links{display:flex;gap:1rem;flex-wrap:wrap;margin:0.9rem 0;}
.sitefoot a{display:inline-block;padding:0.4rem 0;color:var(--text-dim);}
.sitefoot a:hover{color:var(--accent);}
/* A link inside a sentence of footer text needs more than colour to read as one. */
.sitefoot > div:not(.links) a{text-decoration:underline;text-decoration-thickness:1px;text-underline-offset:0.2em;}

/* The index pages set their footer off with a rule, since the content above it
   runs to the edge of the container rather than ending in a citation list. */
.page-wrap .sitefoot{
  margin-top:3.5rem;
  padding-top:1.7rem;
  border-top:1px solid var(--rule);
}

/* --- Shared page furniture ---------------------------------------------- */
/* The small vocabulary the index pages repeat: eyebrows above a heading,
   section labels, the baseline-aligned title/meta row, and pill links. */

.eyebrow{
  font-size:0.75rem;
  font-weight:600;
  text-transform:uppercase;
  letter-spacing:0.07em;
  color:var(--text-dim);
  margin-bottom:0.5rem;
}
.eyebrow-accent{
  font-size:0.75rem;
  font-weight:600;
  text-transform:uppercase;
  letter-spacing:0.07em;
  color:var(--accent);
}

.sec-h{
  font-size:0.85rem;
  font-weight:600;
  text-transform:uppercase;
  letter-spacing:0.1em;
  color:var(--text-dim);
  margin:0 0 1.1rem;
}
.sec-h.tight{margin-bottom:0.9rem;}
.sec-h.loose{margin-bottom:1.3rem;}

.row{
  display:flex;
  justify-content:space-between;
  align-items:baseline;
  gap:1rem;
  flex-wrap:wrap;
}
/* Flex items will not shrink past their longest word unless told they may,
   which is how one long title pushes a whole row off the screen. */
.row > *{min-width:0;}

/* On a phone, wrapping made the trailing label ("Read", a date) sit beside a
   short title but drop to its own left-aligned line under a long one, purely
   because of how long that title happened to be. Stacking is predictable, gives
   the title the full width, and cannot overflow the way a nowrap row can. */
@media (max-width:640px){
  .row{
    flex-direction:column;
    align-items:flex-start;
    gap:0.3rem;
  }
}

.dim{color:var(--text-dim);}
.nowrap{white-space:nowrap;}

.entry-title{font-family:var(--serif);font-size:1.55rem;font-weight:700;overflow-wrap:break-word;}
.entry-note{margin:0.7rem 0 0;font-size:1.02rem;color:var(--text-dim);}
.entry-meta{font-size:0.88rem;color:var(--text-dim);margin-top:0.35rem;}

/* A whole row that is a link: rule above, no underline, colour on hover. */
.listlink{
  display:block;
  text-decoration:none;
  color:var(--text);
  padding:2.2rem 0;
  border-top:1px solid var(--rule);
}
.listlink:is(:hover,:focus-visible){color:var(--accent);}
.listlink:is(:hover,:focus-visible) .arrow{color:var(--accent);}

.pill{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  box-sizing:border-box;
  max-width:100%;
  min-width:0;
  overflow-wrap:break-word;
  color:var(--text);
  text-decoration:none;
  font-size:0.85rem;
  font-weight:600;
  text-transform:uppercase;
  letter-spacing:0.05em;
  border:1px solid var(--text);
  border-radius:4px;
  padding:0.55rem 1.2rem;
}
.pill:hover{border-color:var(--accent);color:var(--accent);}

.pill-row{display:flex;gap:0.9rem;flex-wrap:wrap;align-items:center;}

/* Flex containers drop the space between a word and an arrow glyph that sits
   in its own (aria-hidden) element; a gap puts it back. */
.pill,.back,.nav-mobile-menu a{gap:0.3em;}

/* A row of three pills (the homepage hero CTAs, the Contact links) is wider
   than a narrow phone screen at the base pill size, so the third one wraps
   onto its own line. Shrinking padding, type and gap here keeps a pill-row
   to one line down to a 320px screen instead. Placed after the base .pill
   rule above so it actually wins the cascade at this width. */
@media (max-width:480px){
  .pill{
    padding:0.5rem 0.4rem;
    font-size:0.75rem;
    letter-spacing:0.01em;
  }
  .pill-row{gap:0.3rem;}
}

/* Three pills at 0.75rem still overflow a 320px screen's three-abreast rows
   (the hero CTAs, the Contact row) once padding and the gap are added back
   in, so the narrowest phones get one more size step down. Still well above
   the 10.24px this replaced. */
@media (max-width:340px){
  .pill{
    padding:0.5rem 0.35rem;
    font-size:0.7rem;
    letter-spacing:0;
  }
  .pill-row{gap:0.2rem;}
}

/* A <button> needs resets a plain <a class="pill"> doesn't, since browsers
   apply their own chrome to buttons. */
button.pill{background:transparent;font-family:inherit;cursor:pointer;-webkit-appearance:none;appearance:none;}
button.pill:hover{border-color:var(--accent);color:var(--accent);}

/* --- Subscribe form (Enoda/Remeta release notifications) ---------------- */
.subscribe-form{display:flex;gap:0.6rem;flex-wrap:wrap;}
.subscribe-form[hidden]{display:none;}
.subscribe-form input[type="email"]{
  flex:1 1 220px;
  min-width:0;
  box-sizing:border-box;
  font-family:var(--sans);
  font-size:1rem;
  color:var(--text);
  background:var(--bg);
  border:1px solid var(--edge);
  border-radius:4px;
  padding:0.55rem 1.2rem;
}
.subscribe-form input[type="email"]::placeholder{color:var(--text-dim);}
.subscribe-form input[type="email"]:focus-visible{outline:2px solid var(--accent);outline-offset:2px;}

/* Honeypot: a field real visitors never see or fill, since it sits off-screen
   rather than merely `hidden` (bots that skip hidden/display:none fields
   still tend to fill this one). subscribe.js drops the submission silently
   if it's non-empty. */
.subscribe-form .hp-field{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden;}

/* Shown by subscribe.js in place of the form once it submits. The form posts
   to a hidden iframe rather than a new tab, so the page never navigates. */
.subscribe-success{margin:0;font-size:0.95rem;color:var(--text);}

@media (max-width:760px), (pointer:coarse){
  .subscribe-form input[type="email"]{min-height:44px;}
}

/* Status labels ("In progress", "Complete", "Work in progress"). Set as the
   same dim uppercase small caps the eyebrow lines use, rather than in a
   rounded outline that competes with them. Accent marks the unfinished ones,
   so the index still scans at a glance. */
.tag,.stat,.status{
  display:inline-block;
  font-size:0.75rem;
  font-weight:700;
  text-transform:uppercase;
  letter-spacing:0.07em;
  color:var(--text-dim);
  white-space:nowrap;
}
.stat,.status{margin-right:0.6rem;}
.stat.wip,.status.wip{color:var(--accent);}
.stat.done,.status.done{color:var(--text-dim);}

.section{margin-bottom:var(--section-gap);}
.section-top{
  margin-top:var(--section-gap);
  padding-top:var(--section-gap);
  border-top:1px solid var(--rule);
}

.back{
  display:inline-block;
  color:var(--text-dim);
  font-size:0.88rem;
  margin-bottom:2.5rem;
  padding:0.45rem 0;
}
.back:hover{color:var(--accent);}

/* Page furniture that used to be repeated as inline styles. */
.entry-sub{margin-top:0.3rem;font-size:0.98rem;color:var(--text-dim);}
.entry-date{font-size:0.88rem;color:var(--text-dim);white-space:nowrap;}
.block{margin-bottom:3.2rem;}
.body-p{margin:0;font-size:1.05rem;}
.cv-entry-title{font-weight:600;font-size:1.3rem;margin:0;}
.block-end{margin-bottom:0;padding-top:2.2rem;border-top:1px solid var(--rule);}
.entry-title-sm{font-weight:600;font-size:1.05rem;margin:0;}
.entry-time{color:var(--accent);}
.page-title{font-family:var(--serif);font-weight:700;font-size:clamp(2.4rem,6vw,3.4rem);margin:0 0 0.9rem;letter-spacing:-0.01em;}
.block-ruled{margin-top:3.2rem;padding-top:1.7rem;border-top:1px solid var(--rule);}
.lede-dim{font-size:1.1rem;margin:0;color:var(--text-dim);}
.note-dim{margin:0;font-size:1.02rem;color:var(--text-dim);}

/* --- Print -------------------------------------------------------------- */
/* Screen furniture goes; ligatures are disabled so the PDF text layer
   extracts cleanly rather than gluing fi and fl into single glyphs. */

@media print{
  /* Print is always ink on white, whatever theme is on screen. */
  :root,:root:not([data-theme="light"]),:root[data-theme="dark"],:root[data-theme="light"]{
    --bg:#fff;--text:#111;--text-dim:#444;--rule:#bbb;--edge:#999;--accent:#111;--navy:#111;--pale:#fff;
  }
  @page{size:A4;margin:18mm 17mm;}
  *{font-variant-ligatures:none;-webkit-font-feature-settings:"liga" 0,"clig" 0;}
  html,body{background:#fff;color:#111;font-size:10.5pt;}
  .sitehead,.nav-mobile-menu,.nav-toggle,.back,.skip,.sitefoot,.pdflink,.viewtabs,.subscribe-form,.subscribe-success,.pill-row{display:none !important;}
  .wrap,.page-wrap{max-width:none;padding:0;}
  a{color:#111;text-decoration:none;}
  .kicker{color:#444;}
  h1{font-size:20pt;margin-bottom:0.5em;}
  .lede{color:#333;}
  .brief{border:1px solid #bbb;break-inside:avoid;}
  .essay p,p{orphans:3;widows:3;}
  h2,h3{break-after:avoid;}
  figure,.evidence-img,table,.brief,.greek,.term{break-inside:avoid;}
  img{max-width:100% !important;}
  footer{border-top:1px solid #bbb;color:#444;}
  main a[href^="http"]::after{content:" (" attr(href) ")";font-size:0.85em;color:#444;word-break:break-all;}
}

/* --- Theme toggle -------------------------------------------------------- */
/* Sun on the left, moon on the right, a sliding knob between them. Parked
   bottom right, out of the reading column on every width. */

.themetoggle{
  position:fixed;
  right:calc(clamp(0.9rem,3vw,1.6rem) + env(safe-area-inset-right,0px));
  bottom:calc(clamp(0.9rem,3vw,1.6rem) + env(safe-area-inset-bottom,0px) + var(--lift,0px));
  z-index:50;
  transition:transform .25s ease, opacity .2s ease;
}

/* A control that floats over the page will sit on top of a link sooner or
   later. It drops out of the way while you scroll down and comes back the
   moment you scroll up or reach the end. */
.themetoggle.tucked{
  transform:translateY(180%);
  opacity:0;
  pointer-events:none;
}
/* Tabbing to a tucked control brings it back, so a keyboard user never
   focuses something invisible. */
.themetoggle.tucked:focus-within,.toc.tucked:focus-within,.totop.visible.tucked:focus-within{
  transform:none;
  opacity:1;
  pointer-events:auto;
}

/* --lift is set by theme.js, on the root (not this element - .totop reads
   it too), when the toggle would otherwise land on a link or a button at
   first paint. */

.themetoggle button{
  display:flex;
  align-items:center;
  gap:0.45rem;
  min-height:44px;
  padding:0.4rem 0.7rem;
  font:inherit;
  color:var(--text-dim);
  background:var(--bg);
  border:1px solid var(--edge);
  border-radius:4px;
  cursor:pointer;
  box-shadow:0 1px 6px rgb(0 0 0 / 0.10);
}
.themetoggle button:hover{color:var(--text);border-color:var(--accent);}

.themetoggle .ic{width:15px;height:15px;flex:none;display:block;}

/* The track and knob are the switch itself. */
.themetoggle .track{
  position:relative;
  width:34px;
  height:18px;
  flex:none;
  border-radius:999px;
  background:var(--rule);
  border:1px solid var(--edge);
  box-sizing:border-box;
}
.themetoggle .knob{
  position:absolute;
  top:2px;
  left:2px;
  width:12px;
  height:12px;
  border-radius:50%;
  background:var(--navy);
  transition:transform .22s ease;
}
.themetoggle button[aria-checked="true"] .knob{transform:translateX(16px);}

/* Whichever side is active is the one lit up. */
.themetoggle button[aria-checked="false"] .sun,
.themetoggle button[aria-checked="true"] .moon{color:var(--accent);}

/* Less of it on a phone, so it covers less of what is underneath. */
@media (max-width:760px){
  .themetoggle button{gap:0.35rem;padding:0.35rem 0.55rem;}
  .themetoggle .ic{width:13px;height:13px;}
  .themetoggle .track{width:28px;height:16px;}
  .themetoggle .knob{width:10px;height:10px;}
  .themetoggle button[aria-checked="true"] .knob{transform:translateX(12px);}
}

@media (prefers-reduced-motion: reduce){
  .themetoggle,.themetoggle .knob{transition:none;}
}

@media print{
  .themetoggle{display:none !important;}
}

/* --- In-page contents ----------------------------------------------------
   A quiet button in the bottom-left corner, mirroring the theme toggle in
   the bottom-right and wearing the same shell, which opens a panel of the
   page's sections over the page. Nothing sits in the margin or in the
   reading column until it is asked for. */

.toc{
  position:fixed;
  left:calc(clamp(0.9rem,3vw,1.6rem) + env(safe-area-inset-left,0px));
  /* + var(--lift,0px): tracks the same lift the right-hand corner uses, so
     the two corners' buttons stay level with each other instead of the
     left one sitting still while the right one rises. */
  bottom:calc(clamp(0.9rem,3vw,1.6rem) + env(safe-area-inset-bottom,0px) + var(--lift,0px));
  z-index:50;
  transition:transform .25s ease, opacity .2s ease;
}
/* Tucks away while reading down the page, like the theme toggle - comes
   back on scroll-up, at the top, or at the end. Never tucks while its own
   panel is open (toc.js), so it can't vanish out from under an open menu. */
.toc.tucked{
  transform:translateY(180%);
  opacity:0;
  pointer-events:none;
}
.toc-btn{
  display:flex;
  align-items:center;
  gap:0.45rem;
  min-height:44px;
  padding:0.4rem 0.75rem;
  font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif;
  font-size:0.75rem;
  font-weight:700;
  text-transform:uppercase;
  letter-spacing:0.07em;
  color:var(--text-dim);
  background:var(--bg);
  border:1px solid var(--edge);
  border-radius:4px;
  cursor:pointer;
  box-shadow:0 1px 6px rgb(0 0 0 / 0.10);
}
.toc-btn:hover{color:var(--text);border-color:var(--accent);}
/* Three short rules, drawn rather than shipped as an icon font. */
.toc-btn .ic{width:13px;height:9px;flex:none;display:block;}

.toc-panel{
  position:absolute;
  bottom:calc(100% + 0.5rem);
  left:0;
  width:min(17.5rem,calc(100vw - 2.5rem));
  max-height:min(60vh,26rem);
  max-height:min(60dvh,26rem);
  overflow-y:auto;
  overscroll-behavior:contain;
  box-sizing:border-box;
  padding:1rem 1.15rem 1.1rem;
  /* A scroll shadow, not a hard clip: the long lists (tyranny-in-athens has
     14 entries) were cutting the last visible link off mid-line with
     nothing to say there was more below. The two gradients that scroll
     with the content (background-attachment:local) cover the two that
     don't (:scroll) whenever the panel is at that edge, so the shadow
     appears only where there is genuinely more to see. */
  background-color:var(--bg);
  background-image:
    linear-gradient(var(--bg), transparent),
    linear-gradient(transparent, var(--bg)),
    radial-gradient(farthest-side at 50% 0, rgb(0 0 0 / 0.18), transparent),
    radial-gradient(farthest-side at 50% 100%, rgb(0 0 0 / 0.18), transparent);
  background-repeat:no-repeat;
  background-position:top, bottom, top, bottom;
  background-size:100% 28px, 100% 28px, 100% 12px, 100% 12px;
  background-attachment:local, local, scroll, scroll;
  border:1px solid var(--rule);
  /* Lifted by its shadow alone in light; in dark, where a shadow cannot
     show, by the hairline edge alone. Ignored where light-dark() is not
     supported, which keeps the edge. */
  border-color:light-dark(transparent,var(--rule));
  border-radius:8px;
  box-shadow:0 6px 24px rgb(0 0 0 / 0.14);
  opacity:0;
  visibility:hidden;
  transform:translateY(6px);
  transition:opacity .22s ease, transform .22s ease, visibility 0s linear .22s;
}
.toc.open .toc-panel{
  opacity:1;
  visibility:visible;
  transform:translateY(0);
  transition:opacity .22s ease, transform .22s ease, visibility 0s;
}
.toc-panel-head{
  display:flex;
  align-items:baseline;
  justify-content:space-between;
  gap:0.8rem;
  margin:0 0 0.6rem;
}
.toc-panel-label{
  margin:0;
  font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif;
  font-size:0.68rem;
  font-weight:700;
  text-transform:uppercase;
  letter-spacing:0.07em;
  color:var(--text-dim);
}
/* A visible way to close the panel, alongside the outside-click and Escape
   that already work but aren't discoverable on their own. */
.toc-panel-close{
  flex:none;
  display:flex;
  align-items:center;
  justify-content:center;
  position:relative;
  width:1.5rem;
  height:1.5rem;
  margin:-0.2rem -0.3rem 0 0;
  padding:0;
  color:var(--text-dim);
  background:transparent;
  border:0;
  border-radius:4px;
  cursor:pointer;
}
.toc-panel-close:hover{color:var(--text);}
/* The icon stays small; the tappable area reaches 44px. */
.toc-panel-close::after{content:"";position:absolute;inset:-0.65rem;}
.toc-panel-close .ic{width:11px;height:11px;flex:none;display:block;}
.toc-panel ol{list-style:none;margin:0;padding:0;font-size:0.88rem;line-height:1.35;}
.toc-panel li{margin:0;}
.toc-panel a{display:block;padding:0.4rem 0;color:var(--text);text-decoration:none;}
.toc-panel a:hover{color:var(--accent);}

@media (prefers-reduced-motion: reduce){
  .toc,.toc-panel,.toc.open .toc-panel{transition:none;}
}

@media print{
  .toc{display:none !important;}
}

/* --- Colophon --------------------------------------------------------------
   A row of label/value pairs for a project page's vitals - used in place of
   a stat-card grid or a row of pill chips, either of which reads as a pitch
   deck rather than the rest of the site. Shared by enoda.html and
   remeta.html; give any future project page the same thing rather than a
   bespoke stat row. */
.colophon{
  display:grid;
  grid-template-columns:auto 1fr;
  gap:0.7rem 2rem;
  margin:0;
  padding:1.5rem 0;
  border-top:1px solid var(--rule);
  border-bottom:1px solid var(--rule);
  font-size:0.97rem;
}
.colophon dt{
  font-size:0.75rem;
  font-weight:600;
  text-transform:uppercase;
  letter-spacing:0.07em;
  color:var(--text-dim);
  padding-top:0.2rem;
}
.colophon dd{margin:0;}
@media (max-width:560px){
  .colophon{grid-template-columns:1fr;gap:0.15rem 0;}
  .colophon dd{margin-bottom:0.7rem;}
  .colophon dd:last-child{margin-bottom:0;}
}

/* --- Back to top ----------------------------------------------------------
   Appears once the top of the page has scrolled out of view - shown by
   scroll position, not direction, unlike the theme toggle just below it,
   which tucks away while actively scrolling down. Stacked directly above
   it in the same corner (44px button height + gap), sharing its shell. */

.totop{
  position:fixed;
  right:calc(clamp(0.9rem,3vw,1.6rem) + env(safe-area-inset-right,0px));
  /* + var(--lift,0px): the theme toggle just below it can lift itself clear
     of a link at first paint (theme.js), and this needs to rise the same
     amount or the toggle lifts straight into it - a real, reachable overlap
     the mobile audit caught on translations.html/projects.html at 375-390px. */
  bottom:calc(clamp(0.9rem,3vw,1.6rem) + env(safe-area-inset-bottom,0px) + var(--lift,0px) + 44px + 0.65rem);
  z-index:50;
  opacity:0;
  visibility:hidden;
  transform:translateY(8px);
  transition:opacity .22s ease, transform .22s ease, visibility 0s linear .22s;
}
.totop.visible{
  opacity:1;
  visibility:visible;
  transform:translateY(0);
  transition:opacity .22s ease, transform .22s ease, visibility 0s;
}
.totop button{
  display:flex;
  align-items:center;
  justify-content:center;
  width:44px;
  height:44px;
  padding:0;
  color:var(--text-dim);
  background:var(--bg);
  border:1px solid var(--edge);
  border-radius:4px;
  cursor:pointer;
  box-shadow:0 1px 6px rgb(0 0 0 / 0.10);
}
.totop button:hover{color:var(--text);border-color:var(--accent);}
.totop .ic{width:14px;height:14px;flex:none;display:block;}

/* Tucks away while reading down the page, same as the theme toggle and the
   contents button (totop.js); back on scroll-up, at the top or at the end. */
.totop.visible.tucked{
  transform:translateY(180%);
  opacity:0;
  pointer-events:none;
}

/* Phones: full-width text means a fixed button will sit over some of it
   whenever it is showing. Make the three controls smaller and a little
   see-through so they cover less, and so what is under them still reads. */
@media (max-width:600px){
  .themetoggle button,
  .toc-btn,
  .totop button{
    background:var(--bg);
    background:color-mix(in srgb, var(--bg) 82%, transparent);
    -webkit-backdrop-filter:blur(6px);
    backdrop-filter:blur(6px);
    box-shadow:none;
  }
  .themetoggle button{min-height:38px;padding:0.25rem 0.55rem;}
  .toc-btn{padding:0.25rem 0.55rem;}
  .totop button{width:38px;height:38px;}
  .totop{bottom:calc(clamp(0.9rem,3vw,1.6rem) + env(safe-area-inset-bottom,0px) + var(--lift,0px) + 38px + 0.5rem);}
}

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

@media print{
  .totop{display:none !important;}
}

/* --- Motion ----------------------------------------------------------------
   Small, fast, and only where something would otherwise snap or teleport.
   Nothing here moves reading text for style. */

/* Press feedback. */
.pill,button.pill,.viewtabs button{transition:transform 160ms var(--ease-out);}
.pill:active,button.pill:active,.viewtabs button:active{transform:scale(0.97);}
.nav-toggle,.themetoggle button,.totop button,.toc>button,.shots-nav button{transition:transform .12s ease,border-color .2s ease,color .2s ease;}
.nav-toggle:active,.themetoggle button:active,.totop button:active,.toc>button:active,.shots-nav button:not(:disabled):active{transform:scale(0.95);}

/* Hover colour easing, on devices that actually hover. Pills keep their
   press transition, so it is restated alongside the colour ones. */
@media (hover:hover) and (pointer:fine){
  a,.toc-btn,.totop button{transition:color 120ms ease-out, border-color 120ms ease-out;}
  .pill,button.pill{
    transition:transform 160ms var(--ease-out), color 120ms ease-out, border-color 120ms ease-out;
  }
  .listlink .arrow{transition:color 120ms ease-out, transform 150ms var(--ease-out);}
  .listlink:hover .arrow{transform:translateX(3px);}

  /* Project cards: their arrow nudges like the list-link arrows above. */
  .hv1 .eyebrow-accent [aria-hidden="true"]{display:inline-block;transition:transform 150ms var(--ease-out);}
  .hv1:hover .eyebrow-accent [aria-hidden="true"]{transform:translateX(3px);}

  /* Header links: a thin accent line draws in from the left under the text,
     on the same baseline as the solid line under the current page (which
     keeps its own). It stops at the text, so it never runs into a
     neighbour's line. */
  .sitenav a{position:relative;}
  .sitenav a::after{
    content:"";
    position:absolute;
    left:0.45rem;
    right:0.45rem;
    bottom:-1px;
    height:1px;
    background:var(--accent);
    transform:scaleX(0);
    transform-origin:left center;
    /* Slower and more even than the press and arrow motions: a line being
       drawn should be seen drawing. */
    transition:transform 240ms cubic-bezier(0.4,0,0.2,1);
  }
  .sitenav a{transition:color 170ms ease-out;}
  .sitenav a:hover::after{transform:scaleX(1);}
  .sitenav a.current::after,.sitenav a[aria-current="page"]::after{display:none;}
}

/* Subscribe confirmation: fades and rises in where the form was. */
.subscribe-success:not([hidden]){display:flex;align-items:center;}
.subscribe-success{
  opacity:1;
  transform:none;
  transition:opacity 200ms var(--ease-out), transform 200ms var(--ease-out), display 200ms allow-discrete;
}
@starting-style{
  .subscribe-success{opacity:0;transform:translateY(6px);}
}
@media (prefers-reduced-motion: reduce){
  .pill,button.pill,.viewtabs button{transition:none;}
  .pill:active,button.pill:active,.viewtabs button:active{transform:none;}
  .nav-toggle,.themetoggle button,.totop button,.toc>button,.shots-nav button{transition:none;}
  .nav-toggle:active,.themetoggle button:active,.totop button:active,.toc>button:active,.shots-nav button:active{transform:none;}
  a,.toc-btn,.totop button,.listlink .arrow{transition:none;}
  .listlink:hover .arrow{transform:none;}
  .hv1:hover .eyebrow-accent [aria-hidden="true"]{transform:none;}
  .sitenav a::after{transition:none;}
  .subscribe-success{transition:opacity 200ms ease-out;}
  @starting-style{
    .subscribe-success{transform:none;}
  }
}

/* Jump target: after a contents or Stephanus jump, tint the landing spot and
   let it fade, so the eye finds where the page stopped. :target covers native
   fragment links and a page opened with a #hash; showview.js adds .jump-flash
   for its own scripted jumps, which never update :target. */
@keyframes jump-flash{
  0%,30%{background-color:color-mix(in srgb, var(--accent) 16%, transparent);}
  100%{background-color:transparent;}
}
:target,.jump-flash{animation:jump-flash 1.4s ease-out;}
:target:is(main,body){animation:none;}
@media (prefers-reduced-motion: reduce){
  :target,.jump-flash{animation:none;}
}

/* Reading progress: a 4px line along the top of the longer pages (the ones
   with a contents button), driven by scroll position in CSS alone. Browsers
   without scroll-driven animations simply don't show it. */
@supports (animation-timeline: scroll()){
  @keyframes read-progress{from{transform:scaleX(0);}to{transform:scaleX(1);}}
  body:has(.toc):not([data-section="projects.html"])::before{
    content:"";
    position:fixed;
    top:0;
    left:0;
    right:0;
    height:4px;
    z-index:61;
    background:var(--accent);
    transform-origin:0 50%;
    transform:scaleX(0);
    pointer-events:none;
    animation:read-progress linear both;
    animation-timeline:scroll(root block);
  }
}
@media (prefers-reduced-motion: reduce){
  body::before{display:none !important;}
}
@media print{
  body::before{display:none !important;}
}

/* Hero actions on a phone: three full-width, thumb-sized buttons instead of a
   cramped row of small ones. */
@media (max-width:480px){
  .hero-pills{flex-direction:column;align-items:stretch;gap:0.75rem;}
  .hero-pills .pill{min-height:48px;font-size:0.85rem;letter-spacing:0.05em;padding:0.55rem 1rem;justify-content:space-between;}
}
