/* ==========================================================================
   Md Sadik Hossain Shanto
   Masthead at the top left, then an editorial ledger: section labels sit in a
   right-aligned margin against a vertical rule, content runs beside them.
   Aquamarine twice over: deep for links, light as a highlighter.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Tokens
   -------------------------------------------------------------------------- */

:root {
  --bg:      #fbfbf8;
  --bg2:     #f2f4ee;
  --card:    #ffffff;
  --fg:      #14181a;
  --fg2:     #4f5651;
  --fg3:     #78807a;
  --line:    #e3e6df;
  --line2:   #c7cec6;

  --ac:      #0b7a6b;                    /* deep aquamarine: links, numerals */
  --ac2:     #085f53;
  --acs:     rgba(11, 122, 107, .085);   /* tint fill                        */
  --ul:      rgba(11, 122, 107, .34);    /* the emphasis hairline            */
  --wash:    rgba(120, 232, 204, .40);   /* selection, the Resume hover fill */

  --shadow:  0 1px 2px rgba(20, 24, 26, .04), 0 16px 34px -20px rgba(20, 24, 26, .2);

  --ser: "Newsreader", Iowan Old Style, Palatino, Georgia, serif;
  --ui:  "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;

  --bar:    56px;
  --margin: 206px;   /* the label column */
  --split:  34px;    /* padding either side of the vertical rule */
  --pad:    clamp(22px, 4.6vw, 72px);
  /* 1300, not 1220. The two research tracks sit side by side, so the content
     column is halved there and at 1220 each track ran about 38 characters wide
     — a tall, narrow, vertically stressed column. This buys each one about 45.
     Nothing else stretches with it: every running-text block on the page is
     capped in `ch`, so the wider page shows as margin, not as longer lines. */
  --maxw:   1300px;
  --ez:     cubic-bezier(.2, .7, .2, 1);

  color-scheme: light;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg:     #0a0d0c;
    --bg2:    #111614;
    --card:   #121917;
    --fg:     #ecefe9;
    --fg2:    #a1a9a3;
    --fg3:    #79827c;
    --line:   #1e2523;
    --line2:  #303935;
    --ac:     #5fe3c6;
    --ac2:    #8cf0da;
    --acs:    rgba(95, 227, 198, .10);
    --ul:     rgba(95, 227, 198, .40);
    --wash:   rgba(95, 227, 198, .24);
    --shadow: 0 1px 2px rgba(0,0,0,.4), 0 16px 34px -20px rgba(0,0,0,.7);
    color-scheme: dark;
  }
}
:root[data-theme="dark"] {
  --bg:     #0a0d0c;
  --bg2:    #111614;
  --card:   #121917;
  --fg:     #ecefe9;
  --fg2:    #a1a9a3;
  --fg3:    #79827c;
  --line:   #1e2523;
  --line2:  #303935;
  --ac:     #5fe3c6;
  --ac2:    #8cf0da;
  --acs:    rgba(95, 227, 198, .10);
  --ul:     rgba(95, 227, 198, .40);
  --wash:   rgba(95, 227, 198, .24);
  --shadow: 0 1px 2px rgba(0,0,0,.4), 0 16px 34px -20px rgba(0,0,0,.7);
  color-scheme: dark;
}

/* --------------------------------------------------------------------------
   2. Base
   -------------------------------------------------------------------------- */

*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--bar) + 20px);
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font: 400 18.5px/1.66 var(--ser);
  font-optical-sizing: auto;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  transition: background-color .25s var(--ez), color .25s var(--ez);
  overflow-x: clip; /* clip, not hidden: hidden would break position:sticky */
}

::selection { background: var(--wash); color: var(--fg); }

img { max-width: 100%; display: block; }
h1, h2, h3 { margin: 0; font-weight: 400; }
/* Long paper titles and URLs must never push the page sideways. */
h1, h2, h3, p, li, dd, figcaption { overflow-wrap: break-word; }
p, ul, ol, dl, dd { margin: 0; padding: 0; }
ul { list-style: none; }
a { color: inherit; }

:focus-visible { outline: 2px solid var(--ac); outline-offset: 3px; border-radius: 3px; }

.sprite { position: absolute; width: 0; height: 0; overflow: hidden; }
svg { width: 18px; height: 18px; flex: none; fill: none; stroke: currentColor;
      stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }
svg.gh { fill: currentColor; stroke: none; }

.skip {
  position: absolute; left: 16px; top: -60px; z-index: 100;
  padding: 10px 16px; background: var(--fg); color: var(--bg);
  font: 500 14px/1 var(--ui); border-radius: 6px; text-decoration: none;
  transition: top .2s var(--ez);
}
.skip:focus { top: 14px; }

/* --- two emphasis levels, used everywhere and nothing else ----------------- */

.key { color: var(--ac); font-weight: 500; }
/* A .key that is also a link already carries the accent and, from the rule below,
   the arrow. The browser's default underline on top of those is one signal too
   many, so it moves to hover, matching .org a. */
a.key {
  text-decoration: none; border-bottom: 1px solid transparent;
  transition: border-color .2s var(--ez);
}
a.key:hover { border-bottom-color: currentColor; }

/* A hairline drawn under the phrase, not a marker pen over it. The wash version
   was too loud on paper, especially with several on screen at once. */
.hl {
  color: var(--fg); font-weight: 500;
  padding-bottom: 2px;
  background-image: linear-gradient(var(--ul), var(--ul));
  background-repeat: no-repeat;
  background-position: 0 100%;
  background-size: 100% 1px;
  -webkit-box-decoration-break: clone;
  box-decoration-break: clone;
}
.js [data-reveal] .hl { background-size: 0% 1px; }
.js [data-reveal].is-in .hl {
  background-size: 100% 1px;
  transition: background-size .8s var(--ez) .2s;
}

.go {
  display: inline-flex; align-items: center; gap: 5px;
  font: 500 12.5px/1 var(--ui);
  color: var(--ac); text-decoration: none;
  padding: 7px 11px; border: 1px solid var(--line2); border-radius: 999px;
  transition: color .2s var(--ez), border-color .2s var(--ez), background-color .2s var(--ez);
}
.go svg { width: 13px; height: 13px; stroke-width: 1.8; opacity: .7; }
.go:hover { color: var(--ac2); border-color: var(--ac); background: var(--acs); }
:root[data-theme="dark"] .go:hover { color: var(--ac2); }
.go.sm { padding: 4px 9px; font-size: 11.5px; margin: 8px 6px 0 0; }

/* Links that carry no pill and no icon — paper titles, project titles, an author's
   Scholar profile, an employer's site — read as plain text until the pointer is
   already on them, which is too late to be useful. They get the same north-east
   arrow the .go pills carry, so one mark means "this goes somewhere" everywhere
   on the page. Sized in em so it tracks whatever it trails, from a 25px heading
   down to a 15px author line, and set in the UI font because Newsreader has no
   glyph for U+2197 and would fall back to something that does not match. */
.pub h3 a::after,
.card h3 a::after,
.pauth a.sr .at::before,
.withs a.sr .at::before,
.org a::after,
a.key::after {
  content: "\2197";
  display: inline-block;
  font-family: var(--ui); font-weight: 400; font-style: normal; line-height: 1;
  font-size: .64em; font-size: max(11px, .64em);   /* floor it in the 15px author lines */
  margin-left: .2em; vertical-align: .2em;
  color: var(--ac); opacity: .68;
  transition: opacity .2s var(--ez), transform .2s var(--ez);
}
.pub h3 a:hover::after,
.card h3 a:hover::after,
.pauth a.sr:hover .at::before,
.withs a.sr:hover .at::before,
.org a:hover::after,
a.key:hover::after { opacity: 1; transform: translate(1px, -1px); }

/* --------------------------------------------------------------------------
   3. Running bar
   -------------------------------------------------------------------------- */

.bar {
  position: sticky; top: 0; z-index: 40; height: var(--bar);
  background: var(--bg); /* fallback where color-mix is unsupported */
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  backdrop-filter: saturate(1.5) blur(12px);
  -webkit-backdrop-filter: saturate(1.5) blur(12px);
  border-bottom: 1px solid transparent;
  transition: border-color .3s var(--ez);
}
@supports not (backdrop-filter: blur(1px)) { .bar { background: var(--bg); } }
.bar.is-stuck { border-bottom-color: var(--line); }

.bar-in {
  max-width: var(--maxw); height: 100%; margin: 0 auto; padding: 0 var(--pad);
  display: flex; align-items: center; gap: 14px;
}

/* A running head, the way a printed book carries the chapter on every page.

   It yields the bar's width before the nav does. Both used to be sized the other
   way round — `flex: none` here and a shrinkable .nav — so the moment the nav
   grew (it now reads "Research Interests", not "Interests") the nav was the one
   that lost, scrolling its last items out of sight while the running head kept
   every pixel. Navigation is the bar's job and the running head is a flourish,
   so the flourish shrinks: a shrink factor of 4 against the nav's 1, and the
   section label ellipsises rather than clipping mid-word. The name never
   shrinks. */
.running {
  flex: 0 4 auto; min-width: 0; display: flex; align-items: baseline; gap: 9px;
  font: 500 17px/1 var(--ser); letter-spacing: -.01em;
  opacity: 0; transform: translateY(3px);
  transition: opacity .35s var(--ez), transform .35s var(--ez);
  pointer-events: none;
}
.bar.is-stuck .running { opacity: 1; transform: none; pointer-events: auto; }
.running a { flex: none; text-decoration: none; }
.running a:hover { color: var(--ac); }
.running b::before { content: "/"; color: var(--line2); margin-right: 9px; font-weight: 400; }
.running b {
  min-width: 0; overflow: hidden; text-overflow: ellipsis;
  font: 500 11px/1 var(--ui); letter-spacing: .14em; text-transform: uppercase;
  color: var(--ac); white-space: nowrap;
}
.running b:empty { display: none; }

/* The nav never shrinks while the running head is on screen. It used to have a
   shrink factor of 1 against the running head's 4, but flex shrinking is shared,
   not exclusive, so at 1440px with "01 Research Interests" in the head the nav
   still lost about 30px and "Contact" slid under the Resume button. The running
   head now takes all of the squeeze, and it hides below 1200px, the width at
   which the name and the whole nav stop fitting side by side. */
.nav { margin-left: auto; flex: none; display: flex; gap: 2px; overflow-x: auto; scrollbar-width: none; }
.nav::-webkit-scrollbar { display: none; }
/* Set by site.js only when the links actually overflow, so the fade is a real
   "there is more this way" rather than a permanent smudge on the last item. */
.nav.is-scroll:not(.at-end) {
  -webkit-mask-image: linear-gradient(90deg, #000 0, #000 calc(100% - 26px), transparent 100%);
  mask-image: linear-gradient(90deg, #000 0, #000 calc(100% - 26px), transparent 100%);
}
.nav a {
  position: relative; flex: none; padding: 7px 10px; border-radius: 6px;
  font: 500 13.5px/1 var(--ui); color: var(--fg3);
  text-decoration: none; white-space: nowrap;
  transition: color .2s var(--ez), background-color .2s var(--ez);
}
.nav a::after {
  content: ""; position: absolute; left: 10px; right: 10px; bottom: 1px; height: 2px;
  background: var(--ac); transform: scaleX(0); transition: transform .32s var(--ez);
}
.nav a:hover { color: var(--fg); }
.nav a.is-active { color: var(--ac); }
.nav a.is-active::after { transform: scaleX(1); }

.cv-btn {
  flex: none; display: inline-flex; align-items: center; gap: 7px;
  padding: 7px 13px; border-radius: 999px;
  border: 1px solid var(--ac); background: var(--acs); color: var(--ac);
  font: 500 13.5px/1 var(--ui); text-decoration: none;
  transition: background-color .2s var(--ez), color .2s var(--ez);
}
.cv-btn svg { width: 15px; height: 15px; }
.cv-btn:hover { background: var(--wash); color: var(--ac2); }
:root[data-theme="dark"] .cv-btn:hover { color: var(--fg); }

.theme {
  flex: none; width: 34px; height: 34px; display: grid; place-items: center;
  background: none; border: 1px solid var(--line); border-radius: 8px;
  color: var(--fg3); cursor: pointer;
  transition: color .2s var(--ez), border-color .2s var(--ez), background-color .2s var(--ez);
}
.theme:hover { color: var(--ac); border-color: var(--line2); background: var(--acs); }
.theme .ic-moon { display: none; }
:root[data-theme="dark"] .theme .ic-sun { display: none; }
:root[data-theme="dark"] .theme .ic-moon { display: block; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .theme .ic-sun { display: none; }
  :root:not([data-theme="light"]) .theme .ic-moon { display: block; }
}

/* --------------------------------------------------------------------------
   4. Masthead
   -------------------------------------------------------------------------- */

.page { max-width: var(--maxw); margin: 0 auto; padding: 0 var(--pad); }

/* Tight, because the whole identity band plus the intro has to land inside one
   screen on a laptop: portrait, name, designations, link row, the two-column
   intro, and the curve arriving at the fold as the sign there is more below. */
.mast { padding: clamp(30px, 4.2vw, 54px) 0 clamp(24px, 3.2vw, 38px); }

/* Two axes, not one. Who he is sits on the indented axis: the portrait in the
   left column, the name, the designations and the link row beside it. What he
   does breaks back out to the page edge underneath, so the bio starts a new
   movement instead of continuing the link row.
   The indented axis is the ledger's: the portrait column is exactly --margin
   wide and the gap is exactly --split, so the portrait's right edge falls on
   the vertical rule the section labels use, and the name starts on the axis the
   section content keeps. Row rhythm is the margin each block already carries,
   so the grid sets no row gap of its own. */
.mast-grid {
  display: grid;
  grid-template-columns: var(--margin) minmax(0, 1fr);
  column-gap: var(--split);
  align-items: start;
}
.portrait { grid-column: 1; grid-row: 1 / span 3; }
.name     { grid-column: 2; grid-row: 1; }
.role     { grid-column: 2; grid-row: 2; }
.toplinks { grid-column: 2; grid-row: 3; }
.bio      { grid-column: 1 / -1; grid-row: 4; }

/* No kicker above the name. The greeting opens the bio instead. */
.name {
  font-family: var(--ser); font-weight: 500;
  font-size: clamp(2.5rem, 5.8vw, 4.05rem); line-height: 1.02; letter-spacing: -.026em;
  max-width: 14ch;
}
/* Three designations, set small so they cost the masthead as little height as
   possible. What keeps them visible is not size: one accent rule marks the whole
   block, the way a pulled quote is marked, and the institutions are .key. The
   rule stands on the name's left edge, so the designations read as indented
   under it rather than as a list of three unrelated items. */
.role {
  margin-top: 15px; padding-left: 16px;
  border-left: 2px solid var(--ac);
  font: 400 14.5px/1.5 var(--ui);
  letter-spacing: .006em; color: var(--fg2);
}
.role .ln { display: block; text-wrap: balance; }
.role .ln + .ln { margin-top: 4px; }
/* Institution names link out; same underline-on-hover as .org. Never split a
   name across a wrap. */
.role a {
  white-space: nowrap; text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: border-color .2s var(--ez);
}
.role a:hover { border-bottom-color: currentColor; }

/* One hairline only. The band below the links is the page-wide rule on .bio,
   and two rules that close together, one of them stopping short of the page
   edge, read as a mistake. */
.toplinks {
  display: flex; flex-wrap: wrap; gap: 10px 24px;
  margin-top: 20px; padding: 14px 0 0;
  border-top: 1px solid var(--line);
}
.toplinks a {
  display: inline-flex; align-items: center; gap: 8px;
  font: 500 15px/1 var(--ui); color: var(--fg2); text-decoration: none;
  transition: color .2s var(--ez);
}
.toplinks svg { width: 17px; height: 17px; color: var(--ac); transition: transform .25s var(--ez); }
.toplinks a:hover { color: var(--ac); }
.toplinks a:hover svg { transform: translateY(-2px); }

/* --- the supervisors and collaborators line -------------------------------- */

/* It closes the intro band, running the full width under both prose columns. A
   "Ph.D. applications, Fall 2027" pill held this slot and is gone: a page of this
   shape, reading this way, is already an application, and saying so spent the
   masthead's one remaining line on the least scarce fact on it. Who the work was
   done with is the scarce one.
   Set as a labelled line rather than a sentence, because it is scanned rather than
   read — the uppercase 10.5px label .courses already established. Names and posts
   repeat the .sr / .at pair from the author lines in 02, which is what stops it
   reading as a second vocabulary.
   Full width, not half: at six names with posts it is two lines across the band
   and five down one column, and the second shape pushed the curve under the fold. */
.withs {
  margin-top: 6px;
  font: 400 15px/1.72 var(--ui); color: var(--fg2);
}
/* Weight 400, not 500. This label sits directly under a column that carries no
   bold at all — the drop cap is that column's only emphasis — and a semibold
   label under it read as a heading for the whole band rather than for its own
   line. Letterspaced uppercase is doing the work; weight would be a second signal
   saying the same thing. */
.withs .lbl {
  display: block; font: 400 10.5px/1 var(--ui); letter-spacing: .16em;
  text-transform: uppercase; color: var(--fg3); margin-bottom: 8px;
}
.withs .sr { color: var(--ac); font-weight: 500; }
/* Institution only, not the full post. The titles are on the author lines, where
   a committee reads them; here they would triple the length of a masthead line
   that exists to be taken in at a glance. 16px because Newsreader italic sets
   smaller than Inter at equal px, the same correction .pauth .at makes. */
.withs .sr .at {
  font: italic 400 16px/1 var(--ser); color: var(--ac); letter-spacing: 0;
}
.withs a.sr { text-decoration: none; }
.withs a.sr:hover, .withs a.sr:hover .at { color: var(--ac2); }

/* An editorial portrait, not an avatar: tall, full colour, no card, no caption.
   The ratio lives on the figure, never on the img. An img carrying width and
   height attributes has those mapped to CSS as presentational hints, which
   leaves neither dimension `auto`, and `aspect-ratio` is then ignored outright. */
.portrait {
  margin: 0; overflow: hidden;
  aspect-ratio: 3 / 4;
  border: 1px solid var(--line); background: var(--bg2);
}
.portrait img {
  width: 100%; height: 100%; object-fit: cover; object-position: 40% 12%;
  transition: transform .6s var(--ez);
}
.portrait:hover img { transform: scale(1.03); }

/* --- the motto, drawn the full width of the page -------------------------- */

.traj { margin: clamp(22px, 3vw, 36px) 0 0; }
.tj-wrap { position: relative; height: clamp(42px, 5vw, 66px); }
.tj-wrap svg { width: 100%; height: 100%; display: block; stroke: none; }
.tj-base {
  stroke: var(--line2); stroke-width: 1; stroke-dasharray: 3 7; stroke-linecap: round;
}
.tj-curve {
  fill: none; stroke: var(--ac); stroke-width: 2; stroke-linecap: round;
  stroke-dasharray: 1400; stroke-dashoffset: 1400;
}
.tj-dot {
  position: absolute; left: 98.33%; top: 8%;
  width: 9px; height: 9px; border-radius: 50%; background: var(--ac);
  transform: translate(-50%, -50%) scale(.2); opacity: 0;
}
.is-drawn .tj-curve { animation: draw 1.6s var(--ez) .4s forwards; }
.is-drawn .tj-dot   { animation: pop .55s var(--ez) 1.9s forwards; }
@keyframes draw { to { stroke-dashoffset: 0; } }
@keyframes pop {
  0%   { opacity: 0; transform: translate(-50%, -50%) scale(.2); }
  60%  { opacity: 1; transform: translate(-50%, -50%) scale(1.7); }
  100% { opacity: 1; transform: translate(-50%, -50%) scale(1); }
}
.traj figcaption {
  margin-top: 12px; text-align: right;
  font: italic 400 clamp(16px, 1.6vw, 19px)/1.4 var(--ser); color: var(--fg3);
}

/* --- the intro, a band of its own across the whole page -------------------- */

/* Everything above is a stack on the indented axis. This is not one: a rule the
   width of the page, then the lede and the paragraphs that support it set
   side by side across the full measure. Different shape, different axis and a
   different measure, so it cannot read as a fourth line of the block above.
   Running it wide also stops the right half of the masthead falling empty,
   which is what made the earlier version read as a trailing afterthought. */
/* Both columns carry running prose, and they are equal. Two earlier versions
   split the band by function — lede and roster on one side, supporting paragraphs
   on the other — which meant the intro only ever had half the band to say
   anything in. It now runs across both: the lede opens the left column and the
   healthcare track continues under it, the security track and the record take the
   right, and the roster spans the pair underneath. Equal widths because both
   columns are now the same kind of thing. */
.bio {
  margin-top: clamp(24px, 2.8vw, 34px);
  padding-top: clamp(22px, 2.6vw, 32px);
  border-top: 1px solid var(--line);
  display: grid; align-items: start;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  column-gap: clamp(30px, 3.8vw, 58px);
  row-gap: 14px;
}
.bio-l    { grid-column: 1; grid-row: 1; }
.bio-more { grid-column: 2; grid-row: 1; }
.withs    { grid-column: 1 / -1; grid-row: 2; }

/* One body size for every supporting paragraph in the band, whichever column it
   is in, so the two columns read as one piece of writing rather than as a lede
   with a sidebar. Only the lede steps up. */
.bio-l p + p, .bio-more p { font-size: 16.5px; line-height: 1.6; color: var(--fg2); }
.bio-l p + p { margin-top: 14px; }
.bio-more p + p { margin-top: 13px; }
.bio em { font-style: italic; color: var(--fg); }
/* A step down from the first version. The band carries four paragraphs and a
   roster now rather than two paragraphs, and it still has to clear the fold. */
.lede {
  font-size: clamp(1.1rem, 1.68vw, 1.28rem); line-height: 1.44;
  letter-spacing: -.01em;
}
.lede::first-letter {
  float: left; font-size: 3.3em; line-height: .8;
  padding: .04em .085em 0 0; color: var(--ac); font-weight: 500;
}

/* --------------------------------------------------------------------------
   5. The ledger grid
   -------------------------------------------------------------------------- */

.sec { border-top: 1px solid var(--line); }
.sg {
  display: grid; grid-template-columns: var(--margin) minmax(0, 1fr);
  padding: clamp(44px, 5.2vw, 72px) 0;
}
.sec.last .sg { padding-bottom: clamp(38px, 4.6vw, 60px); }

.sl {
  position: sticky; top: calc(var(--bar) + 34px); align-self: start;
  text-align: right; padding-right: var(--split);
}
.sn {
  font: 500 11px/1 var(--ui); letter-spacing: .2em; color: var(--ac); margin-bottom: 13px;
}
.sl h2 {
  font-family: var(--ser); font-weight: 500;
  font-size: clamp(1.6rem, 2.5vw, 2.05rem); line-height: 1.1; letter-spacing: -.02em;
}
.sb { border-left: 1px solid var(--line); padding-left: var(--split); min-width: 0; }

/* --------------------------------------------------------------------------
   6. Research interests
   -------------------------------------------------------------------------- */

/* The two tracks run SIDE BY SIDE, not stacked. Stacked, the first read as the
   priority and the second as the afterthought, which is true of neither — the
   interest in both is equal and the layout has to say so before the prose gets
   a chance to. Two columns of exactly equal width, divided by a hairline, say it
   without a word.

   The "Track I / Track II" labels went with the stack. Roman numerals rank
   things, which was the same problem in miniature. Each column opens on an
   identical short accent rule instead, drawn by .track h3::before.

   The rows are a subgrid, so the two titles share a row and the two paragraphs
   start on the same line however many lines each title runs to. Without subgrid
   support the columns still render, just unaligned — worth having, not worth
   guarding. */
.tracks {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  grid-template-rows: repeat(4, auto);
}
.track {
  display: grid; grid-row: span 4; grid-template-rows: subgrid;
  padding-right: clamp(24px, 3vw, 40px);
}
/* --line2, not --line. The divider is doing real work — it is what says these
   are two equal columns and not one wrapped one — so it has to be visible at a
   glance. At --line it disappeared against --bg, which is right for a rule
   between stacked items and wrong for one holding two columns apart. */
.track + .track {
  padding-right: 0; padding-left: clamp(24px, 3vw, 40px);
  border-left: 1px solid var(--line2);
}
.track h3 {
  font-size: clamp(1.22rem, 1.9vw, 1.46rem); line-height: 1.24; letter-spacing: -.014em;
  font-weight: 500; margin-bottom: 11px;
}
.track h3::before {
  content: ""; display: block; width: 32px; height: 2px;
  background: var(--ac); margin-bottom: 15px;
}
/* One paragraph per track, never two. A two-block version — position, then the
   work that backs it — was tried and rejected: the join needed a connector
   ("I do this at…", "I came to this from…") and every one of those read as a
   seam. The single paragraph carries the same order without announcing it. */
.track > p { font-size: 17.6px; line-height: 1.66; color: var(--fg2); }

.kws { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 16px; }
.kws li {
  font: 500 11.5px/1 var(--ui); color: var(--fg2);
  border: 1px solid var(--line); border-radius: 999px; padding: 6px 11px; background: var(--bg2);
}
.tven {
  margin-top: 14px !important;
  font: 400 14px/1.6 var(--ui) !important; color: var(--fg3) !important;
}
.tven i { color: var(--line2); font-style: normal; padding: 0 3px; }

.aside {
  margin-top: 26px; padding-top: 20px; border-top: 1px solid var(--line);
  font: italic 300 17.5px/1.55 var(--ser); color: var(--fg2); max-width: 70ch;
}
.aside > span { display: block; }

/* --------------------------------------------------------------------------
   7. Publications
   -------------------------------------------------------------------------- */

/* The venue rail. This replaced a four-cell counter that opened with "7 papers
   total", and the reason is worth keeping: the list below is a SELECTION, so any
   figure counting it states a career total that is not true. Counts also spend
   the most valuable strip on the page on the least scarce fact. Where the work
   was accepted is the scarce one, so that is what the section now opens with.

   Venue names in the reading serif at heading size, tier beneath in the accent.
   No box and no hairline gaps — the rail is a row of type, not a dashboard, and
   the single rule under it closes the band the way the masthead link row closes.
   Only accepted venues appear; the two papers still out are on their entries,
   where a status chip can say so honestly. */
.vrail {
  display: grid; grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: 20px 16px; align-items: start;
  padding-bottom: 21px; border-bottom: 1px solid var(--line);
}
.v b {
  display: block; font: 500 clamp(1.02rem, 1.45vw, 1.3rem)/1.2 var(--ser);
  letter-spacing: -.015em; color: var(--fg);
}
.vm {
  display: block; margin-top: 8px;
  font: 500 10.5px/1.45 var(--ui); letter-spacing: .11em; text-transform: uppercase;
}
.yr { color: var(--fg3); }
.tier { color: var(--ac); }
.tier::before { content: "\00B7"; color: var(--line2); padding: 0 5px; }

/* One line under the rail, carrying only what the list cannot say for itself:
   where he stands on these papers, and where the ones not shown are. */
.psum {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 24px;
  margin-top: 16px; font: 400 15.5px/1.6 var(--ui); color: var(--fg2);
}
.psum b { color: var(--ac); font-weight: 600; }
.psum i { font-style: normal; color: var(--line2); padding: 0 2px; }

/* The chips need real air beneath them, and the rule below reads as a divider
   between the controls and the list rather than as a stray line. */
.filter { display: flex; flex-wrap: wrap; gap: 7px; margin: 28px 0 26px; }
.chip {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 8px 14px; font: 500 13px/1 var(--ui); color: var(--fg2);
  background: none; border: 1px solid var(--line); border-radius: 999px; cursor: pointer;
  transition: color .2s var(--ez), border-color .2s var(--ez), background-color .2s var(--ez);
}
.chip b { font: 500 11px/1 var(--ui); color: var(--fg3); }
.chip:hover { color: var(--fg); border-color: var(--line2); }
.chip.on { color: var(--ac2); border-color: var(--ac); background: var(--acs); }
.chip.on b { color: var(--ac); }
:root[data-theme="dark"] .chip.on { color: var(--ac); }

.pubs { display: grid; }
/* Uniform padding and rules on every entry, so filtering never leaves a stray
   first or last rule behind and no JS bookkeeping is needed. */
.pub { position: relative; padding: 26px 0 26px 20px; border-top: 1px solid var(--line); }
.pub[hidden] { display: none; }
.pub::before {
  content: ""; position: absolute; left: calc(-1 * var(--split) - 1px);
  top: 26px; bottom: 26px; width: 2px;
  background: var(--ac); transform: scaleY(0); transform-origin: top;
  transition: transform .42s var(--ez);
}
.pub:hover::before, .pub:focus-within::before { transform: scaleY(1); }

.pmeta { display: flex; flex-wrap: wrap; align-items: center; gap: 7px; margin-bottom: 12px; }
.pmeta > * { font: 500 10.5px/1 var(--ui); letter-spacing: .1em; text-transform: uppercase; }
.venue { color: var(--fg); border: 1px solid var(--line2); border-radius: 4px; padding: 5px 9px; }
.venue.on { color: var(--ac2); border-color: var(--ac); background: var(--acs); }
:root[data-theme="dark"] .venue.on { color: var(--ac); }
.core { color: var(--ac); border: 1px solid var(--ac); border-radius: 4px; padding: 5px 8px; }
.cite { color: var(--ac); background: var(--acs); border-radius: 4px; padding: 5px 8px; }
.award { color: var(--ac); }
.award::before { content: "\2726"; margin-right: 5px; opacity: .7; }
/* Where a paper stands is the first thing a committee looks for, so it cannot be
   a caption. Set in quiet serif italic it read as an aside and went unnoticed
   entirely; it now takes the same chip size and weight as everything else on the
   row. The border is dashed where .venue's is solid — provisional against
   settled — which carries the distinction without a second colour. */
.rev {
  color: var(--fg); border: 1px dashed var(--line2); border-radius: 4px; padding: 5px 9px;
}
.sub { color: var(--fg3); }

.pub h3 {
  font-size: clamp(1.26rem, 2.1vw, 1.54rem); line-height: 1.24; letter-spacing: -.012em;
  font-weight: 500; margin-bottom: 9px; max-width: 46ch;
}
.pub h3 a {
  text-decoration: none;
  background-image: linear-gradient(var(--ac), var(--ac));
  background-size: 0% 1px; background-position: 0 100%; background-repeat: no-repeat;
  transition: background-size .4s var(--ez), color .25s var(--ez);
}
.pub h3 a:hover { color: var(--ac); background-size: 100% 1px; }
/* Author lines carry the full list every time, so a reader can see where he sits
   in it rather than take a curated "with X and Y" on trust. Three signals and
   nothing more: his own name in body colour under the emphasis hairline, anyone
   holding a position carrying that position after the name, everyone else plain.
   The fourth signal would cost the other three their meaning. */
.pauth {
  font: 400 15.5px/1.8 var(--ui); color: var(--fg3);
  margin-bottom: 12px; max-width: 76ch;
}
.pauth .me {
  color: var(--fg); font-weight: 600; padding-bottom: 2px;
  background-image: linear-gradient(var(--ul), var(--ul));
  background-repeat: no-repeat; background-position: 0 100%; background-size: 100% 1px;
  -webkit-box-decoration-break: clone; box-decoration-break: clone;
}
/* Senior and named authors. The markup binds the name to its post with a hard
   space, so a line can break inside the parenthetical but never leave a lone "("
   hanging at the end of a line.

   Name and post are ONE object, in one colour. They were split for a while —
   accent name, --fg2 post — and inside a comma-separated list of eleven people
   that read as debris rather than as a credential: the eye had to assemble
   "Ehsan Hoque" and "(Professor, University of Rochester)" out of two differently
   coloured fragments. The accent now runs the whole way through, so a senior
   author lands as a single highlighted unit and the plain student names still
   have something to be plain against.

   The post keeps the serif italic, which is what still separates the two halves.
   A size step alone would read as an accident — the eye asks why that text
   shrank — where a change of register says the parenthetical is a different kind
   of thing, an annotation about a person rather than another name in the list.

   17px because Newsreader italic sets a good deal smaller than Inter at equal
   px; this lands it optically level with the names, not below them. */
.pauth .sr { color: var(--ac); font-weight: 500; }
.pauth .sr .at {
  font: italic 400 17px/1 var(--ser); color: var(--ac); letter-spacing: 0;
}
/* Some of these names carry a Scholar profile and the rest do not, so the accent
   colour cannot also mean "clickable" — the arrow does that, and it hangs off the
   NAME, not off the end of the anchor. Trailing the whole thing put it after the
   closing parenthesis, where "…Penn State)↗" reads as a mark on the affiliation
   and leaves the accent arrow stranded a phrase away from the accent name it
   belongs to. It is a reference mark on a person, so it sits on the person: the
   hard space moved inside .at and the glyph is that element's ::before. */
.pauth a.sr { text-decoration: none; }
.pauth a.sr:hover, .pauth a.sr:hover .at { color: var(--ac2); }
/* Joint-first stars, keyed to .pubkey below the filter row. */
.pauth .eq { font-style: normal; font-weight: 600; color: var(--ac); }

/* The author's own standing on the paper, at the head of the list where it is
   read rather than as an eighth chip in .pmeta, which is already full. */
.pos {
  display: inline-block; margin-right: 10px; vertical-align: 1px;
  font: 500 10.5px/1 var(--ui); letter-spacing: .1em; text-transform: uppercase;
  color: var(--ac); background: var(--acs); border-radius: 4px; padding: 5px 8px;
}
/* Co-authorship is a real credit but not the same credit, and an accent chip would
   claim otherwise at a glance. Padding drops by the border it gains. */
.pos.alt { color: var(--fg3); background: none; border: 1px solid var(--line); padding: 4px 7px; }
.pubkey { margin: -8px 0 22px; font: 400 13.5px/1.6 var(--ui); color: var(--fg3); }
.pubkey .eq { font-style: normal; font-weight: 600; color: var(--ac); }

.pblurb { font-size: 17.6px; line-height: 1.63; color: var(--fg2); max-width: 66ch; }
.plinks { margin-top: 15px; display: flex; flex-wrap: wrap; gap: 8px; }
.empty { padding: 34px 0 0; font: italic 300 18px var(--ser); color: var(--fg3); }

/* --------------------------------------------------------------------------
   8. Entries
   -------------------------------------------------------------------------- */

.entry {
  display: grid; grid-template-columns: 148px minmax(0, 1fr); gap: 26px;
  padding: 26px 0; border-top: 1px solid var(--line);
}
.entry:first-of-type { border-top: 0; padding-top: 0; }
/* The date column is the spine of these sections: on a CV it is read before the
   job title, and it was set at 11.5px in the lightest grey — the least legible
   type on the page carrying one of the most-wanted facts. Same correction as the
   author affiliations: up a size, and darker rather than lighter. */
.when {
  font: 500 13px/1.62 var(--ui); letter-spacing: .085em; text-transform: uppercase;
  color: var(--fg); padding-top: 4px;
}
/* "Present" is the word being looked for, so it is the one thing here in accent. */
.when b { font-weight: 500; color: var(--ac); }
.when span {
  display: block; margin-top: 9px; letter-spacing: .01em; text-transform: none;
  font: italic 400 16px/1.35 var(--ser); color: var(--fg2);
}
.entry h3 {
  font-size: clamp(1.24rem, 2vw, 1.48rem); line-height: 1.2; letter-spacing: -.014em;
  font-weight: 500;
}
/* The employer is the other half of the fact the title states — "Lecturer" means
   nothing without "BRAC University" — and it was set a size down in --fg2, as
   though it were a caption on the title. Same correction as .when above: up a
   size, and into the body colour. The linked part stays accent inside it. */
.org { margin: 6px 0 13px; font: 400 16.5px/1.5 var(--ui); color: var(--fg); }
.org a { color: var(--ac); text-decoration: none; border-bottom: 1px solid transparent;
  transition: border-color .2s var(--ez); }
.org a:hover { border-bottom-color: currentColor; }
.lead { font-size: 17.6px; line-height: 1.62; color: var(--fg2); max-width: 64ch; }
.lead + ul { margin-top: 13px; }
.entry ul { display: grid; gap: 11px; max-width: 68ch; }
.entry li {
  position: relative; padding-left: 21px;
  font-size: 17.4px; line-height: 1.6; color: var(--fg2);
}
.entry li::before {
  content: ""; position: absolute; left: 0; top: .74em;
  width: 10px; height: 1px; background: var(--ac); opacity: .55;
}
.courses { margin-top: 15px; font: 400 15px/1.75 var(--ui); color: var(--fg3); max-width: 62ch; }
.courses b {
  display: block; font: 500 10.5px/1 var(--ui); letter-spacing: .16em; text-transform: uppercase;
  color: var(--fg3); margin-bottom: 7px;
}
.courses i { color: var(--line2); font-style: normal; padding: 0 2px; }

.skills {
  margin-top: 32px; padding-top: 26px; border-top: 1px solid var(--line);
  display: grid; grid-template-columns: 148px minmax(0, 1fr); gap: 13px 26px;
}
/* Same left column, same fix, one step quieter — these are category labels, not
   the dates, so they stop at --fg2. */
.skills dt {
  font: 500 12.5px/1.7 var(--ui); letter-spacing: .09em; text-transform: uppercase; color: var(--fg2);
}
.skills dd { font: 400 16.5px/1.62 var(--ui); color: var(--fg2); }

/* --------------------------------------------------------------------------
   9. Recognition
   -------------------------------------------------------------------------- */

.two { display: grid; grid-template-columns: 1fr 1fr; gap: clamp(28px, 3.5vw, 46px); }
.colh {
  font: 500 10.5px/1 var(--ui); letter-spacing: .17em; text-transform: uppercase;
  color: var(--fg3); padding-bottom: 13px; border-bottom: 1px solid var(--line); margin-bottom: 19px;
}
/* A column titled "Leadership & service" that only ever lists leadership roles
   promises a category it doesn't show, and once a service line does land there it
   reads as one more item in the same list rather than a different kind of thing.
   .subh names the two groups inside the column so a reviewer scanning for service
   doesn't have to infer it from context. Quiet like .colh, since it's structure,
   not a fact being hunted for. */
.subh {
  font: 500 11px/1 var(--ui); letter-spacing: .1em; text-transform: uppercase;
  color: var(--fg3); margin: 0 0 14px;
}
.ticks + .subh { margin-top: 28px; }
.ticks { display: grid; gap: 18px; }
.ticks li {
  display: grid; grid-template-columns: 62px minmax(0, 1fr); gap: 13px;
  font-size: 17px; line-height: 1.56; color: var(--fg2);
}
/* Same correction as .when, .at, .org and .rev: the year is a scanned fact and was
   set at 11.5px in the lightest grey, quieter than the sentence next to it. */
.ticks .y { font: 500 13px/1.5 var(--ui); letter-spacing: .06em; color: var(--fg); }
/* An ongoing role, set the way .when sets it: the range breaks on purpose rather
   than wrapping mid-word in the 62px column, and "Present" takes the accent. */
.ticks .y b { font-weight: 500; color: var(--ac); }

/* --------------------------------------------------------------------------
   10. Projects, photographs in colour
   -------------------------------------------------------------------------- */

.grid {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(16px, 1.8vw, 22px);
}
.card {
  display: flex; flex-direction: column;
  background: var(--card); border: 1px solid var(--line); border-radius: 12px; overflow: hidden;
  transition: border-color .3s var(--ez), transform .3s var(--ez), box-shadow .3s var(--ez);
}
.card:hover { border-color: var(--line2); transform: translateY(-3px); box-shadow: var(--shadow); }
/* Same rule as the portrait: the ratio belongs to the container, and the image
   fills it. Shorter than before, since the cards were running tall. */
.shot {
  display: block; overflow: hidden;
  aspect-ratio: 16 / 9;
  background: var(--bg2); border-bottom: 1px solid var(--line);
}
.shot img {
  width: 100%; height: 100%; object-fit: cover; object-position: center;
  transition: transform .55s var(--ez);
}
.card:hover .shot img { transform: scale(1.035); }
.cbody { padding: 18px 20px; display: flex; flex-direction: column; flex: 1; }
.card h3 {
  font-size: 1.24rem; line-height: 1.22; letter-spacing: -.014em; font-weight: 500;
  margin-bottom: 7px;
}
.card h3 a { text-decoration: none; transition: color .22s var(--ez); }
.card h3 a:hover { color: var(--ac); }
.card p { font-size: 16.4px; line-height: 1.52; color: var(--fg2); }
.stack { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 13px !important; }
.stack span {
  font: 500 10.5px/1 var(--ui); letter-spacing: .05em; color: var(--fg3);
  border: 1px solid var(--line); border-radius: 4px; padding: 5px 7px;
}
.clinks { margin-top: auto; padding-top: 15px; display: flex; flex-wrap: wrap; gap: 7px; }
.more { margin-top: 24px; }

/* --------------------------------------------------------------------------
   11. Contact and footer
   -------------------------------------------------------------------------- */

.contact { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-top: 28px; }
.contact a, .contact .static {
  display: flex; align-items: center; gap: 14px;
  padding: 15px 17px; border: 1px solid var(--line); border-radius: 10px;
  text-decoration: none; background: var(--bg);
  transition: border-color .25s var(--ez), background-color .25s var(--ez), transform .25s var(--ez);
}
.contact a:hover { border-color: var(--ac); background: var(--acs); transform: translateY(-2px); }
.contact svg { width: 21px; height: 21px; color: var(--ac); }
.contact span {
  display: block; min-width: 0; font: 400 16px/1.35 var(--ui); color: var(--fg2);
  word-break: break-word;
}
.contact b {
  display: block; font: 500 10.5px/1 var(--ui); letter-spacing: .14em; text-transform: uppercase;
  color: var(--fg3); margin-bottom: 6px;
}
.contact a:hover span { color: var(--fg); }
.contact .static { background: var(--bg2); }

/* Two rows on the ledger's columns. The couplet stands on the content axis, the
   same left edge every section's content keeps, so it reads as the page's last
   line rather than as an item squeezed between the copyright and the back link.
   Those two sit on the row beneath, at either end. */
.foot {
  display: grid; grid-template-columns: var(--margin) minmax(0, 1fr) auto;
  align-items: baseline; row-gap: 26px;
  padding: 30px 0 34px; border-top: 1px solid var(--line);
  font: 400 15.5px/1.5 var(--ui); color: var(--fg3);
}
.foot > p:first-child { grid-column: 1 / 3; grid-row: 2; }
.foot > p:last-child  { grid-column: 3; grid-row: 2; }
/* The closing couplet. Quiet, attributed, and in the reading serif, so it reads
   as a line he keeps rather than a slogan: same italic register as the motto
   under the curve, one step darker because it is two lines, not one. */
.coda {
  grid-column: 2 / -1; grid-row: 1;
  margin: 0; padding-left: calc(var(--split) + 1px);
}
.coda blockquote { margin: 0; }
.coda p { font: italic 300 18px/1.5 var(--ser); color: var(--fg2); }
.coda figcaption {
  margin-top: 9px; font: 500 10.5px/1 var(--ui); letter-spacing: .14em;
  text-transform: uppercase; color: var(--fg3);
}
.coda figcaption::before { content: "\2014\00A0"; }
.foot a {
  color: var(--fg3); text-decoration: none; border-bottom: 1px solid var(--line2);
  padding-bottom: 1px; transition: color .2s var(--ez), border-color .2s var(--ez);
}
.foot a:hover { color: var(--ac); border-color: var(--ac); }

/* --------------------------------------------------------------------------
   12. Reveal
   -------------------------------------------------------------------------- */

.js [data-reveal] > * { opacity: 0; transform: translateY(13px); }
.js [data-reveal].is-in > * {
  opacity: 1; transform: none;
  transition: opacity .6s var(--ez) calc(var(--i, 0) * 65ms),
              transform .6s var(--ez) calc(var(--i, 0) * 65ms);
}
.js [data-reveal] .ticks > li { opacity: 0; transform: translateY(13px); }
.js [data-reveal].is-in .ticks > li {
  opacity: 1; transform: none;
  transition: opacity .6s var(--ez) calc(var(--i, 0) * 65ms),
              transform .6s var(--ez) calc(var(--i, 0) * 65ms);
}
.js .grid[data-reveal] > .card { opacity: 0; transform: translateY(15px); }
.js .grid[data-reveal].is-in > .card {
  opacity: 1; transform: none;
  transition: opacity .6s var(--ez), transform .6s var(--ez);
}
.js .grid[data-reveal].is-in > .card:nth-child(2) { transition-delay: .07s; }
.js .grid[data-reveal].is-in > .card:nth-child(3) { transition-delay: .14s; }
.js .grid[data-reveal].is-in > .card:nth-child(4) { transition-delay: .21s; }
.js .grid[data-reveal].is-in > .card:nth-child(5) { transition-delay: .28s; }
.js .grid[data-reveal].is-in > .card:nth-child(6) { transition-delay: .35s; }

/* --------------------------------------------------------------------------
   13. Responsive
   -------------------------------------------------------------------------- */

/* Laptops: the ledger margin narrows before anything else gives. */
@media (max-width: 1199px) {
  /* The bar needs the width for the nav; from here down the nav may shrink and
     scroll instead. */
  .running { display: none; }
  .nav { flex: 0 1 auto; }
}

@media (max-width: 1180px) {
  :root { --margin: 172px; --split: 30px; }
}

@media (max-width: 1040px) {
  :root { --margin: 142px; --split: 26px; }
  .two { grid-template-columns: 1fr; gap: 34px; }
  .contact { grid-template-columns: 1fr; }

  /* The tracks stack here, not at 860. The ledger margin is still taking 168px
     out of the row at this width, so two columns would be about 29 characters
     each — narrower than the single column that replaces them, and the reason
     the side-by-side version was asked for in the first place was that 38 felt
     cramped. Stacked, the divider rotates rather than disappearing: the two
     still read as a pair, and neither is numbered. */
  .tracks { grid-template-columns: minmax(0, 1fr); grid-template-rows: none; }
  .track { display: block; grid-row: auto; padding-right: 0; }
  .track + .track {
    padding-left: 0; border-left: 0;
    border-top: 1px solid var(--line2); margin-top: 30px; padding-top: 30px;
  }
}

/* Tablets: the ledger unfolds, the portrait shrinks but keeps the top left. */
@media (max-width: 860px) {
  body { font-size: 17.5px; }

  .mast { padding-top: clamp(30px, 5vw, 48px); }
  /* The ledger unfolds here, so there is no longer a margin column to line the
     portrait up with; it sizes to the viewport instead. */
  .mast-grid { grid-template-columns: clamp(140px, 22vw, 190px) minmax(0, 1fr); column-gap: 24px; }
  .name { max-width: none; }
  /* Two text columns stop paying for themselves here; the band and its rule
     stay, the spread does not. Source order is already lede, support,
     collaborators, so dropping the placement is enough to get the right stack. */
  .bio { grid-template-columns: minmax(0, 1fr); row-gap: 16px; }
  .bio-l, .withs, .bio-more { grid-column: 1; grid-row: auto; }

  .sg { grid-template-columns: minmax(0, 1fr); padding-top: clamp(34px, 6vw, 48px); }
  .sl {
    position: static; text-align: left; padding: 0 0 20px;
    display: grid; grid-template-columns: auto minmax(0, 1fr);
    align-items: baseline; gap: 0 12px;
  }
  .sn { margin-bottom: 0; }
  /* The label runs on one line here. The markup carries a real space in front
     of the break, which line layout drops while the break is shown and which
     keeps the two words apart once it is not. */
  .sl h2 br { display: none; }
  .sb { border-left: 0; padding-left: 0; }
  .pub { padding-left: 14px; }
  .pub::before { left: 0; }

  .entry, .skills { grid-template-columns: minmax(0, 1fr); gap: 9px; }
  .when { padding-top: 0; }
  /* One column here, so the date runs on one line and the place sits beside it.
     The <br> is a two-line layout for the 148px column and nothing else. */
  .when br { display: none; }
  .when span { display: inline; margin: 0 0 0 10px; font-size: 15px; }
  .skills { gap: 3px; }
  .skills dt { margin-top: 13px; }
  .skills dt:first-of-type { margin-top: 0; }
  /* Five across stops fitting the venue names well before the ledger unfolds;
     three keeps the columns aligned on both rows. */
  .vrail { grid-template-columns: repeat(3, minmax(0, 1fr)); }

  /* No margin column to align to, so the couplet starts at the page edge. */
  .foot { grid-template-columns: minmax(0, 1fr) auto; column-gap: 20px; row-gap: 22px; }
  .coda { grid-column: 1 / -1; padding-left: 0; }
  .foot > p:first-child { grid-column: 1; }
  .foot > p:last-child  { grid-column: 2; }
}

/* Phones: single column everywhere, and the nav strip shows it can scroll. */
@media (max-width: 620px) {
  .nav a { padding: 7px 9px; font-size: 13px; }
  .cv-btn { padding: 7px 11px; font-size: 13px; }

  .toplinks { gap: 9px 18px; padding: 15px 0; }
  .toplinks a { font-size: 14.5px; }
  .grid { grid-template-columns: minmax(0, 1fr); }
  .ticks li { grid-template-columns: minmax(0, 1fr); gap: 3px; }
  .plinks, .clinks { gap: 6px; }
  /* The year sits above the entry here, so the range fits on one line. */
  .ticks .y br { display: none; }
  .coda p { font-size: 17px; }
}

/* Narrow phones: beside a portrait this small the name would break to three or
   four lines, so the masthead unstacks into one column instead. */
@media (max-width: 560px) {
  .mast-grid { grid-template-columns: minmax(0, 1fr); }
  .portrait, .name, .role, .toplinks, .bio { grid-column: 1; grid-row: auto; }
  .lede::first-letter { font-size: 3em; }
  .portrait { width: 150px; justify-self: start; margin-bottom: 26px; }
}

/* Small phones: give the type and the chrome a little more room to breathe. */
@media (max-width: 430px) {
  :root { --pad: 18px; }
  .portrait { width: 130px; margin-bottom: 22px; }
  .name { font-size: clamp(2.05rem, 10vw, 2.6rem); }
  .role { font-size: 14px; }
  /* The Resume button drops to its icon; the label stays for screen readers. */
  .cv-btn { padding: 8px 10px; gap: 0; font-size: 0; }
  .cv-btn svg { width: 16px; height: 16px; }
  .vrail { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .withs { font-size: 14.5px; }
  .withs .sr .at { font-size: 15px; }
  .contact a, .contact .static { padding: 13px 14px; gap: 12px; }
  .foot { font-size: 14px; }
}

/* Short laptops. The masthead is the only block on the page with a fold to clear
   — portrait, name, designations, links and the whole intro are meant to be taken
   in without scrolling — so on a 768px screen it gives up size and spacing rather
   than pushing the intro under. Width-gated to the desktop layout: below 860px the
   band is a single column and a short viewport means a phone in landscape, where
   shrinking the type buys nothing.
   The portrait's ratio tightens with them. At 3/4 it stands 275px on a 206px
   ledger column, which is taller than the name, designations and link row beside
   it, so the photograph alone sets the height of the band; a squarer crop hands
   that back to the type without the picture reading as a thumbnail, and object-fit
   absorbs it. */
@media (min-width: 861px) and (max-height: 820px) {
  .mast { padding-top: clamp(22px, 3vw, 34px); }
  .name { font-size: clamp(2.3rem, 5vw, 3.5rem); }
  .portrait { aspect-ratio: 1 / 1.15; }
  .bio { margin-top: 22px; padding-top: 20px; }
  .lede { font-size: clamp(1.06rem, 1.5vw, 1.18rem); }
  .bio-l p + p, .bio-more p { font-size: 16px; line-height: 1.55; }
  .traj { margin-top: 20px; }
  .tj-wrap { height: clamp(36px, 4vw, 50px); }
}

/* --------------------------------------------------------------------------
   14. Reduced motion and print
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
  .js [data-reveal] > *,
  .js [data-reveal] .ticks > li,
  .js .grid[data-reveal] > .card { opacity: 1 !important; transform: none !important; }
  .js [data-reveal] .hl { background-size: 100% 1px; }
  .running { opacity: 1; }
  .tj-curve { stroke-dashoffset: 0; }
  .tj-dot { opacity: 1; transform: translate(-50%, -50%) scale(1); }
}

@media print {
  .bar, .theme, .filter, .traj, .foot, .shot { display: none; }
  body { font-size: 10.5pt; background: #fff; color: #000; }
  .page { max-width: none; padding: 0; }
  .mast-grid { grid-template-columns: 130px 1fr; column-gap: 24px; }
  .sg { grid-template-columns: 130px 1fr; padding: 14pt 0; }
  .sl { position: static; }
  .hl, .pauth .me { background: none !important; font-weight: 600; }
  .filter + .pubkey { margin-top: 0; }
  /* Nothing on paper goes anywhere, so the "this is a link" arrow is just noise. */
  .pub h3 a::after, .card h3 a::after, .pauth a.sr .at::before,
  .withs a.sr .at::before, .org a::after, a.key::after { content: none; }
  .js [data-reveal] > *,
  .js [data-reveal] .ticks > li,
  .js .grid[data-reveal] > .card { opacity: 1 !important; transform: none !important; }
  .pub, .entry, .card, .track { break-inside: avoid; }
  a { color: #000; }
}
