/* Alessio Giorgetta · personal site
   Plain CSS, no frameworks, no build step. Every rule is editable by hand.

   Color and type were calibrated in August 2026 against the WCAG 2.2 contrast
   formula and the APCA lightness-contrast levels published at
   git.apcacontrast.com. The reasoning behind each value is annotated inline. */

/* ---------- self-hosted type ----------
   Source Serif 4 and Source Sans 3: two faces from the same Adobe superfamily,
   drawn to pair, both SIL Open Font License 1.1. Subset and served from
   assets/fonts, so the site still makes zero external requests. Licenses travel
   with the files, as the OFL requires.

   These are variable fonts with the weight axis narrowed to what the site uses,
   so 400, 500 and 600 are all real drawn weights rather than the browser
   smearing one outline. The serif also carries an optical-size axis, which
   browsers apply automatically: the name in the masthead gets slightly different
   letterforms from the year numerals beside a paper. */

@font-face {
  font-family: "Source Serif 4";
  src: url("assets/fonts/serif-roman.woff2") format("woff2-variations"),
       url("assets/fonts/serif-roman.woff2") format("woff2");
  font-weight: 400 600;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Source Sans 3";
  src: url("assets/fonts/sans-roman.woff2") format("woff2-variations"),
       url("assets/fonts/sans-roman.woff2") format("woff2");
  font-weight: 400 600;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Source Sans 3";
  src: url("assets/fonts/sans-italic.woff2") format("woff2");
  font-weight: 400;
  font-style: italic;
  font-display: swap;
}

:root {
  color-scheme: light;

  /* ---- palette ----------------------------------------------------------
     Every value below is placed against the APCA lightness-contrast levels
     published at git.apcacontrast.com:
       Lc 90  preferred for body text        Lc 75  minimum for body text
       Lc 60  minimum for content text       Lc 45  headlines
       Lc 30  placeholder / disabled         Lc 15  floor for non-text
     The old --ink-faint was Lc 60.8 and 3.39:1, below WCAG AA, while being
     used for section headings, dates, venues and the footer. That is the one
     change here that is a bug fix rather than a preference. */

  --ink:        #1a1a18;   /* Lc 103 · body text            */
  --ink-soft:   #55534d;   /* Lc  84 · secondary prose      */
  --ink-faint:  #6e6a63;   /* Lc  75 · metadata (was 60.8, and failed AA) */
  --rule:       #d3d2cf;   /* Lc  22 · hairlines (was 13.5: below the visibility floor) */
  --rule-soft:  #e6e5e1;   /*          the quietest divider */
  --bg:         #fdfcf8;   /* warm paper, never pure white  */
  --bg-inset:   #f6f4ee;   /* recessed panels               */
  --accent:     #8c1515;   /* Lc 88 · Stanford Cardinal     */
  --accent-ink: #fdfcf8;   /* text on an accent fill        */

  --todo-bg:    #fff8e1;
  --todo-edge:  #e0b83a;

  --icon-moon:  block;
  --icon-sun:   none;

  /* ---- type -------------------------------------------------------------
     The self-hosted faces lead, so every visitor sees the same site. The tail
     of each stack is the old system chain, which is what shows for the moment
     before the font arrives and if it ever fails to load. "Iowan Old Style"
     ships only on macOS/iOS, which is why the site used to look meaningfully
     different on Windows and Android. */

  --serif: "Source Serif 4", "Iowan Old Style", "Palatino Linotype",
           "Book Antiqua", Palatino, Charter, Georgia, "Times New Roman", serif;
  --sans:  "Source Sans 3", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
           "Helvetica Neue", Helvetica, Arial, sans-serif;

  /* A real scale rather than sixteen ad-hoc decimals. Perfect fourth (1.333)
     at the display end, tightened to a major third (1.25) near body size so
     the small steps stay distinguishable. */
  --step--2: 0.8125rem;  /* 13px   · chips, eyebrow labels: the floor, nothing goes below */
  --step--1: 0.9rem;     /* 14.4px · dates, footer, captions */
  --step-0:  1.1rem;     /* 17.6px · body */
  --step-1:  1.22rem;    /* 19.5px · entry and project titles */
  --step-2:  1.8rem;     /* 28.8px · page titles */
  --step-3:  2.2rem;     /* 35.2px · the name */

  /* The old 40rem measured 75 characters per line at the old font: the very top
     of Bringhurst's 45–75 range. Source Sans 3 is narrower per character than
     the system faces it replaces, and has a slightly smaller x-height, so the
     body size goes up a little and the column comes in. Together they land at
     ~66 characters, the figure the reading literature converges on. */
  --measure: 34rem;

  --radius:    3px;
  --radius-sm: 2px;
}

/* The dark palette's values live in exactly one place, the --dk-* block below.
   Two selectors map them onto the real tokens: data-theme="dark" for the
   normal path, and a prefers-color-scheme query for the reader whose JS is
   off. Both read the same variables, so a value is still changed once.

   The small script in each page's <head> resolves the saved choice, or the
   system preference, into data-theme on <html> before the first paint, so
   there is no flash.

   Dark mode is where the old palette was weakest: --ink-soft sat at Lc 52.8
   (APCA "headline only") while carrying abstracts and CV bullets, and
   --ink-faint at Lc 31.2, the level APCA reserves for *disabled* text, while
   carrying section headings and dates. Both are raised substantially here.
   The background moves off #161614 to #201f1c: the halation literature puts
   the comfortable dark-surface range around RGB 30–40, and lifting it also
   reduces the glow that pure-dark backgrounds produce for readers with
   astigmatism. */
:root {
  --dk-ink:        #e9e7e3;   /* Lc 90 */
  --dk-ink-soft:   #d0cecb;   /* Lc 75 · was Lc 52.8 */
  --dk-ink-faint:  #b6b4b0;   /* Lc 60 · was Lc 31.2 */
  --dk-rule:       #625e58;   /* Lc 18 · was Lc 0.0, literally below the visibility floor */
  --dk-rule-soft:  #403d39;
  --dk-bg:         #201f1c;
  --dk-bg-inset:   #282621;
  --dk-accent:     #f0a48d;   /* Lc 61 · see note below */
  --dk-accent-ink: #201f1c;
  --dk-todo-bg:    #2b2410;
  --dk-todo-edge:  #7a6420;
}

:root[data-theme="dark"] {
  color-scheme: dark;

  --ink:        var(--dk-ink);
  --ink-soft:   var(--dk-ink-soft);
  --ink-faint:  var(--dk-ink-faint);
  --rule:       var(--dk-rule);
  --rule-soft:  var(--dk-rule-soft);
  --bg:         var(--dk-bg);
  --bg-inset:   var(--dk-bg-inset);
  --accent:     var(--dk-accent);
  --accent-ink: var(--dk-accent-ink);

  --todo-bg:   var(--dk-todo-bg);
  --todo-edge: var(--dk-todo-edge);

  --icon-moon: none;
  --icon-sun:  block;
}

/* No JS means the head script never ran and no data-theme was set, so the
   system preference is all there is to go on. The :not() guard keeps an
   explicit choice of light winning when the script did run. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;

    --ink:        var(--dk-ink);
    --ink-soft:   var(--dk-ink-soft);
    --ink-faint:  var(--dk-ink-faint);
    --rule:       var(--dk-rule);
    --rule-soft:  var(--dk-rule-soft);
    --bg:         var(--dk-bg);
    --bg-inset:   var(--dk-bg-inset);
    --accent:     var(--dk-accent);
    --accent-ink: var(--dk-accent-ink);

    --todo-bg:   var(--dk-todo-bg);
    --todo-edge: var(--dk-todo-edge);

    --icon-moon: none;
    --icon-sun:  block;
  }
}

/* Note on the dark accent: red is an intrinsically dark hue, so pushing it to
   APCA's Lc 75 body-text level forces it to #f3c5b6, a pale pink that has
   lost 60% of its saturation and no longer reads as the same brand color.
   #f0a48d clears Lc 60 ("content text"), keeps 41% saturation, and every link
   that uses it is also underlined, so color is never the only signal. */

* { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0 auto;
  padding: 4rem 1.5rem 3rem;
  max-width: calc(var(--measure) + 9rem);
  background: var(--bg);
  color: var(--ink);
  font-family: var(--sans);
  font-size: var(--step-0);
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

::selection { background: var(--accent); color: var(--accent-ink); }

/* ---------- masthead ---------- */

.masthead {
  display: flex;
  align-items: center;
  gap: 1.75rem;
  margin-bottom: 2.25rem;
}

.portrait {
  flex: 0 0 auto;
  width: 116px;
  height: 116px;
  border-radius: 50%;
  object-fit: cover;
  object-position: 46% 42%;
  filter: saturate(0.92);
  /* a hairline stops the circle dissolving into the page on both themes */
  box-shadow: 0 0 0 1px var(--rule);
}

.identity h1 {
  font-family: var(--serif);
  font-size: var(--step-3);
  font-weight: 500;
  line-height: 1.12;
  letter-spacing: -0.012em;
  margin: 0 0 0.4rem;
}

.identity p { margin: 0; }

.role  { color: var(--ink); }
.affil { color: var(--ink-soft); }

/* ---------- nav ---------- */

nav {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1.5rem;
  padding: 0.85rem 0;
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
  margin-bottom: 3rem;
  /* was 0.75rem/12px with 0.11em tracking. 12px uppercase is below the size
     APCA will certify at any color short of full-strength ink; 13.5px at
     weight 500 clears it and reads less like fine print. */
  font-size: 0.845rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.1em;
}

nav a {
  color: var(--ink-soft);
  text-decoration: none;
  border-bottom: 1px solid transparent;
  padding-bottom: 3px;
  transition: color 0.15s, border-color 0.15s;
}

nav a:hover,
nav a:focus-visible {
  color: var(--accent);
  border-bottom-color: var(--accent);
}

nav a[aria-current="page"] {
  color: var(--ink);
  border-bottom-color: var(--accent);
  border-bottom-width: 2px;
}

.theme-toggle {
  margin-left: auto;
  display: flex;
  align-items: center;
  padding: 0.25rem;
  margin-right: -0.25rem;
  background: none;
  border: 0;
  border-radius: var(--radius);
  color: var(--ink-soft);
  cursor: pointer;
  line-height: 0;
  transition: color 0.15s;
}

.theme-toggle:hover { color: var(--accent); }

.theme-toggle svg {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.icon-moon { display: var(--icon-moon); }
.icon-sun  { display: var(--icon-sun); }

/* ---------- sections ---------- */

section { margin-bottom: 3.5rem; scroll-margin-top: 2rem; }

/* Section headings were --ink-faint: the faintest token on the page carrying
   the page's structural landmarks. Promoted to --ink-soft and up one size. */
h2 {
  font-family: var(--sans);
  font-size: var(--step--2);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.13em;
  color: var(--ink-soft);
  margin: 0 0 1.25rem;
  padding-bottom: 0.5rem;
  border-bottom: 1px solid var(--rule);
}

p { margin: 0 0 1rem; max-width: var(--measure); }

a {
  color: var(--accent);
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
  text-decoration-color: color-mix(in srgb, var(--accent) 45%, transparent);
  transition: text-decoration-color 0.15s;
}
a:hover { text-decoration-color: currentColor; }

/* A recessed panel of prose, same construction as .abstract so the two read as
   one family: inset ground, accent rule on the leading edge, nothing new to
   re-measure. Sits at body size because it is meant to be read, not skimmed. */
.callout {
  max-width: var(--measure);
  margin: 2rem 0;
  padding: 1rem 1.2rem;
  background: var(--bg-inset);
  border-left: 2px solid var(--accent);
  border-radius: 0 var(--radius) var(--radius) 0;
  color: var(--ink-soft);
  line-height: 1.68;
}
.callout p { margin: 0; max-width: none; }
.callout p + p { margin-top: 0.7rem; }
.callout strong { color: var(--ink); font-weight: 600; }

/* ---------- about page ----------

   About is the one page with no figures on it, so once the rail went in it was
   the only page still showing an empty strip on the right. The strip becomes a
   column: a larger portrait, the fields, and the links that used to trail off
   the bottom of the page.

   Everything here is what a phone sees, where the aside simply follows the
   prose in source order. The two-column part is in the wide-screen block. */

.side-list {
  list-style: none;
  margin: 0 0 2rem;
  padding: 0;
  color: var(--ink-soft);
  font-size: 0.97rem;
}

.side-list li { margin-bottom: 0.1rem; }

.about-side h2 { margin-bottom: 0.75rem; }
.about-side .contact-list { margin-bottom: 0; }

/* Stacked under the prose, the aside needs the gap the old "Social media"
   heading carried in .subhead--spaced. Reset once it has its own column. */
.about-side { margin-top: 2.75rem; }

/* Hidden until the aside has a column of its own. Below that width the
   masthead is already carrying the same face, and the same face twice on one
   screen reads as a mistake rather than as a decision. */
.portrait-frame { display: none; }

/* ---------- research projects ---------- */

.subhead {
  font-family: var(--sans);
  font-size: var(--step-0);
  font-weight: 600;
  color: var(--ink);
  margin: 0 0 1rem;
}

.subhead--spaced { margin-top: 2.75rem; }

.project {
  display: grid;
  grid-template-columns: 148px minmax(0, 1fr) auto;
  grid-template-areas:
    "fig body year"
    "abs abs abs";
  gap: 0 1.5rem;
  padding: 1.9rem 0;
  border-top: 1px solid var(--rule);
  align-items: start;
}

.project:last-of-type { border-bottom: 1px solid var(--rule); }

.proj-figure { grid-area: fig; }

.proj-figure img,
.figure-placeholder {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 3;
  border-radius: var(--radius);
}

.proj-figure img {
  object-fit: contain;
  border: 1px solid var(--rule);
  background: #fff;
  padding: 0.3rem;
}

.figure-placeholder {
  border: 1px dashed var(--todo-edge);
  background: var(--todo-bg);
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 0.6rem;
  font-size: 0.72rem;
  line-height: 1.4;
  color: var(--ink-soft);
}

.proj-body { grid-area: body; min-width: 0; }

.proj-body h4 {
  font-family: var(--serif);
  font-size: var(--step-1);
  font-weight: 600;
  line-height: 1.28;
  letter-spacing: -0.005em;
  margin: 0 0 0.3rem;
}

.proj-authors {
  font-size: 0.95rem;
  color: var(--ink-soft);
  margin: 0 0 0.1rem;
}

.proj-venue {
  font-size: 0.92rem;
  color: var(--ink-faint);
  margin: 0 0 0.7rem;
}

.proj-note {
  font-size: 0.9rem;
  font-style: italic;
  color: var(--ink-soft);
  border-left: 2px solid var(--accent);
  padding-left: 0.7rem;
  margin: 0 0 0.75rem;
}

/* The year was set in --rule, which measured Lc 13.5 in light and Lc 0.0 in
   dark: below the level APCA calls the floor for anything being visible at
   all. Kept as a quiet marginal number, but an actually legible one. */
.proj-year {
  grid-area: year;
  font-family: var(--serif);
  font-size: 1.9rem;
  line-height: 1;
  color: var(--ink-faint);
  opacity: 0.55;
  font-variant-numeric: tabular-nums;
  -webkit-user-select: none;
  user-select: none;
}

.chips { display: flex; flex-wrap: wrap; gap: 0.45rem; margin: 0; }

.chip {
  font-family: inherit;
  font-size: var(--step--2);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--ink-soft);
  background: none;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: 0.36rem 0.66rem;
  text-decoration: none;
  cursor: pointer;
  transition: border-color 0.15s, color 0.15s, background 0.15s;
}

.chip:hover { color: var(--accent); border-color: var(--accent); }

/* The open state used to be signaled by color alone. Under simulated
   protanopia the accent red collapses to within 1.4:1 of the body ink, so the
   distinction all but vanished. It now also fills and carries a caret. */
.chip[aria-expanded]::after {
  content: "";
  display: inline-block;
  width: 0.42em;
  height: 0.42em;
  margin-left: 0.5em;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: translateY(-0.15em) rotate(45deg);
  transition: transform 0.18s;
}

.chip[aria-expanded="true"] {
  color: var(--accent-ink);
  background: var(--accent);
  border-color: var(--accent);
}

.chip[aria-expanded="true"]::after {
  transform: translateY(0.05em) rotate(225deg);
}

.chip--pending {
  color: var(--ink-faint);
  border-style: dashed;
  border-color: var(--todo-edge);
  cursor: default;
}
.chip--pending:hover { color: var(--ink-faint); border-color: var(--todo-edge); }

.abstract {
  grid-area: abs;
  margin-top: 1.15rem;
  font-size: 1rem;
  line-height: 1.68;
  color: var(--ink-soft);
  background: var(--bg-inset);
  border-left: 2px solid var(--accent);
  border-radius: 0 var(--radius) var(--radius) 0;
  padding: 1rem 1.2rem;
}

.abstract p { margin: 0; max-width: none; }

/* ---------- cv page ---------- */

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

a.quiet { color: inherit; text-decoration-color: var(--rule); }
a.quiet:hover { color: var(--accent); text-decoration-color: currentColor; }

.contact-line {
  margin-top: 0.5rem;
  font-size: var(--step--1);
  color: var(--ink-faint);
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.page-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
  margin-bottom: 2.5rem;
  padding-bottom: 1.25rem;
  border-bottom: 1px solid var(--rule);
}

/* This was inheriting the body's 1.65 line-height at 28px, so every page title
   sat in a 46px box. Display type wants roughly 1.1–1.25. */
.page-title {
  font-family: var(--serif);
  font-size: var(--step-2);
  font-weight: 500;
  line-height: 1.15;
  letter-spacing: -0.012em;
  text-transform: none;
  color: var(--ink);
  border: 0;
  margin: 0;
  padding: 0;
}

.updated { color: var(--ink-faint); font-size: var(--step--1); margin: 0; }

.button {
  display: inline-block;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: 0.5rem 1rem;
  font-size: var(--step--2);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--ink-soft);
  text-decoration: none;
  transition: border-color 0.15s, color 0.15s, background 0.15s;
}

.button:hover {
  color: var(--accent);
  border-color: var(--accent);
}

/* The PDF is the artifact committees actually download. It was outlined in the
   accent; filling it makes it the one unambiguous call to action on the page,
   and the fill survives every color-vision simulation because it is carrying
   a shape and a weight difference, not only a hue. */
.button--primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
  flex: 0 0 auto;
}

.button--primary:hover {
  background: color-mix(in srgb, var(--accent) 84%, var(--ink));
  border-color: color-mix(in srgb, var(--accent) 84%, var(--ink));
  color: var(--accent-ink);
}

.entry { margin-bottom: 1.85rem; }

.entry-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1.5rem;
}

.entry h3 {
  font-family: var(--serif);
  font-size: var(--step-1);
  font-weight: 600;
  line-height: 1.3;
  margin: 0 0 0.15rem;
}

.entry .date {
  flex: 0 0 auto;
  font-size: var(--step--1);
  color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.entry .place {
  color: var(--ink-soft);
  font-size: 0.97rem;
  margin: 0 0 0.4rem;
}

.entry .place .with { display: inline-block; }

.skills { margin: 0; }
.skills dt {
  font-size: var(--step--2);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--ink-faint);
  margin-bottom: 0.25rem;
}
.skills dd {
  margin: 0 0 1rem;
  color: var(--ink-soft);
  font-size: 0.97rem;
}

.inline-list {
  display: flex;
  flex-wrap: wrap;
  column-gap: 0.55rem;
  row-gap: 0.15rem;
  padding: 0;
  margin: 0;
  list-style: none;
}

.inline-list > li:not(:last-child)::after {
  content: "·";
  color: var(--ink-faint);
  margin-left: 0.55rem;
}

/* ---------- personal gallery ---------- */

.gallery {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  column-gap: 1.75rem;
  row-gap: 3.5rem;
  align-items: start;
}

.shot { margin: 0; }

.shot img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--radius-sm);
}

.shot figcaption {
  margin-top: 0.75rem;
  /* was 0.88rem/14.1px; raised to the --step--1 step so captions sit at a size
     APCA will certify against --ink-soft */
  font-size: var(--step--1);
  line-height: 1.5;
  color: var(--ink-soft);
  /* Was 32ch = 229px. The hiking caption measures 213.3px and its marker 15.1px,
     so it landed inside that box by 0.7px and the browser broke the marker onto
     its own line anyway. 34ch = 243px gives it 15px of slack while staying
     narrower than the 250px column, so the caption still reads as tied to the
     photo rather than running its full width. */
  max-width: 34ch;
}

/* A footnote marker must never be left alone on a line. "[" is an opening
   bracket, and UAX #14 allows a line break in front of one even when there is
   no space there, so the marker detaches from its sentence the moment the line
   is tight. Holding the last word and the marker together moves them as a pair. */
.fn-hold { white-space: nowrap; }

.shot--1 { grid-column: 1 / 6; }
.shot--2 { grid-column: 8 / 13; margin-top: 5rem; }
/* Was 1 / 12, which left column 12 empty and hung the whole photo to the left. */
.shot--3 { grid-column: 1 / -1; margin-top: 1rem; }
.shot--4 { grid-column: 1 / 6; margin-top: 4rem; }
.shot--5 { grid-column: 8 / 13; }

/* The 32ch measure above is sized for the narrow five-column shots. The wide
   one runs the full width, and 32ch is 229px there, which broke the bar joke
   across two lines and stranded the punchline on its own. Measured in the
   shipped subset: the caption is 430px at 14.4px against a 640px column, so it
   sits on one line with room to spare, and still wraps normally on a phone. */
.shot--3 figcaption { max-width: none; }

/* ---------- caption footnotes ----------

   The marker cannot be a <sup>. Captions run at --step--1 (14.4px) and the
   browser shrinks a superscript to roughly 11px, under the 13px floor the rest
   of the site holds. So it is set at --step--2 and lifted by hand, with
   line-height: 0 so the lift contributes no height and the caption's leading
   stays even.

   The brackets are the non-color signal. Underlining a lone digit reads as
   damage rather than as a link, and the accent on its own collapses to within
   1.4:1 of the body ink under simulated protanopia, which is the same reason
   the nav carries a rule under the current item. */

.fn-ref {
  font-family: var(--sans);
  font-size: var(--step--2);
  font-weight: 600;
  line-height: 0;
  position: relative;
  top: -0.42em;
  margin-left: 0.12em;
  white-space: nowrap;
  text-decoration: none;
  scroll-margin-top: 5rem;
}

.fn-ref:hover { text-decoration: underline; text-underline-offset: 2px; }

.notes-section { margin-top: 4rem; }

.notes {
  list-style: none;
  counter-reset: note;
  margin: 0;
  padding: 0;
  max-width: var(--measure);
}

.notes li {
  counter-increment: note;
  position: relative;
  padding: 0.6rem 0.6rem 0.6rem 2.3rem;
  border-top: 1px solid var(--rule-soft);
  font-size: var(--step--1);
  line-height: 1.55;
  color: var(--ink-soft);
  scroll-margin-top: 5rem;
}

.notes li:last-child { border-bottom: 1px solid var(--rule-soft); }

/* The counter sits one step below the note text, so its line-height is
   computed rather than guessed: the note's line box is 0.9rem x 1.55 =
   22.32px, which is 1.717 of 13px. Equal line boxes put the two on a shared
   baseline. */
.notes li::before {
  content: "[" counter(note) "]";
  position: absolute;
  left: 0;
  top: 0.6rem;
  font-family: var(--sans);
  font-size: var(--step--2);
  font-weight: 600;
  line-height: 1.717;
  color: var(--ink-faint);
}

/* Landing on a note has to be visible without leaning on hue: --bg to
   --bg-inset is a lightness change, which survives every color-vision
   simulation the palette was checked against. */
.notes li:target {
  background: var(--bg-inset);
  border-radius: var(--radius);
}
.notes li:target::before { color: var(--accent); }

/* U+2191 rather than the return arrow at U+21A9: the subset fonts carry
   U+2190-2193 and nothing else in that block, so U+21A9 would drop to a
   system face. See assets/fonts/README.md before changing this glyph. */
.fn-back {
  margin-left: 0.4rem;
  text-decoration: none;
}
.fn-back:hover { text-decoration: underline; text-underline-offset: 2px; }

/* ---------- contact ---------- */

.contact-list { list-style: none; margin: 0; padding: 0; }

.contact-list li {
  display: flex;
  gap: 1rem;
  padding: 0.5rem 0;
  border-bottom: 1px solid var(--rule-soft);
}

.contact-list .label {
  flex: 0 0 5.5rem;
  font-size: var(--step--2);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  color: var(--ink-faint);
  padding-top: 0.15rem;
}

/* ---------- placeholders that must never ship ---------- */

.todo {
  background: var(--todo-bg);
  border-left: 3px solid var(--todo-edge);
  padding: 0.85rem 1rem;
  font-size: 0.92rem;
  color: var(--ink-soft);
  border-radius: 0 var(--radius) var(--radius) 0;
}

.todo-inline {
  background: var(--todo-bg);
  border-bottom: 1px dashed var(--todo-edge);
  color: var(--ink-soft);
  font-size: 0.92rem;
  padding: 0 0.25rem;
}

/* ---------- footer ---------- */

footer {
  border-top: 1px solid var(--rule);
  margin-top: 4rem;
  padding-top: 1.25rem;
  color: var(--ink-faint);
  font-size: var(--step--1);
}

footer p { margin: 0; }

/* ---------- accessibility ---------- */

.skip {
  position: absolute;
  left: -9999px;
}

.skip:focus {
  left: 1rem;
  top: 1rem;
  background: var(--bg);
  padding: 0.5rem 0.75rem;
  border: 1px solid var(--accent);
  border-radius: var(--radius);
  z-index: 10;
}

/* Two-tone ring so the focus indicator holds against both the page and any
   filled element it lands on. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  box-shadow: 0 0 0 4px var(--bg);
}

/* The stylesheet had no reduced-motion block at all. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ---------- small screens ---------- */

@media (max-width: 34rem) {
  body { padding: 2.5rem 1.25rem 2rem; }

  .masthead {
    flex-direction: column;
    align-items: flex-start;
    gap: 1.25rem;
  }

  .portrait { width: 96px; height: 96px; }
  .identity h1 { font-size: 1.8rem; }
  nav { gap: 1.1rem; }
  .contact-list li { flex-direction: column; gap: 0.15rem; }
  .contact-list .label { flex: none; }

  .entry-head { flex-direction: column; gap: 0.1rem; }
  .entry .date { order: -1; }

  .project {
    grid-template-columns: 1fr;
    grid-template-areas: "year" "fig" "body" "abs";
    gap: 0.75rem 0;
  }
  .proj-year { font-size: var(--step--1); opacity: 1; }
  .proj-figure { max-width: 220px; }

  .gallery { grid-template-columns: 1fr; row-gap: 2.5rem; }
  .shot--1, .shot--2, .shot--3, .shot--4, .shot--5 {
    grid-column: 1 / -1;
    margin-top: 0;
  }
  .shot figcaption { max-width: none; }
}

/* ---------- wide screens: the rail ----------

   One centered column is the right shape for a phone and the wrong shape for
   a laptop: the page becomes a strip of text with two empty margins. Widening
   the paragraphs is not the fix. --measure is set at 66 characters for the
   reason annotated above, and a 120-character line is harder to read, not
   easier.

   So the extra width goes to layout instead of to line length. Identity and
   nav move into a rail on the left that stays put while the content scrolls,
   and the content column keeps what is left over: 672px at the breakpoint,
   832px from 1184px up, against the 640px the centered layout gave it at any
   size. Figures, the gallery and the paper's charts take that room. The
   reading measure itself never changes.

   Scoped to `screen` deliberately. The print block below assumes a single
   column, and a print canvas can be wider than 64rem. */

@media screen and (min-width: 64rem) {

  /* 15rem is the narrowest rail that still holds the name on two lines rather
     than three, and "alessiog@stanford.edu" on one. Both are what set it. */
  :root { --rail: 15rem; }

  body {
    display: grid;
    grid-template-columns: var(--rail) minmax(0, 1fr);
    column-gap: 4rem;
    max-width: 74rem;
  }

  /* Placed explicitly rather than left to auto-flow, because the rail has to
     share row 1 with <main> for the sticky to have anywhere to travel: a
     sticky element is bounded by its own grid area, so in a row of its own it
     would be pinned inside a box its own height and never move. align-self
     matters for the same reason. A stretched item has no slack either. */
  .rail   { grid-column: 1; grid-row: 1 / 3; align-self: start;
            position: sticky; top: 2.5rem; }
  main    { grid-column: 2; grid-row: 1; }
  footer  { grid-column: 2; grid-row: 2; }

  /* ---- inside the rail ---- */

  .masthead {
    flex-direction: column;
    align-items: flex-start;
    gap: 1.1rem;
    margin-bottom: 1.4rem;
  }

  .portrait { width: 104px; height: 104px; }

  /* One step down from --step-3: at 35.2px the name overruns a 15rem rail and
     breaks into three lines. At 28.8px it breaks into two, which is the shape
     the rail wants anyway. */
  .identity h1 { font-size: var(--step-2); }

  /* Stacked, the middot has nothing left to sit between. */
  .contact-line { flex-direction: column; gap: 0.15rem; }
  .contact-line > span[aria-hidden] { display: none; }

  nav {
    flex-direction: column;
    align-items: flex-start;
    gap: 0.55rem;
    padding: 1rem 0;
    margin-bottom: 0;
  }

  /* flex-start above keeps each link only as wide as its own text, so the
     rule under the current page still sits under the word instead of running
     the full width of the rail. The indicator itself is unchanged. */

  /* margin-left: auto pushed the toggle to the far end of the row. In a
     column it would push it to the far side of the rail. */
  .theme-toggle { margin-left: 0; margin-top: 0.7rem; margin-right: 0; }

  /* ---- inside the content column ----
     Everything in <main> now has up to 832px. Figures and photographs should
     use it; text should not. The blocks below are all reading matter, so they
     stop at 42rem, which is about 80 characters at the smaller sizes the CV
     and the contact rows are set in.

     The two rules that are *not* capped are the ones under .page-head and
     over the footer. Those two bracket the page, so they have to agree with
     the widest thing between them: on Elsewhere a 42rem rule under the title
     stopped 160px short of the gallery it was introducing. A section rule is
     a different job and does stay with the text it heads. */

  .entry,
  h2,
  .contact-list,
  .skills,
  .inline-list { max-width: 42rem; }

  /* The h2 above is the section heading with a rule under it. .page-title is
     an h2 too and inherits the cap, which is harmless: it carries no rule of
     its own, and every page title already wraps well before 42rem. */

  /* The 34ch measure was cut against a 250px gallery column. The column is
     330px here, so the captions were breaking earlier than the photograph
     above them needs them to. 40ch is 286px: wider, still inside the photo. */
  .shot figcaption { max-width: 40ch; }

  /* The wide shot spans the column and its caption is one line that has to
     stay one line. The rule above is also a `.shot figcaption`, identical in
     specificity and later in the file than the base `.shot--3` exception, so
     it was quietly overriding it and breaking the joke across two lines. */
  .shot--3 figcaption { max-width: none; }

  /* The notes were the last thing left at --measure while the gallery above
     them ran the full column, so they read as a leftover of the old layout.
     Two columns spend the width on structure rather than on line length:
     each note holds about 52 characters, which is where footnotes belong.
     Every note carries both borders now, because across two columns the
     base's :last-child rule was closing only the right-hand one. */
  .notes {
    max-width: none;
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: 2.5rem;
  }

  .notes li { border-bottom: 1px solid var(--rule-soft); }

  /* The preview grows with the column, and now runs down alongside the
     abstract rather than stopping at the title block. Keeping the abstract in
     the middle column is also what holds its line length down: spanning all
     three, it would be reading matter 830px wide. */
  .project {
    grid-template-columns: 200px minmax(0, 1fr) auto;
    grid-template-areas:
      "fig body year"
      "fig abs  year";
    column-gap: 2rem;
  }
}

/* ---------- wide screens: the About aside ----------

   Gated higher than the rail, deliberately. At 64rem the content column is
   672px, and taking the aside's 248px plus a 40px gutter out of that would
   leave the prose at 384px, which is 46 characters: inside Bringhurst's range
   but sitting on its floor. From 70rem the prose holds 480px and up, and at
   the 74rem cap it lands exactly on --measure. Between 64 and 70rem About
   stays one column, which is the layout it had before any of this. */

@media screen and (min-width: 70rem) {

  .about-grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 15.5rem;
    column-gap: 2.5rem;
    align-items: start;
  }

  .about-side { margin-top: 0; }

  /* the rail's circle steps aside for the larger one */
  .about .portrait { display: none; }

  .portrait-frame {
    display: block;
    overflow: hidden;
    aspect-ratio: 4 / 5;
    margin-bottom: 2rem;
    border-radius: var(--radius);
    /* the same hairline the circle carries, for the same reason */
    box-shadow: 0 0 0 1px var(--rule);
  }

  /* photo.jpg is a 1544x1024 landscape frame with the subject small in the
     middle of it, so plain object-fit: cover would fill this box mostly with
     doorway. The scale crops in to head and shoulders. transform-origin is
     the point the crop is taken around, which is why its x matches
     object-position's.

     1.4 is a ceiling, not a preference. Above it the visible region of the
     source drops below the 620 device pixels a 310px box needs at 2x, and the
     picture starts to soften on exactly the screens this layout is for.
     Re-measure against the source before raising it. */
  .portrait-frame img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: 46% 50%;
    transform: scale(1.4);
    transform-origin: 46% 32%;
    filter: saturate(0.92);
  }

  /* 88px of label plus the handle does not fit 248px. Stacking them is what
     the phone layout already does with these same rows. */
  .about-side .contact-list li { flex-direction: column; gap: 0.15rem; }
  .about-side .contact-list .label { flex: none; }
}

/* ---------- print ---------- */

@media print {
  nav, .todo, .todo-inline, .button, .skip, footer a, .theme-toggle { display: none; }
  /* The marker and the note both stay: on paper that is just a footnote.
     Only the jump back up is meaningless. */
  .fn-back { display: none; }
  body { max-width: none; color: #000; background: #fff; padding: 0; }
  a { color: #000; text-decoration: none; }
  .portrait { display: none; }
  section { margin-bottom: 1.25rem; page-break-inside: avoid; }
  .entry { page-break-inside: avoid; }
  .page-head { border-bottom: 1px solid #999; }
  .abstract { background: none; border-left: 2px solid #999; }
}
