/* ---------------------------------------------------------------
   main.css - one stylesheet for the whole site.

   Journal layout, periwinkle palette. Centered masthead, double
   rules, a justified measure, a drop cap on the opening paragraph,
   and publications set as a numbered reference list.

   Published and accepted work carries the visual weight: the venue
   sits in accent caps directly under the title, and repository links
   are deliberately quiet.
   --------------------------------------------------------------- */

@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500&display=swap');

/* Libron, by Nico Verbruggen: a reading serif tuned for screens, a
   refinement of Readerly, itself based on Production Type's Newsreader.
   SIL Open Font License 1.1; the license ships beside the files in
   fonts/OFL.txt. https://github.com/nicoverbruggen/libron (v0.31) */
@font-face {
    font-family: 'Libron';
    src: url('fonts/Libron-Regular.woff2') format('woff2');
    font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
    font-family: 'Libron';
    src: url('fonts/Libron-Italic.woff2') format('woff2');
    font-weight: 400; font-style: italic; font-display: swap;
}
@font-face {
    font-family: 'Libron';
    src: url('fonts/Libron-Bold.woff2') format('woff2');
    font-weight: 700; font-style: normal; font-display: swap;
}
@font-face {
    font-family: 'Libron';
    src: url('fonts/Libron-BoldItalic.woff2') format('woff2');
    font-weight: 700; font-style: italic; font-display: swap;
}

:root {
    --paper:     #f0f1fa;
    --paper-2:   #e2e4f4;
    --ink:       #26283f;
    --accent:    #545a9c;          /* links, venues, section labels */
    --accent-2:  #9095ca;          /* decorative rules only - too pale for text */
    --wash:      #dcdef4;          /* selection and link hover */
    --rule:      rgba(38, 40, 63, 0.20);
    --hairline:  rgba(38, 40, 63, 0.10);
    --muted:     rgba(38, 40, 63, 0.68);

    --serif: 'Libron', 'Iowan Old Style', Georgia, 'Times New Roman', serif;
    --mono:  'IBM Plex Mono', ui-monospace, 'SFMono-Regular', Consolas, monospace;

    --measure: 36rem;              /* the reading column */
    --page:    50rem;              /* the full page, for blocks that break out */
    color-scheme: light dark;
}

@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
        --paper:    #111326;
        --paper-2:  #191c32;
        --ink:      #e4e6f4;
        --accent:   #a6abe8;
        --accent-2: #7f85b8;
        --wash:     #272b49;
        --rule:     rgba(228, 230, 244, 0.24);
        --hairline: rgba(228, 230, 244, 0.13);
        --muted:    rgba(228, 230, 244, 0.62);
    }
}
:root[data-theme="dark"] {
    --paper:    #111326;
    --paper-2:  #191c32;
    --ink:      #e4e6f4;
    --accent:   #a6abe8;
    --accent-2: #7f85b8;
    --wash:     #272b49;
    --rule:     rgba(228, 230, 244, 0.24);
    --hairline: rgba(228, 230, 244, 0.13);
    --muted:    rgba(228, 230, 244, 0.62);
}

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

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

body {
    position: relative;
    overflow-x: hidden;        /* the drawing bleeds past the right edge */
    margin: 0;
    padding: 0 16px 5rem;
    background-color: var(--paper);
    color: var(--ink);
    font-family: var(--serif);
    font-size: 1.0625rem;
    line-height: 1.6;
    -webkit-font-smoothing: antialiased;
}

.wrap { max-width: var(--page); margin: 0 auto; }

/* ------------------------------ botanical art ----------------- */

/* One plant per page, drawn in fine ink with soft fills, sitting in the
   right margin on plain paper. Pieces marked .drift move with scroll
   progress (--sp, set by app.js): the dandelion's seeds blow away, petals
   fall and steam rises as you read. Transform only, so it composites
   without repainting; nothing moves for reduced-motion users. */

:root {
    --sp: 0;
    --art-a: #505ba7;        /* ink: stems, outlines */
    --art-b: #a18cc8;        /* lavender: steam, roots */
    --art-c: #c46a95;        /* rose: blossoms */
    --art-leaf: #5f8f80;     /* sage: leaves */
    --art-opacity: 0.9;
}
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
        --art-a: #a3aee8;
        --art-b: #9a86c4;
        --art-c: #e597bb;
        --art-leaf: #8cbcab;
        --art-opacity: 0.8;
    }
}
:root[data-theme="dark"] {
    --art-a: #a3aee8;
    --art-b: #9a86c4;
    --art-c: #e597bb;
    --art-leaf: #8cbcab;
    --art-opacity: 0.8;
}

.artwork {
    position: fixed;
    top: 0;
    right: 0;
    width: clamp(18rem, 30vw, 40rem);
    height: 100vh;
    z-index: 0;
    opacity: var(--art-opacity);
    pointer-events: none;
    -webkit-mask-image: linear-gradient(to right, transparent 0%, #000 40%);
            mask-image: linear-gradient(to right, transparent 0%, #000 40%);
    transform: translate3d(0, calc(var(--sp) * -3vh), 0);
}
.artwork svg { display: block; width: 100%; height: 100%; overflow: visible; }
.artwork .drift {
    transform-box: fill-box;
    transform-origin: center;
    transform: translate(calc(var(--sp) * var(--dx, 0) * 1px),
                         calc(var(--sp) * var(--dy, 0) * 1px))
               rotate(calc(var(--sp) * var(--dr, 0) * 1deg));
}

/* ------------------------------ motion ------------------------ */

/* The vocabulary the drawings use. Each element's resting style is its
   finished state; time-based animations run from a start state toward it,
   so turning animation off simply shows the drawing complete. Origins are
   in the drawing's own coordinates (--ox, --oy). */

.artwork .sway, .artwork .float, .artwork .unfold, .artwork .crack,
.artwork .grow, .artwork .spin {
    transform-box: view-box;
    transform-origin: calc(var(--ox, 0) * 1px) calc(var(--oy, 0) * 1px);
}

/* Plants moving in a light wind. */
.artwork .sway {
    animation: art-sway var(--dur, 7s) ease-in-out var(--delay, 0s) infinite alternate;
}
@keyframes art-sway {
    from { transform: rotate(calc(var(--amp, 1) * -1deg)); }
    to   { transform: rotate(calc(var(--amp, 1) * 1deg)); }
}

/* Seeds, leaves and petals letting go: drift away, fade, return. */
.artwork .float {
    opacity: 0;
    animation: art-float var(--dur, 16s) cubic-bezier(0.35, 0.05, 0.6, 1) var(--delay, 0s) infinite;
}
@keyframes art-float {
    0%   { transform: translate(0, 0) rotate(0deg); opacity: 0; }
    10%  { opacity: var(--op, 0.7); }
    72%  { opacity: var(--op, 0.7); }
    100% { transform: translate(calc(var(--fx, 0) * 1px), calc(var(--fy, 0) * 1px))
                      rotate(calc(var(--fr, 0) * 1deg)); opacity: 0; }
}
.artwork .bob {
    animation: art-bob var(--bdur, 3s) ease-in-out infinite alternate;
}
@keyframes art-bob {
    from { transform: translate(-7px, 2px); }
    to   { transform: translate(7px, -2px); }
}

/* Strokes that draw themselves in, once (paths carry pathLength="1"). */
.artwork .draw {
    stroke-dasharray: 1;
    stroke-dashoffset: 0;
    animation: art-draw var(--dur, 2s) cubic-bezier(0.4, 0.1, 0.3, 1) var(--delay, 0s) backwards;
}
@keyframes art-draw { from { stroke-dashoffset: 1; opacity: 0; } }

/* Leaves and petals opening from their base, once. */
.artwork .unfold {
    animation: art-unfold var(--dur, 1.4s) cubic-bezier(0.2, 0.8, 0.25, 1.15) var(--delay, 0s) backwards;
}
@keyframes art-unfold { from { transform: scale(0.001) rotate(calc(var(--r0, 0) * 1deg)); opacity: 0; } }

/* A seed coat splitting open, once. */
.artwork .crack {
    transform: rotate(calc(var(--rot, 0) * 1deg));
    animation: art-crack 1.1s cubic-bezier(0.3, 0.6, 0.3, 1) var(--delay, 0s) backwards;
}
@keyframes art-crack { from { transform: rotate(0deg); } }

/* Steam: rises and thins out, over and over. */
.artwork .rise {
    animation: art-rise var(--dur, 7s) ease-in-out var(--delay, 0s) infinite;
}
@keyframes art-rise {
    0%   { transform: translateY(18px); opacity: 0; }
    35%  { opacity: 1; }
    100% { transform: translateY(-48px); opacity: 0; }
}

/* Rings spreading across water, over and over. */
.artwork .ripple {
    transform-box: view-box;
    transform-origin: calc(var(--ox, 0) * 1px) calc(var(--oy, 0) * 1px);
    animation: art-ripple var(--dur, 6s) ease-out var(--delay, 0s) infinite;
}
@keyframes art-ripple {
    0%   { transform: scale(0.2); opacity: 0; }
    15%  { opacity: 1; }
    100% { transform: scale(1.7); opacity: 0; }
}

/* Scroll-driven: appears between --a and --a + --w of the way down. */
.artwork .grow { --g: clamp(0, calc((var(--sp) - var(--a, 0)) / var(--w, 0.2)), 1); }
.artwork path.grow { stroke-dasharray: 1; stroke-dashoffset: calc(1 - var(--g)); }
.artwork g.grow { transform: scale(var(--g)); opacity: min(1, calc(var(--g) * 3)); }

/* Scroll-driven: the coneflower spirals turn as you read. */
.artwork .spin { transform: rotate(calc(var(--sp) * 50deg)); }

@media (prefers-reduced-motion: reduce) {
    .artwork, .artwork * { animation: none !important; }
    .artwork .float { display: none; }
    body[data-art="teaching"] .artwork { --sp: 1; }   /* show it fully grown */
}

/* Text sits above it. The drawing is a child of .wrap, so its siblings
   need their own layer above it. */
.wrap { position: relative; z-index: 1; }
.wrap > :not(.artwork) { position: relative; z-index: 1; }

/* Narrow screens have no margin. Each page gets the drawing as a band in
   the flow, between the nav and the content, framed on that page's
   subject (--focus is where the band starts, as a share of the drawing's
   height). Nothing ever sits behind the text. Home keeps it in the corner
   beside the photo, where there is room. */
@media (max-width: 64rem) {
    .artwork {
        position: relative;
        top: auto;
        right: auto;
        width: auto;
        height: var(--band-h, 15rem);
        margin: 0.4rem -16px -0.6rem;
        overflow: hidden;
        transform: none;
        opacity: var(--art-opacity);
        -webkit-mask-image: linear-gradient(to bottom, transparent 0%, #000 14%, #000 80%, transparent 100%);
                mask-image: linear-gradient(to bottom, transparent 0%, #000 14%, #000 80%, transparent 100%);
    }
    .artwork svg {
        width: min(100%, 26rem);
        height: auto;
        aspect-ratio: 420 / 960;
        margin: 0 auto;
        transform: translateY(calc(var(--focus, 30) * -1%));
    }
    body[data-art="safety"]     { --focus: 20; }
    body[data-art="capability"] { --focus: 15; }
    body[data-art="resume"]     { --focus: 31; --band-h: 16rem; }
    body[data-art="teaching"]   { --focus: 48; --band-h: 16rem; }
    body[data-art="writing"]    { --focus: 50; --band-h: 17rem; }

    body[data-art="home"] .artwork {
        position: absolute;
        top: 0;
        right: 0;
        width: min(58vw, 17rem);
        height: auto;
        aspect-ratio: 420 / 960;
        margin: 0;
        overflow: visible;
        opacity: calc(var(--art-opacity) * 0.8);
        -webkit-mask-image: linear-gradient(to right, transparent 0%, #000 55%),
                            linear-gradient(to bottom, #000 55%, transparent 100%);
        -webkit-mask-composite: source-in;
                mask-image: linear-gradient(to right, transparent 0%, #000 55%),
                            linear-gradient(to bottom, #000 55%, transparent 100%);
                mask-composite: intersect;
    }
    body[data-art="home"] .artwork svg { width: 100%; height: 100%; transform: none; }
}
@media print { .artwork { display: none; } }

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

#masthead {
    position: relative;
    padding: 3.2rem 0 0;
    text-align: center;
}

h1 {
    margin: 0;
    font-weight: 600;
    font-size: clamp(2rem, 5vw, 2.7rem);
    line-height: 1.1;
    letter-spacing: -0.01em;
}
h1 .subtitle {
    display: block;
    margin-top: 0.6rem;
    font-family: var(--mono);
    font-size: 0.72rem;
    font-weight: 400;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--muted);
}
h1 a:link, h1 a:visited { color: inherit; text-decoration: none; }
h1 a:hover { background: none; }

#theme-toggle {
    position: absolute;
    top: 1.1rem;
    right: 0;
    font-family: var(--mono);
    font-size: 0.68rem;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--muted);
    background: none;
    border: 0;
    padding: 0.2rem;
    cursor: pointer;
}
#theme-toggle:hover { color: var(--accent); }

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

#sitenav {
    margin: 1.8rem 0 0;
    border-top: 3px double var(--rule);
    border-bottom: 3px double var(--rule);
}
#sitenav ul {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 0.3rem 1.8rem;
    margin: 0;
    padding: 0.6rem 0;
    max-width: none;
    list-style: none;
    font-family: var(--mono);
    font-size: 0.72rem;
    letter-spacing: 0.13em;
    text-transform: uppercase;
}
#sitenav li { margin: 0; }
#sitenav a:link, #sitenav a:visited {
    color: var(--ink);
    text-decoration: none;
    padding-bottom: 2px;
    border-bottom: 1px solid transparent;
}
#sitenav a:hover { border-bottom-color: var(--accent); background: none; }
#sitenav [aria-current="true"],
#sitenav [aria-current="page"] {
    color: var(--accent);
    padding-bottom: 2px;
    border-bottom: 1px solid var(--accent);
}

/* Downloads: a quieter second row under the sections. */
#sitenav .downloads {
    flex-basis: 100%;
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    align-items: baseline;
    gap: 0 0.55rem;
    margin-top: 0.35rem;
    padding-top: 0.45rem;
    border-top: 1px solid var(--hairline);
    font-size: 0.64rem;
    letter-spacing: 0.12em;
    color: var(--muted);
}
#sitenav .downloads a:link, #sitenav .downloads a:visited { color: var(--accent); }
#sitenav .downloads a:hover { border-bottom-color: var(--accent); }
#sitenav .downloads .sep { color: var(--muted); }
#sitenav .downloads .resumes { white-space: nowrap; }
#sitenav .resumes .sep { margin: 0 0.55rem; }

/* ------------------------------ the column -------------------- */

main {
    counter-reset: ref;
    max-width: var(--page);
    margin: 2.6rem auto 0;
}

/* Everything sits in the reading measure; .bio and .wide break out. */
main > p,
main > ul,
main > ol,
main > h2,
main > h3,
main > h4,
main > .pub,
main > .entries,
main > .cv-item,
main > blockquote {
    max-width: var(--measure);
    margin-left: auto;
    margin-right: auto;
}

/* ------------------------------ headings ---------------------- */

/* Section labels sit on a rule that runs the width of the column. */
h2 {
    scroll-margin-top: 3.5rem;
    margin: 3rem auto 1.1rem;
    padding-bottom: 0.5rem;
    border-bottom: 1px solid var(--rule);
    font-family: var(--mono);
    font-size: 0.76rem;
    font-weight: 500;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: var(--accent);
}

h3 {
    margin: 0 0 0.2rem;
    font-weight: 600;
    font-size: 1.15rem;
    line-height: 1.3;
    letter-spacing: -0.005em;
}

h4 {
    margin: 2rem auto 0.4rem;
    font-family: var(--mono);
    font-size: 0.7rem;
    font-weight: 500;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--muted);
}

/* ------------------------------ text -------------------------- */

p { margin: 0 0 1rem; }
main p { text-align: justify; hyphens: auto; }

.lead { font-size: 1.15rem; line-height: 1.5; }
.lead::first-letter {
    float: left;
    font-size: 3.2em;
    line-height: 0.82;
    padding: 0.08em 0.09em 0 0;
    color: var(--accent);
    font-weight: 400;     /* Libron has 400 and 700 only; at this size regular carries it */
}

ul, ol { padding-left: 1.2rem; }
li { margin: 0.3rem 0; }

a:link, a:visited {
    color: var(--accent);
    text-decoration: underline;
    text-decoration-thickness: 1px;
    text-underline-offset: 2.5px;
}
a:hover {
    background-color: var(--wash);
    text-decoration-color: currentColor;
}
a:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
::selection { background-color: var(--wash); color: var(--ink); }

strong, b { font-weight: 600; }
code, kbd { font-family: var(--mono); font-size: 0.9em; }

hr { margin: 3rem auto; border: 0; border-top: 1px solid var(--rule); max-width: var(--measure); }

/* ------------------------------ bio + portrait ---------------- */

/* The one block that breaks out of the measure, so the photo can sit
   beside the opening paragraphs. */
.bio {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 13rem;
    gap: 2.4rem;
    align-items: start;
    max-width: var(--page);
    margin: 0 auto;
}
.bio .bio-text > p:last-child { margin-bottom: 0; }

#portrait {
    width: 100%;
    /* height:auto is load-bearing: without it the img's height attribute
       wins as a presentational hint, the box stops being square, and
       object-fit crops a vertical strip out of the middle of the photo. */
    height: auto;
    display: block;
    aspect-ratio: 1 / 1;
    object-fit: cover;
    border: 1px solid var(--rule);
    background-color: var(--paper-2);
}
.caption {
    margin: 0.5rem 0 0;
    font-family: var(--mono);
    font-size: 0.66rem;
    line-height: 1.5;
    letter-spacing: 0.03em;
    color: var(--muted);
    text-align: left;
}

/* Under the portrait: a short stacked list, not a row. */
.intro-photo .contact {
    flex-direction: column;
    align-items: flex-start;
    gap: 0.3rem;
    margin-top: 0.9rem;
    font-size: 0.72rem;
}

.contact {
    display: flex;
    flex-wrap: wrap;
    gap: 0.2rem 1.1rem;
    list-style: none;
    padding: 0;
    margin: 1rem 0 0;
    font-family: var(--mono);
    font-size: 0.68rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}
.contact li { margin: 0; }

/* The one-line facts under the intro paragraph: where I am, in order. */
.facts {
    list-style: none;
    padding: 0;
    margin: 1rem 0 0;
    font-size: 0.92rem;
    line-height: 1.45;
}
.facts li {
    margin: 0.25rem 0;
    padding-left: 0.9rem;
    border-left: 2px solid var(--hairline);
    color: var(--muted);
}
.facts .k {
    display: inline;
    margin-right: 0.45rem;
    font-family: var(--mono);
    font-size: 0.66rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--ink);
}

@media (max-width: 46rem) {
    .bio { grid-template-columns: 1fr; gap: 1.4rem; }
    .bio-side { max-width: 13rem; }
}

/* ------------------------------ intro block ------------------- */

/* Portrait and contact, side by side under the masthead. */
.intro {
    display: grid;
    grid-template-columns: 12rem minmax(0, 1fr);
    gap: 2rem;
    align-items: start;
    max-width: 44rem;
    margin: 2.4rem auto 0;
}
.intro p { text-align: left; margin-bottom: 0; }
.intro-side p + p { margin-top: 0.9rem; }
.intro .intro-side h4:first-child { margin-top: 0; }
@media (max-width: 34rem) {
    .intro { grid-template-columns: 1fr; gap: 1.2rem; }
    .intro-photo { max-width: 12rem; }
}

/* ------------------------------ filter bar -------------------- */

/* Nav bar two. It sits under the bio and toggles which papers show,
   so the page stays short instead of running on for sixteen entries. */
.filterbar {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 0.4rem 0.5rem;
    max-width: 44rem;
    margin: 2.8rem auto 1.6rem;
    padding-bottom: 0.9rem;
    border-bottom: 1px solid var(--rule);
}
.filterbar button {
    font-family: var(--mono);
    font-size: 0.66rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--muted);
    background: none;
    border: 1px solid var(--hairline);
    border-radius: 2px;
    padding: 0.28rem 0.6rem;
    cursor: pointer;
}
.filterbar button:hover { color: var(--ink); border-color: var(--rule); }
.filterbar button[aria-pressed="true"] {
    color: var(--paper);
    background-color: var(--accent);
    border-color: var(--accent);
}
.filterbar button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.pub[hidden] { display: none; }

/* ------------------------------ paper dialogs ----------------- */

/* The compact index entry: title, venue, authors, and a button that
   opens the summary. No abstract inline - that is what the dialog and
   the detail pages are for. */
.pub.compact { margin-bottom: 1.3rem; }
.pub.compact .authors { margin-bottom: 0.3rem; }

.more {
    display: none;                 /* revealed only when JS is running */
    font-family: var(--mono);
    font-size: 0.62rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--muted);
    background: none;
    border: 1px solid var(--hairline);
    border-radius: 2px;
    padding: 0.18rem 0.5rem;
    cursor: pointer;
}
.js .more { display: inline-block; }
.more:hover { color: var(--accent); border-color: var(--accent); }
.more:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.paper-dialog {
    max-width: 34rem;
    width: calc(100% - 2rem);
    padding: 1.6rem 1.8rem 1.4rem;
    border: 1px solid var(--rule);
    border-radius: 3px;
    background-color: var(--paper);
    color: var(--ink);
    box-shadow: 0 16px 50px rgba(0, 0, 0, 0.3);
}
.paper-dialog::backdrop {
    background: rgba(38, 40, 63, 0.45);
    backdrop-filter: blur(2px);
}
.paper-dialog h3 {
    margin: 0 0 0.9rem;
    padding-bottom: 0.7rem;
    border-bottom: 1px solid var(--hairline);
    font-size: 1.1rem;
}
.paper-dialog p { text-align: left; margin-bottom: 0.9rem; }
.paper-dialog .links { font-family: var(--mono); font-size: 0.66rem; letter-spacing: 0.1em; text-transform: uppercase; }
.paper-dialog .close {
    font-family: var(--mono);
    font-size: 0.64rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--paper);
    background-color: var(--accent);
    border: 0;
    border-radius: 2px;
    padding: 0.35rem 0.9rem;
    cursor: pointer;
}
.paper-dialog .close:hover { background-color: var(--ink); }
.paper-dialog .close:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

@media print { .more, .paper-dialog { display: none; } }

/* ------------------------------ publications ------------------ */

/* Numbered like a reference list. The venue line is the loudest part
   after the title: accepted work should read as accepted at a glance. */

.pub {
    counter-increment: ref;
    position: relative;
    margin: 0 auto 1.8rem;
    padding-left: 2.4rem;
}
.pub::before {
    content: "[" counter(ref) "]";
    position: absolute;
    left: 0;
    top: 0.2rem;
    font-family: var(--mono);
    font-size: 0.74rem;
    color: var(--accent);
}

.pub .venue {
    display: block;
    margin: 0.25rem 0 0.4rem;
    font-family: var(--mono);
    font-size: 0.68rem;
    font-weight: 500;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--accent);
}
.pub .venue .status {
    color: var(--muted);
    font-weight: 400;
    letter-spacing: 0.08em;
}

.pub .authors {
    margin: 0 0 0.45rem;
    font-size: 0.92rem;
    line-height: 1.45;
    color: var(--muted);
    text-align: left;
}
.pub .authors .me { color: var(--ink); font-weight: 600; }

.pub .abstract { margin: 0 0 0.4rem; }

/* Repository and PDF links sit below the work, quiet on purpose. */
.pub .links {
    margin: 0;
    font-family: var(--mono);
    font-size: 0.64rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    text-align: left;
}
.pub .links a:link, .pub .links a:visited {
    color: var(--muted);
    text-decoration-color: var(--hairline);
    margin-right: 0.9rem;
}
.pub .links a:hover { color: var(--accent); }

/* Unrefereed items: no venue line, so the title carries less weight. */
.pub.informal h3 { font-weight: 500; font-size: 1.05rem; }

/* Equal-contribution footnote under a publication list. */
.footnote {
    margin: -0.6rem auto 0;
    max-width: var(--measure);
    font-family: var(--mono);
    font-size: 0.66rem;
    letter-spacing: 0.04em;
    color: var(--muted);
    text-align: left;
}
.pub .authors .eq { color: var(--accent); font-weight: 600; }

/* ------------------------------ entry lists ------------------- */

.entries { list-style: none; padding: 0; }
.entries li {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0 0.9rem;
    margin: 0;
    padding: 0.6rem 0;
    border-bottom: 1px solid var(--hairline);
}
.entries .date {
    flex: 0 0 4.5rem;
    font-family: var(--mono);
    font-size: 0.68rem;
    letter-spacing: 0.06em;
    color: var(--muted);
}
.entries .what { flex: 1 1 16rem; min-width: 0; text-align: left; }
.entries .note { display: block; font-size: 0.9rem; color: var(--muted); }

/* ------------------------------ log entries ------------------- */

/* The research log: dated, informal, newest first. Looser than a .pub
   because it is writing rather than a reference list. */
.log {
    max-width: var(--measure);
    margin: 0 auto 2.2rem;
    padding-bottom: 1.6rem;
    border-bottom: 1px solid var(--hairline);
}
.log:last-of-type { border-bottom: 0; }
.log h3 {
    margin-bottom: 0.3rem;
    font-size: 1.2rem;
}
.log .when {
    display: block;
    margin-bottom: 0.8rem;
    font-family: var(--mono);
    font-size: 0.66rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--accent);
}
.log p { text-align: justify; hyphens: auto; }
.log p:last-child { margin-bottom: 0; }

/* One line at the top of each entry, for people who will read only that. */
.log .tldr {
    margin: 0 0 1.1rem;
    padding: 0.55rem 0.85rem;
    border-left: 2px solid var(--accent);
    background-color: var(--hairline);
    background-color: color-mix(in srgb, var(--accent) 7%, transparent);
    font-size: 0.97rem;
    line-height: 1.5;
    text-align: left;
    hyphens: manual;
}
.log .tldr .k {
    margin-right: 0.45rem;
    font-family: var(--mono);
    font-size: 0.64rem;
    letter-spacing: 0.12em;
    color: var(--accent);
}

/* ------------------------------ CV ---------------------------- */

.cv-item {
    display: grid;
    grid-template-columns: 7.5rem minmax(0, 1fr);
    gap: 0 1.4rem;
    padding: 0.8rem 0;
    border-bottom: 1px solid var(--hairline);
    max-width: 42rem;
}
.cv-item .when {
    padding-top: 0.3rem;
    font-family: var(--mono);
    font-size: 0.68rem;
    letter-spacing: 0.06em;
    color: var(--muted);
}
.cv-item .what p { text-align: left; }
.cv-item .what p:last-child { margin-bottom: 0; }
.cv-item .what .where { color: var(--muted); }
@media (max-width: 34rem) {
    .cv-item { grid-template-columns: 1fr; gap: 0.2rem; }
    .cv-item .when { padding-top: 0; }
}

/* Curriculum rows on the teaching page. */
.cv-item .topics {
    font-family: var(--mono);
    font-size: 0.64rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--accent);
}
.cv-item .milestone { font-size: 0.88rem; }
.cv-item .when { line-height: 1.5; }

/* ------------------------------ link notes -------------------- */

/* Links carry a short note in their title; app.js shows it as this
   bubble on hover and on keyboard focus. Colors invert the page, so it
   reads in both themes. */
.tip {
    position: fixed;
    z-index: 60;
    max-width: 17rem;
    padding: 0.45rem 0.65rem;
    border-radius: 4px;
    background: var(--ink);
    color: var(--paper);
    font-family: var(--serif);
    font-size: 0.86rem;
    line-height: 1.38;
    box-shadow: 0 6px 22px rgba(0, 0, 0, 0.22);
    pointer-events: none;
    opacity: 0;
    transform: translateY(3px);
    transition: opacity 0.14s ease, transform 0.14s ease;
}
.tip.on { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) { .tip { transition: none; } }
@media print { .tip { display: none; } }

/* Linked paper titles and labels: quieter than body links. */
.pub h3 a:link, .pub h3 a:visited {
    color: inherit;
    text-decoration-color: var(--rule);
}
.pub h3 a:hover { color: var(--accent); background: none; }
.facts .k a:link, .facts .k a:visited, h4 a:link, h4 a:visited {
    color: inherit;
    text-decoration-color: var(--rule);
}
.facts .k a:hover, h4 a:hover { color: var(--accent); background: none; }

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

footer {
    max-width: var(--measure);
    margin: 4rem auto 0;
    padding-top: 1.2rem;
    border-top: 3px double var(--rule);
    font-size: 0.9rem;
    color: var(--muted);
    text-align: center;
}
.keywords { font-family: var(--mono); font-size: 0.64rem; letter-spacing: 0.05em; line-height: 1.7; }

.skip { position: absolute; left: -9999px; }
.skip:focus {
    left: 1rem;
    top: 1rem;
    z-index: 10;
    padding: 0.5rem 0.8rem;
    background-color: var(--paper);
    border: 2px solid var(--accent);
}

/* ------------------------------ phones ------------------------ */

@media (max-width: 40rem) {
    /* Justified text on a narrow measure leaves rivers of space. */
    main p, .log p { text-align: left; hyphens: manual; }

    /* Nav: short labels, and links tall enough to hit with a thumb. The
       dropped word stays readable to screen readers. */
    .nav-long {
        position: absolute;
        width: 1px;
        height: 1px;
        overflow: hidden;
        clip-path: inset(50%);
        white-space: nowrap;
    }
    #sitenav ul { gap: 0 1.1rem; padding: 0.15rem 0; }
    #sitenav a:link, #sitenav a:visited {
        display: inline-block;
        padding: 0.7rem 0.1rem 0.6rem;
    }
    #sitenav .downloads { margin-top: 0; padding-top: 0.1rem; gap: 0 0.35rem; letter-spacing: 0.05em; }
    #sitenav .resumes .sep { margin: 0 0.35rem; }
    #sitenav .downloads a:link, #sitenav .downloads a:visited { padding: 0.55rem 0.1rem 0.5rem; }

    /* The topic filter becomes one row of chips you swipe, fading at the
       right edge to show there is more. */
    .filterbar {
        flex-wrap: nowrap;
        justify-content: flex-start;
        overflow-x: auto;
        scrollbar-width: none;
        margin: 2.2rem -16px 1.4rem;
        padding: 0 16px 0.9rem;
        -webkit-mask-image: linear-gradient(to right, #000 82%, transparent);
                mask-image: linear-gradient(to right, #000 82%, transparent);
    }
    .filterbar::-webkit-scrollbar { display: none; }
    .filterbar button { flex: none; padding: 0.62rem 0.85rem; }

    /* Other small targets. */
    .more { padding: 0.5rem 0.8rem; }
    #theme-toggle { top: 0.35rem; right: -0.45rem; padding: 0.7rem 0.6rem; }
    .contact { gap: 0.15rem; }
    .contact li a { display: inline-block; padding: 0.3rem 0; }

    /* Dialogs use the screen. */
    .paper-dialog { padding: 1.3rem 1.2rem 1.1rem; width: calc(100% - 1.5rem); }
}

/* The narrowest phones: CV and resumes on their own lines, so no line
   ends on a stray separator. */
@media (max-width: 22rem) {
    #sitenav .downloads > .sep { display: none; }
    #sitenav .downloads { gap: 0 0.6rem; }
}

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

@media print {
    :root {
        --paper: #fff;
        --ink: #000;
        --accent: #000;
        --muted: #444;
        --rule: #999;
        --hairline: #ccc;
    }
    body { font-size: 10.5pt; padding: 0; }
    #sitenav, #theme-toggle, .bio-side, .caption { display: none; }
    a:link, a:visited { text-decoration: none; }
    .cv-item, .entries li, .pub { break-inside: avoid; }
    .bio { display: block; }
}
