/* ---- EB Garamond ----
   Served from Google Fonts (the <link> lives in each page's head), with
   Georgia and Times as the fallback stack so the page still sets in an
   old-style serif if the request fails.

   Replaces Paragon, which was self-hosted from assets/fonts and shipped a
   single 400 cut: no bold, which meant nothing on the page could be marked
   by weight. EB Garamond carries 400/500/600 plus a true italic, so the
   weight overrides that pass left behind are gone and `font-synthesis: none`
   with them — there is no longer a fake bold to suppress. */
/* ---- Palette ----
   White ground, near-black ink. The warm dark palette is gone; what warmth
   is left lives in the ink, which keeps a slight warm bias (R > B) rather
   than being a neutral grey, so the page does not read as clinical.

   The alpha tokens are tinted with the ink colour instead of pure black, so
   borders and surfaces stay in the same temperature family as the type.

   ---- Type ----
   Two serifs, paired by optical size rather than by style. The display face
   carries the name and the section headings; the text face carries
   everything else, including the small data lines that used to be mono. One
   genre, two jobs — which reads as deliberate where a serif plus a mono read
   as two unrelated voices alternating line by line.

   Playfair Display is a high-contrast transitional display serif: thin
   hairlines against heavy stems, a large x-height, tight spacing. It earns
   its place at the sizes it runs at here (the name, the section headings,
   the pull quote) and nowhere smaller — the hairlines thin out below about
   24px, which is exactly why the text face exists alongside it.

   Newsreader is a screen text serif with a tall x-height and sturdier
   strokes, which is what the 12-16px data lines need. */
:root {
    --bg: #ffffff;
    --text: #15161a;
    /* The accent is link text and list markers, so it owes 4.5:1. A brighter
       orange fails on white — #f97316 measures 2.80:1, #ea580c 3.56:1 — so
       this is a true burnt orange that clears with headroom at 5.18:1
       rather than one scraping the line. */
    --accent: #c2410c;
    --border: rgba(21, 22, 26, 0.13);
    --border-strong: rgba(21, 22, 26, 0.28);
    --surface: rgba(21, 22, 26, 0.04);
    /* A card surface just off the ground. On white the lift has to come from
       going darker, not lighter. */

    /* One family for the whole site. The two tokens are kept so the display
       and text roles stay nameable, but they resolve to the same face: a
       legibility serif scales up to a masthead more gracefully than a
       display serif scales down to a 13px data line. */
    --font-display: "EB Garamond", Georgia, "Times New Roman", serif;
    --font-text: "EB Garamond", Georgia, "Times New Roman", serif;
    /* Interface text only — labels, chips, dates, metadata, nav. A serif
       carries its detail in thin strokes and bracketed serifs, and below
       about 14px those stop resolving; the 12px status chips were the worst
       of it. Prose and headings stay in the serif. Every swapped rule also
       drops a size step: mono has a far larger x-height, so it reads bigger
       than Garamond at the same nominal size. */
    --font-mono: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, monospace;

    /* The sidebar's width and the gutter between the columns, which together
       set the reading column: 840 - 300 - 64 = 476px. The prose fills that
       column rather than being capped inside it, so there is no dead band
       between where a line ends and where the divider starts.
       300px is the narrowest the sidebar goes before the education entries
       and the track title start wrapping — that was measured at 219px. */
    --aside-width: 300px;
    --column-gap: 4rem;
    /* One centred container for the whole page. Every row — headings, the
       timeline, the project grids — shares this width and this left edge, so
       nothing drifts apart as the window grows. Content inside it is
       left-aligned; it's the container that centres, not the text.
       --page-pad is the floor for the side gutters, not the gutter itself.

       840px, not the reference's 1024px. His frame gives a 620px reading
       column that holds 56 characters because JetBrains Mono is wide;
       Paragon at 15px puts 93 characters in that same column, which is well
       past the 45-75 a line should run. Matching his pixels with a narrower
       typeface would copy the number and lose the proportion. 840 lands the
       column at 476px and the line at ~71 characters. */
    --content-max: 840px;
    --page-pad: 3rem;
    /* One step up from --surface, for the project rows on hover. */
    --surface-strong: rgba(21, 22, 26, 0.055);
    color-scheme: light;
}

/* ---- Dark ----
   Only the colour tokens are redefined; every rule on the page reads through
   them, so nothing else changes. A true near-black rather than a tinted one,
   to match the white it mirrors. Dark has the easier job of the two: a bright
   accent gains contrast against this ground, where on white it loses it,
   which is why the teal can be vivid while the orange has to be deep.

   Two selectors for the same values. The media query covers everyone who has
   never touched the toggle, guarded with :not([data-theme="light"]) so an
   explicit light choice still wins on a dark OS. The attribute selector
   covers the toggle itself, in both directions. */
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
        --bg: #0d0d0d;
        --text: #ededed;
        --accent: #2dd4bf;   /* 10.44:1 on the ground — AA */
        --border: rgba(237, 237, 237, 0.15);
        --border-strong: rgba(237, 237, 237, 0.32);
        --surface: rgba(237, 237, 237, 0.05);
        --surface-strong: rgba(237, 237, 237, 0.08);
        color-scheme: dark;
    }
}

:root[data-theme="dark"] {
    --bg: #0d0d0d;
    --text: #ededed;
    --accent: #2dd4bf;
    --border: rgba(237, 237, 237, 0.15);
    --border-strong: rgba(237, 237, 237, 0.32);
    --surface: rgba(237, 237, 237, 0.05);
    --surface-strong: rgba(237, 237, 237, 0.08);
    color-scheme: dark;
}


html {
    scroll-behavior: smooth;
}

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

/* Flat. The 24px dot grid that tiled this came from an earlier, busier
   version of the page and put texture behind every line of type on the
   site. Nothing is laid out against it, and its token went with it. */
body {
    /* Everything renders lowercase. Done here rather than by rewriting the
       markup so the Spotify track title — which arrives from the API at
       runtime — follows the same rule, and so <title>, the meta description
       and the og: tags keep their real casing for search results, social
       cards and screen readers. Copying text off the page still yields the
       properly-cased original. */
    text-transform: lowercase;
    background-color: var(--bg);
    color: var(--text);
    font-family: var(--font-text);
}


/* Content-height sections in one continuous scroll — no 100vh, no snap.
   Rows span the full width; only prose is held to a readable measure. */
section {
    /* Clears the fixed dock at the top, otherwise an anchor jump lands the
       heading underneath it. */
    scroll-margin-top: 6rem;
    padding: 5rem var(--page-pad);
}

.timeline,
.group-label,
.sheet-featured,
.sheet-grid,
.sheet-rows,
section > h2 {
    width: 100%;
    max-width: var(--content-max);
    margin-inline: auto;
}

/* ---- Theme toggle ----
   Sits with the contact marks at the right of the nav row. Same 19px box and
   same ink as those, so it reads as one row of utilities rather than a
   control bolted on.

   Both icons are in the markup; CSS picks one. The sun shows on the light
   page (press it to go dark), the moon on the dark page. Hit area is padded
   to 44px with a matching negative margin, same trick as the deck arrows --
   the glyph stays 19px and the target is tappable. */
.masthead-utils {
    display: flex;
    align-items: center;
    gap: 1.5rem;
}

#theme-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--text);
    background: none;
    border: 0;
    padding: 12.5px;
    margin: -12.5px;
    cursor: pointer;
    opacity: 1;
    transition: opacity 0.2s ease;
}

#theme-toggle:hover {
    opacity: 0.6;
}

.theme-icon {
    width: 19px;
    height: 19px;
    display: block;
}

/* Default (light): show the sun. */
.theme-icon-moon {
    display: none;
}

@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .theme-icon-sun {
        display: none;
    }

    :root:not([data-theme="light"]) .theme-icon-moon {
        display: block;
    }
}

:root[data-theme="dark"] .theme-icon-sun {
    display: none;
}

:root[data-theme="dark"] .theme-icon-moon {
    display: block;
}

/* An explicit light choice beats a dark OS, so restate the light pairing at
   higher specificity than the media query above. */
:root[data-theme="light"] .theme-icon-sun {
    display: block;
}

:root[data-theme="light"] .theme-icon-moon {
    display: none;
}

/* ---- Casing exceptions ----
   The page sets lowercase, which reads as a style choice on prose and proper
   nouns but as a mistake on acronyms — "cvpr" and "icml" are the two most
   credentialing strings on the site, and lowercased they look like text that
   lost its casing rather than text that chose it.

   Two mechanisms. These blocks are entirely technical metadata, so they opt
   out wholesale; .caps handles single acronyms sitting inside running prose,
   where a block rule would also un-lowercase the sentence around them. */
.project-stack,
.skill-items,
.caps {
    text-transform: none;
}

/* ---- Masthead over a divided two-column body ----
   Replaces the single centred column. The name is the page's only <h1> and
   the first thing read; the body below it is a wide main column and a
   sidebar, split by a vertical rule. Layout borrowed from a reference, not
   its typography — this stays on Cormorant with mono only for meta, the way
   the rest of the site already works. */
/* Padding outside the cap, max-width on the inner element — the same order
   `section` uses. With both on one element the gutter came out of the 1100px
   instead of sitting beside it, and the name landed 48px right of the column
   below it.
   The 8rem that used to be here was clearing the floating dock; with the nav
   in the masthead there is nothing fixed above it. */
#masthead {
    width: 100%;
    padding: 4rem var(--page-pad) 0;
}

/* Portrait beside the identity block, nav underneath the pair. Centred
   rather than baseline-aligned here: the photo is a square mass, so it wants
   to sit against the optical middle of the two text lines, not their first
   baseline. */
.masthead-top {
    display: flex;
    align-items: center;
    gap: 1.25rem;
}

#masthead-photo {
    width: 80px;
    height: 80px;
    border-radius: 50%;
    object-fit: cover;
    flex-shrink: 0;
    display: block;
    /* Shows through only while the image is still decoding */
    background: var(--surface);
}

/* One left-aligned stack: name, role, nav. The nav sat top-right until now,
   which put the only right-hand element on a page where everything else
   hangs off one left edge — and it needed a separate stacked layout under
   768px. This is the same layout at every width. */
.masthead-inner {
    width: 100%;
    max-width: var(--content-max);
    margin-inline: auto;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
}

/* ---- Masthead nav ----
   The site's only navigation now. It replaces both the fixed mobile bar with
   its hamburger drawer and the floating dock — two mechanisms that swapped at
   769px, neither of which the page needs when the links sit in the header.
   The current page is marked by ink weight alone, the way the reference does
   it; the dock's accent underline is gone with the dock. */
/* Nav and contact marks share one row: links left, marks hard right. The
   right half of this row was empty before, and the marks belong here rather
   than beside the name — at 47px the name should be the only thing claiming
   that much attention. */
.masthead-row {
    width: 100%;
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: center;
    gap: 1rem 1.5rem;
    margin-top: 1.6rem;
}

#masthead-nav {
    display: flex;
    flex-wrap: wrap;
    gap: 1.5rem;
    /* The 1.6rem that separated this from the identity block now sits on
       .masthead-row, which owns the spacing for both halves. */
}

#masthead-nav a {
    color: var(--text);
    text-decoration: none;
    font-family: var(--font-mono);
    font-size: 0.9375rem;
    line-height: 1.5;
    opacity: 0.6;
    /* Transparent on every link so the current page's underline appearing
       shifts nothing; only the colour changes. */
    border-bottom: 1px solid transparent;
    padding-bottom: 3px;
    transition: opacity 0.2s ease, border-color 0.2s ease;
}

#masthead-nav a:hover {
    border-bottom-color: var(--border-strong);
}

#masthead-nav a:hover {
    opacity: 1;
}

#masthead-nav a[aria-current="page"] {
    opacity: 1;
    border-bottom-color: var(--accent);
}

/* The name links home from every page, including index */
#masthead-name a {
    color: inherit;
    text-decoration: none;
}

#masthead-name {
    font-family: var(--font-display);
    font-size: clamp(2.375rem, 5vw, 3.4rem);
    font-weight: 400;
    line-height: 1.05;
    letter-spacing: -0.015em;
}

/* Mono, like the dates and the school lines — it reads as a standing fact
   rather than a sentence. */
/* Status, not decoration: sits on the role line so it is on every page
   without scrolling. No chip — it reads as the second half of the line,
   divided by a rule rather than boxed. The accent carries it instead, which
   is otherwise barely used anywhere. Not a link, so no underline, no hover. */
.open-badge {
    color: var(--accent);
    white-space: nowrap;
    opacity: 1;
}

/* Decorative, so it is hidden from screen readers — the two halves are
   already separate phrases to anyone listening. */
.role-sep {
    opacity: 0.3;
    margin: 0 0.2rem;
}

#masthead-role {
    font-family: var(--font-mono);
    font-size: 0.8125rem;
    line-height: 1.5;
    letter-spacing: 0.01em;
    opacity: 0.6;
    margin-top: 0.7rem;
}

/* Home used to run a narrower container than the other two — 860px against
   1100px — so the masthead jumped 120px sideways when you clicked from about
   to work. One width everywhere now; what varies is the measure of the prose
   inside the frame, not the frame itself. */
#home-grid {
    width: 100%;
    max-width: var(--content-max);
    margin-inline: auto;
    display: grid;
    grid-template-columns: minmax(0, 1fr) var(--aside-width);
    gap: var(--column-gap);
    align-items: start;
}

/* The divider. A border on the sidebar rather than a column of its own, so
   it runs exactly as tall as the sidebar's content. */
#home-aside {
    display: flex;
    flex-direction: column;
    gap: 1.5rem;
    border-left: 1px solid var(--border);
    padding-left: 2rem;
}

/* No max-width on the prose. It used to be capped at 512px inside a 676px
   column, which left a 164px band of nothing between the end of every line
   and the divider — the thing that made this page look loose next to the
   reference, where the paragraph runs the full column and the only space
   before the rule is the gutter. The column itself is now the measure. */
#about-text {
    hyphens: none;
}

/* ---- Sidebar blocks ----
   A small uppercase mono label over its content, blocks separated by rules.
   Uppercase at this size needs the tracking or it sets too tight. */
/* Lowercase, so the tracking comes off: 0.1em is what stops uppercase from
   colliding, and on lowercase it just looks loose. Weight and a slightly
   larger size carry the label instead of caps. */
/* Weight marks these as labels now that the family has a 600; the heavy
   tracking that stood in for it while Paragon offered one cut is mostly
   off again. */
.side-label {
    font-family: var(--font-mono);
    font-size: 0.75rem;
    font-weight: 500;
    line-height: 1.5;
    letter-spacing: 0.06em;
    opacity: 0.6;
    margin-bottom: 0.9rem;
}

.side-block + .side-block {
    border-top: 1px solid var(--border);
    padding-top: 1.5rem;
}


/* The sidebar's width is fixed while the prose column is 1fr, so every pixel
   lost comes off the reading column: at 820px the prose was down to 416px
   with the sidebar still at full width. Stack well before phone width.
   prose = viewport - 96 (gutters) - 300 (sidebar) - 64 (gap), and the frame
   stops shrinking once the viewport clears 936px, so below that the loss is
   all on the prose: 880px is where the column drops under 420. */
@media (max-width: 880px) {
    #home-grid {
        grid-template-columns: minmax(0, 1fr);
        gap: 2.5rem;
    }

    /* Stacked, the divider becomes a top rule so the sidebar still reads as
       a separate region rather than more of the same column. */
    #home-aside {
        border-left: 0;
        padding-left: 0;
        border-top: 1px solid var(--border);
        padding-top: 2.5rem;
    }
}

/* ---- Rotation deck ----
   Four cards in one slot with arrows. The deck is a grid with every card
   stacked in cell 1/1, so its height is the tallest card's and the sidebar
   does not jump as you page through — the usual carousel bug. */
.rot-deck {
    display: grid;
}

.rot-card {
    grid-area: 1 / 1;
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
}

/* Without JS the deck renders as a plain stack of all four, which is the
   honest fallback; the script adds this class before hiding anything. */
.rot-deck.is-deck .rot-card[aria-hidden="true"] {
    visibility: hidden;
    pointer-events: none;
}

.rot-kind {
    font-family: var(--font-mono);
    font-size: 0.75rem;
    line-height: 1.5;
    opacity: 0.6;
    margin-bottom: 0.35rem;
}

.rot-title {
    font-family: var(--font-mono);
    font-size: 0.875rem;
    line-height: 1.5;
}

.rot-by {
    font-family: var(--font-mono);
    font-size: 0.75rem;
    line-height: 1.5;
    opacity: 0.6;
}

.rot-nav {
    display: flex;
    align-items: center;
    gap: 1.9rem;
    margin-top: 1.1rem;
}

/* The glyph is 17px; the hit area is not. Padding takes the target to 44px
   and the negative margin pulls the row back to the glyph's own alignment,
   so it looks identical and is actually tappable on a phone. */
.rot-btn {
    font-family: inherit;
    font-size: 1.0625rem;
    line-height: 1;
    color: var(--text);
    background: none;
    border: 0;
    padding: 13.5px;
    margin: -13.5px;
    cursor: pointer;
    /* 0.6, not 0.45: at 0.45 these measured 2.92:1 on white and 3.97:1 on
       the dark ground, both short of the 4.5:1 that 14-17px text needs.
       0.6 is the single muted level the rest of the page uses. */
    opacity: 0.6;
    transition: opacity 0.2s ease;
}

.rot-btn:hover,
.rot-btn:focus-visible {
    opacity: 1;
}

.rot-count {
    font-size: 0.9rem;
    opacity: 0.6;
    font-variant-numeric: tabular-nums;
}

/* ---- About ----
   The masthead band that used to sit above this section is gone, along with
   the portrait, the <h1> name and the social chips it carried. .social-chips
   went with it and with the footer, which were its only two uses.

   One centred column now, not prose against a 260px sidebar — and the text
   in it is left-aligned. */


/* 1.65 leading pushed consecutive lines far enough apart that a paragraph
   read as separate floating lines rather than a block. */
#about-text p {
    font-size: 1.0625rem;
    line-height: 1.55;
    margin-bottom: 1.25rem;
}

/* Same treatment as the links in the work entries, and for the same reason:
   there is no global rule for an <a>, so an inline link in prose renders at
   the user agent's #0000EE until something claims it. The one link in here is
   the hiring address, which is the last place on the site that should look
   unstyled. */
#about-text a {
    color: var(--accent);
    text-decoration: underline;
    text-underline-offset: 2px;
    transition: opacity 0.2s;
}

#about-text a:hover {
    opacity: 0.65;
}

/* The Rutgers degree is long enough to take three lines in this column, and
   the default greedy wrap leaves "technology" alone on the last one. Balance
   evens the lines out instead; browsers that do not support it just wrap the
   old way. */
#about-education li {
    text-wrap: balance;
}

/* ---- Contact ----
   A label/value ledger, centred as a block: the labels are right-aligned
   against the values, so the eye reads down one edge instead of hunting for
   a line start that moves, which is what makes a centred paragraph slower.
   `justify-content: center` on the grid centres the pair of columns without
   centring anything inside them. */
/* ---- Label row over a card ----
   The reference's "On GitHub ....... @handle" row above its contribution
   graph. space-between so a right-hand item can be added later; with only
   the label it reads as a flush-left caption. The <h3> that used to live
   inside each card was removed in favour of this, so the heading is not
   set twice. */


/* ---- Highlights ----
   Doubles what the main column holds. The marker is the accent, which until
   now only appeared on the work page — this carries it onto home. em dash
   rather than a bullet: it reads as an aside in a serif, where a round
   bullet reads as a UI list. */
.hl {
    margin-top: 2.5rem;
}

.hl-list {
    list-style: none;
}

.hl-list li {
    position: relative;
    padding-left: 1.4rem;
    font-size: 1.0625rem;
    line-height: 1.55;
    margin-bottom: 0.8rem;
}

.hl-list li:last-child {
    margin-bottom: 0;
}

.hl-list li::before {
    content: "\2022";
    position: absolute;
    left: 0;
    color: var(--accent);
}

/* <strong> is visually inert here, and deliberately so: Paragon has no bold
   cut, the palette is black and white, and italic was rejected. Size, colour
   and weight are all unavailable, so nothing is left to mark it with. The
   rule stays to pin the weight — without it the browser fakes a bold — and
   the tag keeps its meaning for screen readers. */
.hl-list strong {
    font-weight: 600;
}

.hl-list a {
    color: var(--text);
    text-decoration: none;
    border-bottom: 1px solid var(--border-strong);
}

.hl-list a:hover {
    color: var(--accent);
    border-bottom-color: var(--accent);
}

/* ---- Contact: an outbound link, a rule, then the marks ----
   Replaces the contact <dl>. The rule is a border rather than an <hr> so it
   spans the column exactly and carries no default margin. */
/* Holds the resume button alone now that the marks have moved into the
   main column. align-items keeps the box at its content width rather than
   stretching it across the sidebar. */
#contact {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
}


/* ---- Social marks ----
   Under the bio in the main column. They were in a footer that carried them
   to all three pages; with the footer gone they appear once, on the page
   someone actually arrives at.

   Full-strength ink, no opacity. These were set at 0.7 to sit back as
   footer furniture — in the body of the page that just reads as a greyed
   out, half-disabled control. */
.social-row {
    display: flex;
    flex-wrap: wrap;
    gap: 1.25rem;
}

.social-row a {
    display: inline-flex;
    align-items: center;
    color: var(--text);
    text-decoration: none;
    opacity: 1;
}

.social-row a:hover {
    opacity: 0.6;
}

/* currentColor, so the marks take the link's ink */
.social-row svg {
    width: 19px;
    height: 19px;
    flex-shrink: 0;
}

/* The one boxed control on the site. It can carry a border because
   the masthead's plain resume link. inline-flex, not flex: block-level it
   stretches the sidebar's full width and reads as a slab. */
#resume-button {
    display: inline-flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.75rem 1.1rem;
    border: 1px solid var(--border-strong);
    color: var(--text);
    text-decoration: none;
    font-family: var(--font-mono);
    font-size: 0.875rem;
    line-height: 1.5;
    transition: background 0.2s, border-color 0.2s;
}

#resume-button:hover {
    background: var(--surface);
    border-color: var(--text);
}

#resume-button svg {
    width: 16px;
    height: 16px;
    flex-shrink: 0;
}

/* Unbulleted — at this size a marker column costs more than it clarifies,
   and the heading already frames the lines as a list. */
.aside-list {
    list-style: none;
}

.aside-list li {
    font-family: var(--font-mono);
    font-size: 0.875rem;
    line-height: 1.5;
    margin-bottom: 0.9rem;
}

.aside-list li:last-child {
    margin-bottom: 0;
}

/* Secondary line inside a list row — school and year under a degree. Same
   treatment as .aside-track-artist, which does this for the artist name. */
.aside-meta {
    font-family: var(--font-mono);
    display: block;
    font-size: 0.8125rem;
    line-height: 1.5;
    opacity: 0.6;
}


/* Hidden until the fetch resolves, so a half-built row never flashes. */
/* Text only now, matching the other three cards. Still an <a>, so the track
   links out to Spotify — the artwork went, the link did not. */
.aside-track {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    color: var(--text);
    text-decoration: none;
}

.aside-track:hover .aside-track-name {
    text-decoration: underline;
    text-underline-offset: 2px;
}

.aside-track.loaded {
    opacity: 1;
}

.aside-track-name {
    display: block;
    font-family: var(--font-mono);
    font-size: 0.875rem;
    line-height: 1.5;
    /* A long title would otherwise push the row past the 260px column */
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.aside-track:hover .aside-track-name {
    text-decoration: underline;
    text-underline-offset: 3px;
}

.aside-track-artist {
    font-family: var(--font-mono);
    display: block;
    font-size: 0.75rem;
    line-height: 1.5;
    opacity: 0.6;
}

/* No surface, no radius, no padding. These were lifted cards; now the
   .side-label above each one is the only thing marking it as a group, and
   content sits flush on the column's left edge like everything else. The
   rules between the list rows do the separating the box used to.
   .aside-module h3 is gone too — each card's heading became that label. */

#projects {
    display: flex;
    flex-direction: column;
    align-items: stretch;
}

/* Every one of these is lowercase, which is the whole reason for this face.
   400, not 500: the masthead name sets at 47px without extra weight, so a
   32px heading asking for some was the one inconsistency in the system.
   Size carries the big type; weight is reserved for marking small type. */
/* Mono from here down. The rule across the site: serif for sentences and
   the name, mono for anything that titles or labels. Headings are structure,
   not prose — and holding the serif back to the masthead alone makes the
   name a wordmark rather than just the biggest text on the page.
   Sized down from 2rem: mono's x-height runs far larger than Garamond's, so
   the same nominal size reads heavier. */
section > h2 {
    font-family: var(--font-mono);
    font-size: 1.4375rem;
    font-weight: 500;
    line-height: 1.2;
    letter-spacing: -0.015em;
    margin-bottom: 0.5rem;
}


#experience {
    display: flex;
    flex-direction: column;
    align-items: stretch;
}


/* ---- Experience: ledger rows ----
   Amrit's pattern — role and company on the left, dates right-aligned, no
   rail, no dots and no dividers. The content column is capped well short of
   the container so the eye can still bridge the gap to the date unaided. */
.timeline-entry {
    display: grid;
    /* The content column is capped rather than 1fr. Left at 1fr the date was
       pinned to the far right at ~1232px while the description ended near
       750px, leaving ~480px of dead middle in every row. Capping it pulls the
       date back to sit with the text, and the section still spans full width
       — only the row's content is bounded, same idea as --prose. */
    grid-template-columns: 34px minmax(0, 800px) max-content;
    grid-template-areas: "logo content date";
    column-gap: 2rem;
    align-items: baseline;
    /* No dividers — spacing separates the entries instead. The hover tint is
       now the only thing marking a row as a discrete block. */
    padding: 1.6rem 0;
    transition: background 0.2s;
}

.timeline-entry:hover {
    background: var(--surface);
}

/* The ledger is deliberately flat: five rows, same weight, dates doing the
   ranking. A promoted row lived here — first keyed to the "current" role,
   then to the MIT research row — but ranking belongs in the home page's
   highlights list, which is meant to be ordered. A chronology is not, and
   emphasising one row here said the same thing twice while costing the
   clean reverse-chronological read. */

.timeline-date {
    font-family: var(--font-mono);
    grid-area: date;
    font-size: 0.8125rem;
    line-height: 1.5;
    white-space: nowrap;
    opacity: 0.6;
}

/* Company mark. Each source is a square avatar whose artwork is an inscribed
   circle or a full-bleed colour, so the 50% radius clips the corners away and
   leaves the mark itself untouched. The logos keep their own colours in both
   themes; the border is what separates the dark ones from a dark page. */
.timeline-logo {
    grid-area: logo;
    align-self: start;
    margin-top: 0.1rem;
    width: 34px;
    height: 34px;
    overflow: hidden;
    border: 1px solid var(--border);
    border-radius: 50%;
    /* Shows through only while a lazy image is still loading */
    background: var(--surface);
}

.timeline-logo img {
    width: 100%;
    height: 100%;
    /* cover, not contain — these are square, so cover fills the circle
       edge to edge instead of leaving the source's white corners visible. */
    object-fit: cover;
    display: block;
}

.timeline-content {
    grid-area: content;
}

/* The role is the thing being scanned, so it gets the scale. At 1rem it was
   the same size as the description beneath it and the whole section read
   flat — five equal rows regardless of what each one was. */
.timeline-content h3 {
    font-family: var(--font-mono);
    font-size: 1rem;
    font-weight: 500;
    line-height: 1.35;
    letter-spacing: -0.01em;
}

.timeline-company {
    display: block;
    font-size: 1rem;
    font-style: italic;
    line-height: 1.4;
    opacity: 0.8;
    margin-bottom: 0.65rem;
}

/* Inline links in entry descriptions — underlined, never browser blue */
.timeline-content a {
    color: var(--accent);
    text-decoration: underline;
    text-underline-offset: 2px;
    transition: opacity 0.2s;
}

.timeline-content a:hover {
    opacity: 0.65;
}

/* Measured even though the row is full-bleed */
.timeline-content p {
    font-size: 1.0625rem;
    line-height: 1.65;
    opacity: 0.75;
    max-width: 88ch;
}


/* Narrow desktop: tighten the nav so name + links + socials still fit on one line */

/* Mobile */
@media (max-width: 768px) {

    /* One token instead of a padding override per section — the unified
       `section` rule then covers the hero too, which previously would have
       inherited the 3rem desktop gutter on a 375px screen. */
    :root {
        --page-pad: 1.25rem;
    }

    section {
        padding: 3.5rem var(--page-pad);
    }


    /* The min-height: 100dvh that used to be here is gone. It dates from the
       one-page design, where every section filled a screen. With the site
       split across three documents it only pads short sections out with
       nothing: Contact's four rows were given a full 812px screen, which put
       home at 2,436px for about 1,100px of content. Sections size to their
       content now, as they already did on desktop. */
    section,
    #projects,
    #experience {
        height: auto;
        overflow: visible;
    }


    #masthead {
        padding-top: 2.5rem;
    }

    /* Re-measured a third time, for EB Garamond. The name runs 6.44px per
       pixel of font-size — narrower than Paragon's 7.94 — but the scaled-up
       2.125rem still asked for 219px against the 212px a 44px portrait and a
       0.75rem gap leave at 375px, so it wrapped. 1.9375rem needs 200px and
       clears with 12px to spare. */
    .masthead-top {
        gap: 0.75rem;
    }

    #masthead-photo {
        width: 44px;
        height: 44px;
    }

    #masthead-name {
        font-size: 1.9375rem;
    }


    /* No room for a right-hand date column — stack it above the role, with
       the logo spanning both rows beside them */
    .timeline-entry {
        grid-template-columns: 30px minmax(0, 1fr);
        grid-template-areas:
            "logo date"
            "logo content";
        column-gap: 0.85rem;
        row-gap: 0.2rem;
        padding: 1.25rem 0;
    }

    .timeline-logo {
        width: 30px;
        height: 30px;
        font-size: 0.6875rem;
    }


    


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

}
/* ---- Skills ----
   Typographic, like the Experience ledger — no logo grid (brand colours
   fight the palette and half the marks are unreadable at this size) and no
   proficiency bars (unverifiable, and self-graded). Label plus a run of
   names, which claims nothing it cannot back. */
#skills {
    display: flex;
    flex-direction: column;
    align-items: stretch;
}


/* ---- Projects: ledger rows ----
   The same object as the Experience ledger: content left, one muted word
   right-aligned, no surface and no dividers. What this replaces drew five
   kinds of box on a page whose other two sections draw none — a tinted
   panel per project at an 18px radius, a bordered well around each
   screenshot with its own 12px radius and a forced 16/10 letterbox, pill
   chips for the stack and outlined buttons for the links.

   The "01 / 03" counters went with them. Numbering is only information when
   the items are a sequence, and three projects are not one. */
/* Three tiers, following the reference: one full-width card with its
   screenshot alongside, a two-up grid under it, then plain rows. Card chrome
   was stripped from this page once before — this brings back a border and a
   small radius, which is the price of the tiering reading as tiers. */
.sheet-featured,
.sheet-card {
    border: 1px solid var(--border);
    border-radius: 6px;
    padding: 1.5rem;
    transition: border-color 0.2s, background 0.2s;
}

.sheet-featured:hover,
.sheet-card:hover {
    background: var(--surface);
    border-color: var(--border-strong);
}

/* Text left, screenshot right. */
.sheet-featured {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    column-gap: 2rem;
    align-items: start;
}

.sheet-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 1.25rem;
    margin-top: 1.25rem;
}

/* Column, so the stack and links sit at the foot however tall the text is. */
.sheet-card {
    display: flex;
    flex-direction: column;
}

.sheet-card .sheet-figure {
    margin: 0.9rem 0;
}

/* The connect 4 shot is portrait where the others are landscape, so at card
   width it would run ~470px tall against their ~250 and stretch its whole
   grid row. Capped and cropped from the top, which keeps the board and
   trims the chart beneath it. */
.sheet-card .sheet-figure img {
    max-height: 300px;
    object-fit: cover;
    object-position: top;
}

/* Pushes stack and links down when descriptions differ in length, so the
   two cards' footers line up. */
.sheet-card .project-stack {
    margin-top: auto;
    padding-top: 0.9rem;
}

/* Last tier: no border, no image, just a rule between rows. */
.sheet-rows {
    list-style: none;
}

.sheet-row {
    border-top: 1px solid var(--border);
    padding: 1.1rem 0;
}

/* The row spans the full frame, so without a cap its description ran 105
   characters a line against the 45-75 the cards sit in. The measure is
   capped rather than the row, so the rule above it still spans. */
.sheet-row > p {
    max-width: 62ch;
}

.sheet-row:last-child {
    border-bottom: 1px solid var(--border);
}

/* No well, no padding, no forced ratio: the screenshot keeps its own
   proportions behind a single hairline. */
.sheet-figure {
    margin: 0;
    min-width: 0;
}

.sheet-figure img {
    width: 100%;
    height: auto;
    display: block;
    border: 1px solid var(--border);
}

.sheet-text {
    min-width: 0;
}

/* Group heading over each run of projects. Borrowed from the reference,
   which sorts its work under labels rather than running one flat list.
   Same treatment as the sidebar labels on the home page. */
.group-label {
    font-family: var(--font-mono);
    font-size: 0.75rem;
    font-weight: 500;
    line-height: 1.5;
    letter-spacing: 0.06em;
    opacity: 0.6;
    margin-top: 2.5rem;
    margin-bottom: 0.25rem;
}

.group-label:first-of-type {
    margin-top: 1rem;
}

/* Title and status on one line. The status used to be a column of its own
   on the far right, which left it stranded from the thing it described. */
.sheet-head {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 0.6rem;
    margin-bottom: 0.3rem;
}

/* Matches .timeline-content h3 — same ledger, same title treatment.
   Scoped to .sheet-head, not .sheet-text: only the featured card kept a
   .sheet-text wrapper when the tiers went in, so the grid cards and the row
   were falling through to the browser default — 16px/700 against the 20px/600
   these are meant to be. */
.sheet-head h4 {
    font-family: var(--font-mono);
    font-size: 1rem;
    font-weight: 500;
    line-height: 1.35;
    letter-spacing: -0.01em;
}

/* Same reason: the descriptions in cards and rows had no rule at all and
   were rendering at 16px with line-height: normal. */
.sheet-featured .sheet-text > p,
.sheet-card > p,
.sheet-row > p {
    font-size: 1.0625rem;
    line-height: 1.55;
    margin-bottom: 0.65rem;
}

/* A chip now rather than a column: next to the title it reads as that
   project's state, which is what it always meant. */
.sheet-status {
    font-family: var(--font-mono);
    font-size: 0.6875rem;
    line-height: 1.4;
    white-space: nowrap;
    padding: 0.15rem 0.5rem;
    border: 1px solid var(--border);
    border-radius: 2px;
    opacity: 0.7;
    position: relative;
    top: -0.1em;
}

/* Plain separated text where there were bordered pills. */
.project-stack {
    font-family: var(--font-mono);
    font-size: 0.75rem;
    line-height: 1.5;
    opacity: 0.6;
    margin-bottom: 0.5rem;
}

.project-stack:empty {
    display: none;
}

.project-stack span:not(:last-child)::after {
    content: " \00b7 ";
}

/* Underlined, like every other link on the site. Was a bordered button that
   inverted to solid ink on hover. */
.project-links {
    display: flex;
    gap: 1.25rem;
    flex-wrap: wrap;
}

.project-link {
    font-family: var(--font-mono);
    font-size: 0.875rem;
    color: var(--text);
    /* Underline moves to the label span: on an inline-flex link the rule
       would run under the icon too. */
    text-decoration: none;
    display: inline-flex;
    align-items: center;
    gap: 0.4em;
    transition: opacity 0.2s;
}

.project-link > span {
    text-decoration: underline;
    text-underline-offset: 2px;
}

/* A globe for a live deployment, angle brackets for source. Sized in em so
   the mark tracks the label rather than needing its own breakpoint. */
.link-mark {
    width: 1em;
    height: 1em;
    flex: 0 0 auto;
    fill: none;
    stroke: currentColor;
    opacity: 0.75;
}

.project-link:hover {
    opacity: 0.6;
}

/* Marks the link as leaving the site. Kept outside the underline by sitting
   in ::after with its own margin, so the arrow is not underlined with the
   text. */
.project-link[target="_blank"]::after {
    content: "\2197";
    display: inline-block;
    margin-left: 0.25em;
    text-decoration: none;
    font-size: 0.85em;
    opacity: 0.7;
}

/* Must follow the arrow rule, not precede it: :has() takes the specificity
   of its argument, so this and the [target] rule both land on (0,2,1) and
   source order is what decides. The mark already says where the link goes. */
.project-link:has(.link-mark)::after {
    content: none;
}


@media (max-width: 768px) {

    /* Both tiers collapse to one column: side by side, neither the featured
       card's two halves nor the grid's two cards leave the text enough
       width. */
    .sheet-featured,
    .sheet-grid {
        grid-template-columns: minmax(0, 1fr);
    }

    .sheet-featured {
        row-gap: 1.25rem;
    }

    .sheet-featured,
    .sheet-card {
        padding: 1.1rem;
    }

    .sheet-figure {
        max-width: 400px;
    }

}

/* The sprite itself is never painted — only its symbols, via <use>. */
#icon-sprite {
    display: none;
}

/* ---- Skills ----
   Grouped rows, not an icon grid. The grid had three of fourteen tools with
   no logo, so those cells sat misaligned against their neighbours; 14 items
   across 6 columns orphaned two onto a third row; and it was centred on a
   page where everything else hangs off one left edge.

   Grouping costs the logos and buys something better: the categories say how
   the stack is organised, which is a judgement a flat list cannot make. */
.skill-groups {
    display: flex;
    flex-direction: column;
    gap: 1.1rem;
    width: 100%;
    max-width: var(--content-max);
    margin-inline: auto;
}

/* Fixed label column, so every row's items start at the same x. */
.skill-row {
    display: grid;
    grid-template-columns: 7rem minmax(0, 1fr);
    gap: 1rem;
    align-items: baseline;
    padding-top: 1.1rem;
    border-top: 1px solid var(--border);
}

.skill-label {
    font-family: var(--font-mono);
    font-size: 0.75rem;
    font-weight: 500;
    line-height: 1.5;
    letter-spacing: 0.06em;
    opacity: 0.6;
}

.skill-items {
    list-style: none;
    display: flex;
    flex-wrap: wrap;
    /* Column gap halved from 1rem now that a dot sits between items: the
       separator plus the old gap read as too much air. */
    gap: 0.35rem 0.5rem;
    font-family: var(--font-mono);
    font-size: 0.875rem;
    line-height: 1.5;
}

/* Same middle dot the project stacks use, rather than the katakana one: this
   is set in JetBrains Mono, which has no CJK coverage, so U+30FB would fall
   back to a system font and sit at a different width and baseline from
   everything around it. The margin matches the flex gap so the dot lands
   centred between the two items instead of hugging the one before it. */
.skill-items li:not(:last-child)::after {
    content: "\00b7";
    margin-left: 0.5rem;
    opacity: 0.4;
}

/* Narrow: a 7rem label column leaves too little for the items, so the label
   moves onto its own line above them. */
@media (max-width: 600px) {
    .skill-row {
        grid-template-columns: minmax(0, 1fr);
        gap: 0.5rem;
    }
}

/* ---- Hover ----
   Nothing on the page responded to the pointer before this. Kept to tone
   shifts rather than movement, so the layout stays still. */
.sheet {
    transition: background 0.25s ease;
}

.sheet:hover {
    background: var(--surface-strong);
}

.sheet-figure img {
    transition: opacity 0.25s ease;
    opacity: 0.92;
}

.sheet:hover .sheet-figure img {
    opacity: 1;
}

.timeline-entry {
    transition: opacity 0.2s ease;
}

.timeline:hover .timeline-entry:not(:hover) {
    opacity: 0.6;
}

.project-link,
.social-row a,
.aside-track {
    transition: background 0.2s ease, color 0.2s ease, opacity 0.2s ease;
}

/* ---- Separate pages ----
   The site is three documents now: index (about), work.html (experience) and
   projects.html (projects, then skills). The bar, the dock and the <head>
   metadata are duplicated in all three — GitHub Pages has no includes.

   All three pages open on the masthead now, so the first section no longer
   has to clear anything fixed — the 8rem here was dock clearance. */
section:first-of-type {
    padding-top: 2.5rem;
}


@media (max-width: 768px) {
    section:first-of-type {
        padding-top: 2rem;
    }
}

/* Back to a real bold now that the family has one. */
strong,
b {
    font-weight: 600;
}


/* ---- 404 ----
   Served by GitHub Pages for any unmatched path. It borrows the masthead and
   the h2 rule from the rest of the site, so all it needs of its own is the
   prose measure and the row of ways out. */
#notfound p {
    width: 100%;
    max-width: var(--content-max);
    margin-inline: auto;
    font-size: 1.0625rem;
    line-height: 1.55;
    margin-bottom: 1.5rem;
}

.notfound-nav {
    display: flex;
    flex-wrap: wrap;
    gap: 1.5rem;
    width: 100%;
    max-width: var(--content-max);
    margin-inline: auto;
    font-family: var(--font-mono);
    font-size: 0.9375rem;
}

/* These are the whole point of the page, so they take the accent rather than
   the masthead nav's dimmed ink. Without a rule they fall back to the user
   agent's blue, which is what shipped the first time. */
.notfound-nav a {
    color: var(--accent);
    text-decoration: underline;
    text-underline-offset: 3px;
    transition: opacity 0.2s;
}

.notfound-nav a:hover {
    opacity: 0.65;
}
