:root { 
    --background-color: #f4f4f4;
    --default-color: #333;
    --heading-color: #fff;
    --accent-color: #666;
    --surface-color: #1c1c1c;
    --contrast-color: #ddd;
    --link-color: #0d1d91;

    /* Content column — see .page-col */
    --content-width: 900px;
    --content-gutter-min: 1rem;
    --content-gutter-max: 2rem;

    /* Hero — see the Hero Section block. Everything scales off --hero-t,
       which ramps 1 -> 0 between these two viewport widths. */
    --hero-font: 'EB Garamond', Georgia, 'Times New Roman', serif;
    --hero-w-full: 620px;    /* --hero-t = 1 at and above this */
    --hero-w-tight: 432px;   /* --hero-t = 0 here; also the stack threshold */
    --hero-img-max: 220px;
    --hero-img-min: 150px;
    --hero-gap: 2rem;
    --hero-gap-min: 1.25rem;
  }

/*-------------------------------------------------------------------
# Generic Settings
--------------------------------------------------------------------*/

body {
    color: var(--default-color);
    background-color: var(--background-color);
    font-family: 'Inter', sans-serif;
    overflow-x: hidden;
}

/* AOS's fade-left/fade-right animate via translate3d. Transforms don't affect
   layout but do extend the scrollable overflow area, which on a narrow screen
   makes the page wider than the viewport — the browser then zooms out to fit
   and drags the position:fixed hamburger off-screen. Clipping here contains
   the animation without changing how it looks. */
main {
    overflow-x: clip;
}

strong, b {
  font-weight: 500;
}

/*-------------------------------------------------------------------
# Links
--------------------------------------------------------------------*/
a:link,
a:visited,
a:hover,
a:active {
  text-decoration: none;
  color: var(--link-color);
}
a:hover {
  color: color-mix(in srgb, var(--link-color), transparent 25%);
  text-decoration: underline;
  transition: 3s ease-in;
}

/* Header & Footer links */
header a:link,
header a:visited,
header a:hover,
header a:active,
footer a:link,
footer a:visited,
footer a:hover,
footer a:active,
.project-row a:link,
.project-row a:visited,
.project-row a:hover,
.timeline-event a:active,
.timeline-event a:link,
.timeline-event a:visited,
.timeline-event a:hover,
.timeline-event a:active {
  text-decoration: none;
  color: var(--accent-color);
}
header a:hover,
footer a:hover,
.project-row a:hover,
.timeline-event a:hover {
  color: color-mix(in srgb, var(--accent-color), transparent 25%);
}

/*-------------------------------------------------------------------
# Default Content Styles
--------------------------------------------------------------------*/

/* Default section styling */
main section {
  padding: 2.5rem 0 0 0;
}

/* The single content column for every page.

   Width behaves in three regimes as the viewport narrows:
     1. wide      — capped at --content-width, centred, gutter is the leftover
     2. shrinking — 4vw gutter shrinks proportionally with the display
     3. narrow    — gutter floors at --content-gutter-min, text reflows/wraps
                    from there and never reaches the screen edge

   clamp() covers all three without media queries. */
.page-col {
  width: 100%;
  max-width: calc(var(--content-width) + 2 * var(--content-gutter-max));
  margin-inline: auto;
  padding-inline: clamp(var(--content-gutter-min), 4vw, var(--content-gutter-max));
  box-sizing: border-box;
}

/* Default heading style */
main h1 {
  font-size: 1.5rem;
  font-weight: 400;
  text-align: left;
  margin: 0 0 1.25rem 0;
}

.resources-page h2 {
  font-size: 1.3rem;
  font-weight: 400;
  color: var(--default-color);
  margin-top: 1.5rem;
}

.resources-page code {
  font-family: SFMono-Regular, Menlo, Consolas, monospace;
  color: var(--default-color);
  background-color: color-mix(in srgb, var(--default-color), transparent 92%);
  padding: 0.1em 0.35em;
  border-radius: 0.25rem;
}

/* Default paragraph style */
main p {
  text-align: justify;
  text-justify: inter-word;
}

/*-------------------------------------------------------------------
# Header — hamburger + dropdown menu
--------------------------------------------------------------------*/

.menu-toggle {
  position: fixed;
  top: 1.25rem;
  right: 1.5rem;
  z-index: 1001;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: none;
  background: var(--background-color);
  box-shadow: 0px 0 18px rgba(0, 0, 0, 0.1);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

.menu-toggle .bar {
  position: absolute;
  width: 20px;
  height: 2px;
  background: var(--default-color);
  border-radius: 2px;
  transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.2s ease, top 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}

.menu-toggle .bar:nth-child(1) { top: 15px; }
.menu-toggle .bar:nth-child(2) { top: 22px; }
.menu-toggle .bar:nth-child(3) { top: 29px; }

.menu-toggle.open .bar:nth-child(1) { top: 22px; transform: rotate(45deg); }
.menu-toggle.open .bar:nth-child(2) { opacity: 0; }
.menu-toggle.open .bar:nth-child(3) { top: 22px; transform: rotate(-45deg); }

.nav-dropdown {
  position: fixed;
  top: 1.25rem;
  right: 1.5rem;
  z-index: 1000;
  background: var(--background-color);
  box-shadow: 0px 0 18px rgba(0, 0, 0, 0.15);
  border-radius: 12px;
  padding: 44px 0 8px 0;
  min-width: 180px;
  transform-origin: top right;
  transform: scale(0.9);
  opacity: 0;
  visibility: hidden;
  transition: transform 0.22s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.18s ease, visibility 0s linear 0.22s;
}

.nav-dropdown.open {
  transform: scale(1);
  opacity: 1;
  visibility: visible;
  transition: transform 0.22s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.18s ease, visibility 0s linear 0s;
}

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

.navmenu li a {
    display: block;
    padding: 0.55rem 1.5rem;
    color: var(--accent-color);
    font-weight: 450;
    font-size: 0.95rem;
    white-space: nowrap;
    transition: color 0.3s ease-in-out;
}

.navmenu li a:hover,
.navmenu li a.active {
    color: color-mix(in srgb, var(--accent-color), transparent 25%);
    text-decoration: none;
}

.navmenu li a .label {
    position: relative;
    display: inline-block;
}

.navmenu li a .label::before {
    content: "";
    position: absolute;
    width: 0px;
    height: 1.5px;
    bottom: -4px;
    left: 0;
    background-color: currentColor;
    transition: width 0.3s ease-in-out;
}

.navmenu li a:hover .label::before,
.navmenu li a.active .label::before {
    width: 100%;
}

.nav-dropdown .btn-cv-row {
  padding: 0.5rem 1.5rem 0.25rem 1.5rem;
}

.nav-dropdown .btn-cv,
.nav-dropdown .btn-cv:focus {
  display: block;
  color: var(--heading-color);
  background: var(--accent-color);
  font-size: 14px;
  padding: 8px 13px;
  border-radius: 4px;
  transition: 0.3s;
  font-weight: 450;
  text-align: center;
}

.nav-dropdown .btn-cv:hover,
.nav-dropdown .btn-cv:focus:hover {
  background: color-mix(in srgb, var(--accent-color), transparent 15%);
}

.nav-dropdown .btn-cv:active,
.nav-dropdown .btn-cv:focus:active {
    color: var(--surface-color);
    background: var(--accent-color);
}

/*-------------------------------------------------------------------
# Hero Section
--------------------------------------------------------------------*/

/* The hero shrinks in four stages as the viewport narrows:
     1. full      — everything at full size, text beside the image, pair centred
     2. gutters   — .page-col padding shrinks 2rem -> 1rem
     3. lockstep  — image AND text shrink together, driven by one scale factor
     4. stacked   — below the threshold the text drops under the image,
                    left-aligned with the block centred, still shrinking

   Image size, type size and vertical rhythm are all derived from --hero-t, a
   single 0..1 ramp, so they move in lockstep rather than on separate curves.
   --hero-t is 1 at --hero-w-full and 0 at --hero-w-tight, clamped at both
   ends. clamp() does the clamping; the division yields a unitless number.

   While the two sit side by side, the text column is sized to match the image
   height exactly, so neither spills past the other under align-items:center.
   The name/li line-height (1.25) and the li margin (0.16rem + 0.28rem * t) are
   solved against the image width formula and hold to within a pixel across the
   whole ramp — changing any one of them means re-solving the others. */
.hero {
  --hero-t: clamp(0, (100vw - var(--hero-w-tight)) / (var(--hero-w-full) - var(--hero-w-tight)), 1);

  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: center;
  gap: calc(var(--hero-gap-min) + (var(--hero-gap) - var(--hero-gap-min)) * var(--hero-t));
  margin: 0;
}

/* min-width:0 so the flex item can shrink below the image's intrinsic
   width (409px), which would otherwise force the page wider than a phone
   viewport and carry the position:fixed hamburger off-screen with it. */
.hero .img-container {
  flex: 0 1 auto;
  width: calc(var(--hero-img-min) + (var(--hero-img-max) - var(--hero-img-min)) * var(--hero-t));
  min-width: 0;
}

.hero .img-container img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 5%;
}

.hero .hero-text {
  display: flex;
  flex-direction: column;
  justify-content: center;
  flex: 0 1 auto;
  min-width: 0;
}

.hero .hero-name {
  text-align: left;
  margin: 0 0 calc(0.2rem + 0.3rem * var(--hero-t)) 0;
  font-family: var(--hero-font);
  font-style: normal;
  font-weight: 500;
  font-size: calc(1.3rem + 0.3rem * var(--hero-t));
  line-height: 1.25;
  letter-spacing: 0.01em;
  color: var(--default-color);
  white-space: nowrap;
}

.hero ul {
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    list-style: none;
}

.hero ul>li {
    text-align: left;
    font-style: italic;
    font-weight: 400;
    margin: calc(0.16rem + 0.28rem * var(--hero-t)) 0;
    font-family: var(--hero-font);
    font-size: calc(0.95rem + 0.15rem * var(--hero-t));
    line-height: 1.25;
    white-space: nowrap;
}

/* Stage 4. At the threshold --hero-t has reached 0, so image and text are
   already at their smallest and the switch to a column introduces no jump in
   either. Below it the pair simply reflows; --hero-t stays clamped at 0. */
@media screen and (max-width: 432px) {
  .hero {
    /* --hero-t stays clamped at 0 here: type size and rhythm are already at
       their minimum when we cross the threshold and must not spring back.
       A separate ramp drives the image alone, since below the threshold it no
       longer competes with the text for horizontal room. --hero-img-t is 1 at
       the threshold, so the image width is continuous across the switch. */
    --hero-img-t: clamp(0, (100vw - 300px) / 132px, 1);

    flex-direction: column;
    gap: 1.25rem;
  }
  .hero .img-container {
    width: calc(var(--hero-img-min) - 32px + 32px * var(--hero-img-t));
  }
  .hero .hero-text {
    align-items: flex-start;
  }
}

/*-------------------------------------------------------------------
# Footer
--------------------------------------------------------------------*/
footer {
    position: relative;
    text-align: center;
    padding: 1rem 0 1rem 0;
    margin: 3rem auto 0 auto;
    color: #999;
    border-top: 1px solid color-mix(in srgb, var(--default-color), transparent 88%);
    font-size: 0.9rem;
    bottom: 0;
    background-color: #f4f4f4;
}

#social-links {
    display: flex;
    flex-direction: row;
    justify-content: space-around;
}

#social-links i {
    margin: .5rem;
}

/*-------------------------------------------------------------------
# Projects
--------------------------------------------------------------------*/

/* Width comes from the enclosing .page-col. */
.project-list {
  width: 100%;
  display: flex;
  flex-direction: column;
}

.project-row {
  display: flex;
  flex-direction: row;
  align-items: flex-start;
  gap: 1.5rem;
  padding: 2rem 0;
}

.project-row:first-child {
  padding-top: 0.5rem;
}

.project-thumb {
  flex: 0 0 96px;
  width: 96px;
  height: 96px;
  border-radius: 6px;
  overflow: hidden;
  background: color-mix(in srgb, var(--default-color), transparent 94%);
}

.thumbnail-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.thumbnail-placeholder {
  width: 100%;
  height: 100%;
  align-items: center;
  justify-content: center;
  color: var(--accent-color);
  font-size: 0.65rem;
  text-align: center;
  padding: 0.5rem;
}

.project-body {
  flex: 1 1 0;
  min-width: 0;
}

.project-heading {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
}

.project-title {
  font-size: 1.15rem;
  font-weight: 500;
  margin: 0;
  color: var(--default-color);
  text-align: left;
  border-bottom: none;
}

.project-date {
  font-size: 0.8rem;
  color: var(--accent-color);
  white-space: nowrap;
  font-weight: 400;
}

.project-tags {
  font-style: normal;
  font-size: 0.75rem;
  color: var(--accent-color);
  font-weight: 400;
  margin: 0.3rem 0 0.6rem 0;
}

.project-description {
  font-size: 0.9rem;
  color: var(--default-color);
  line-height: 1.55;
  margin: 0 0 0.75rem 0;
}

.project-links {
  gap: 1.1rem;
  flex-wrap: wrap;
}

.project-link {
  font-size: 0.8rem;
  font-weight: 500;
  color: var(--accent-color);
}

.project-link:hover {
  color: var(--link-color);
}

/* Responsive adjustments */
@media (max-width: 600px) {
  .project-row {
    gap: 1rem;
  }
  .project-thumb {
    flex-basis: 64px;
    width: 64px;
    height: 64px;
  }
  .project-title {
    font-size: 1rem;
  }
}

/*-------------------------------------------------------------------
# Timeline
--------------------------------------------------------------------*/

#timeline-preview {
    padding-left: 20px;
}

.timeline-event {
    position: relative;
    margin: 0.5rem;
}

/* On the timeline page the dot must sit on the row's first line rather than
   at the row's vertical centre: a wrapped two-line entry would otherwise
   push its dot down and out of line with the month label beside it. */
#timeline .timeline-event {
    align-items: flex-start !important;
}

/* Sit on the centre of the row's first text line. --dot-offset is that
   distance from the event's top edge; it is a constant so that strong/weak
   rows (different title sizes) and wrapped rows all place their dot at the
   same height, keeping every dot in step with the month label beside it. */
#timeline .timeline-dot {
    top: var(--dot-offset);
    margin-top: calc(var(--dot-half) * -1);
}

/* Dot sizing. Hierarchy is carried by diameter alone — same colour, same
   shape — so milestones read clearly while routine entries stay quiet.
   --dot-half is half the weak dot and drives the month-label alignment
   below; keep the two in step if the sizes ever change. */
:root {
    --dot-weak: 7px;
    --dot-strong: 14px;
    --dot-half: 3.5px;
    /* Distance from a .timeline-event's top edge down to its dot centre:
       half of the 18px/1.5 title line box. Constant across strong/weak and
       wrapped rows, so every dot lines up with its month label. */
    --dot-offset: 13.5px;
}

.timeline-dot {
    width: var(--dot-strong);
    height: var(--dot-strong);
    background-color: var(--accent-color);
    border-radius: 50%;
    position: absolute;
    left: -20px;
    /* Centre stays on the line whatever the diameter. */
    margin-left: calc(var(--dot-strong) / -2);
    z-index: 1;
}

/*-------------------------------------------------------------------
# Timeline page — year/month rail
--------------------------------------------------------------------*/

/* Dates are shown once per group on a left rail instead of on every row.
   Both labels are sticky and pin to the same line, so as you scroll the
   year stays put while the month advances beside it. */
/* No left padding here: the two rail columns of .year-group already provide
   the offset, and .month-body adds the 20px dot gutter. */
#timeline {
    position: relative;
}

/* The line is drawn per month-body rather than once on #timeline, because
   only .month-body sits at the dot axis — #timeline spans the rail columns
   too. Each body's line runs its full height and the bodies stack directly
   on top of each other, so the result is continuous; the first and last
   groups then inset their outer end onto the first/last dot centre. */
.month-body:before {
    content: '';
    position: absolute;
    width: 4px;
    /* Dot centres sit at the body's 20px padding plus the event's own 0.5rem
       side margin, less the dot's 20px offset — i.e. exactly the margin. */
    left: 0.5rem;
    margin-left: -2px;
    top: 0;
    bottom: 0;
    background-color: var(--contrast-color);
    z-index: 0;
}

/* Inset the very first and very last ends so the line stops on a dot centre
   instead of overshooting into the surrounding whitespace. */
/* `.month-group` is not :first-of-type within its year group — the
   `.year-label` div precedes it — so the first month group is nth-of-type(2).
   Keep this in step with the markup in pages/timeline.md. */
#timeline > .year-group:first-child .month-group:nth-of-type(2) .month-body:before {
    top: calc(0.5rem + var(--dot-offset));
}

#timeline > .year-group:last-child .month-group:last-child .month-body:before {
    /* From the body's bottom edge up to the last dot's centre: the event's
       0.5rem margin, the title's 1rem bottom margin, and the remainder of
       the title's first line below the dot. */
    bottom: calc(0.5rem + 1rem + 18px - var(--dot-offset));
}

.year-group {
    display: grid;
    /* Fixed widths, never auto: auto sizes each year group independently and
       the whole rail jitters horizontally from one year to the next.
       4.9rem = widest year text at 1.35rem, plus the 1.5rem gap the month
       uses before its dot, so both gaps read as equal. */
    grid-template-columns: 4.9rem 3.6rem 1fr;
    align-items: start;
}

.year-label {
    grid-column: 1;
    position: sticky;
    /* Offset so the year's text centre lines up with the month's despite the
       larger font size. Must be applied via `top`, not a margin — a margin
       shifts the box out of its own sticky range and it stops pinning. */
    top: calc(4.2rem + (0.78rem * 1.2 / 2) - (1.35rem * 1.2 / 2));
    z-index: 3;
    align-self: start;
    font-size: 1.35rem;
    font-weight: 400;
    line-height: 1.2;
    color: var(--accent-color);
    text-align: right;
    padding: 0 1.5rem 0 0;
    background-color: var(--background-color);
}

/* subgrid so the month's own columns line up with the year grid. */
.month-group {
    display: grid;
    grid-template-columns: subgrid;
    grid-column: 2 / -1;
}

.month-label {
    grid-column: 1;
    /* Same pin line as the year, so the two sit level side by side. */
    position: sticky;
    top: 4.2rem;
    z-index: 2;
    align-self: start;
    font-size: 0.78rem;
    font-weight: 500;
    line-height: 1.2;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--accent-color);
    /* Right-aligned so the edge the dot gap is measured from stays fixed;
       left-aligning makes that gap vary with each month's word width. */
    text-align: right;
    padding: 0 1.5rem 0.35rem 0;
    /* Opaque so an outgoing month is hidden behind the next year's label
       rather than showing through it. */
    background-color: var(--background-color);
    /* Centre the label on its group's first dot. The event is flex-centred,
       so its dot sits --dot-offset below the group's top edge (the 0.5rem
       margin plus half the title's line box, not half the dot). The label's
       own centre is at half its box height: text line + bottom padding. */
    margin-top: calc(0.5rem + var(--dot-offset) - (1.2em + 0.35rem) / 2);
}

/* The 20px gutter is what the dots hang in: each is positioned left:-20px
   relative to its event, putting its centre on this padding edge. */
.month-body {
    grid-column: 2;
    position: relative;
    padding-left: 20px;
}

/* The line is drawn in two halves per event (above and below the dot's
   center) so consecutive events always meet exactly at the dot, regardless
   of row height. The first/last event in a list clips its outer half so the
   line starts/ends cleanly at the first/last dot instead of overshooting
   into the surrounding whitespace. */
.timeline-event:before,
.timeline-event:after {
    content: '';
    position: absolute;
    width: 4px;
    left: -20px;
    margin-left: -2px;
    background-color: var(--contrast-color);
}

.timeline-event:before {
    top: -0.5rem;
    height: calc(50% + 0.5rem);
}

.timeline-event:after {
    top: 50%;
    height: calc(50% + 0.5rem);
}

.timeline-event:first-child:before {
    top: 50%;
    height: 0;
}

.timeline-event:last-child:after {
    height: 0;
}

/* The timeline page draws one continuous line on #timeline instead, so the
   per-event halves would double up on it. The preview still uses them. */
#timeline .timeline-event:before,
#timeline .timeline-event:after {
    content: none;
}

/* Dots sit above the sticky rail labels: their opaque backgrounds are flush
   with the dot column, and at an equal stacking level they clip the dot's
   edge into a visible flat corner as they scroll past. */
#timeline .timeline-dot {
    z-index: 6;
}

/* Preview only: more (older) events exist below the last shown dot, so its
   line fades out instead of stopping cleanly like the full page does. */
#timeline-preview .timeline-event:last-child:after {
    top: 50%;
    height: 2.5rem;
    background: linear-gradient(to bottom, var(--contrast-color) 0%, var(--contrast-color) 60%, transparent 100%);
}

.timeline-content {
    padding: 0 20px;
    border-radius: 6px;
    position: relative;
}

.timeline-date {
    font-size: 14px;
    color: var(--accent-color);
}

.timeline-title {
    font-size: 18px;
    margin: 0 0 1rem 0;
}

/* Timeline highlighting based on importance. Strong and weak differ only in
   diameter: previously both were the same colour 4px apart, which made the
   two tiers nearly indistinguishable. */
.timeline-event.strong .timeline-dot {
    width: var(--dot-strong);
    height: var(--dot-strong);
    margin-left: calc(var(--dot-strong) / -2);
    background-color: var(--accent-color);
}

.timeline-event.weak .timeline-dot {
    width: var(--dot-weak);
    height: var(--dot-weak);
    margin-left: calc(var(--dot-weak) / -2);
    background-color: var(--contrast-color);
    filter: brightness(0.82);
}

.timeline-event.strong .timeline-title {
    font-size: 1.15rem;
    font-weight: 400;
}

.timeline-event.weak .timeline-title {
    font-size: 1rem;
    font-weight: 400;
    /* color: var(--accent-color); */
}

.timeline-event.strong .timeline-date {
    font-size: 1rem;
    font-weight: 500;
    color: var(--accent-color);
}

.timeline-event.weak .timeline-date {
    font-size: 0.85rem;
    font-weight: 350;
    color: var(--accent-color);
}

/* Narrow screens: the two rail columns cost ~8.5rem, too much next to the
   text on a phone. The rail is dropped entirely and both labels move ONTO
   the line — year above month, each a full-width opaque band that entries
   scroll behind, with only the label text sitting on the line's axis.

   The previous version kept the labels as full-width sticky BLOCKS with no
   column of their own, pinned at two different `top`s. That is what made
   them float over the entry text and over each other: on desktop a sticky
   label overlays nothing because it owns a rail column, and going
   display:block threw that property away without replacing it. Here the
   bands are opaque and span the whole row, so covering the text as it
   passes underneath is the intended effect rather than a defect. */
@media (max-width: 600px) {
  /* --axis is the single x that the spine, the dots and both label texts
     all sit on, measured from .year-group's left edge. It must exceed half
     the widest label ("2026" ~= 30px) or the text overhangs the screen. */
  #timeline {
    --axis: 2.5rem;
  }

  .year-group,
  .month-group {
    display: block;
  }

  /* Only the OUTER group is padded: .month-group nests inside .year-group,
     so padding both would compound to 2*--axis and put the month's axis
     40px right of the year's. They must share one x, not each get their own. */
  .year-group {
    padding-left: var(--axis);
  }
  .month-group {
    padding-left: 0;
  }

  /* The body's own 20px dot gutter is redundant now that the group padding
     places the axis, so the spine and dots reposition onto it. */
  .month-body {
    padding-left: 0;
  }
  .month-body:before {
    left: 0;
    margin-left: -2px;
  }
  .timeline-event {
    margin: 0.5rem 0;
  }
  .timeline-dot {
    left: 0;
  }

  /* The band. The label spans the full row so its opaque background covers
     the whole line of text scrolling underneath; the text itself is offset
     onto the axis by the inner span, not by the label, since transforming
     the label would drag the band with it. */
  .year-label,
  .month-label {
    display: block;
    /* box-sizing is border-box (Bootstrap's reset), so the --axis padding
       below is absorbed rather than added: the width must include the
       --axis that margin-left pulls the band back by, or the band falls
       --axis short of #timeline's right edge. Do NOT switch this to a
       vw-based width — that extends the scrollable area and widens the
       page, the failure `overflow-x: clip` on main already guards against. */
    width: calc(100% + var(--axis));
    margin-left: calc(var(--axis) * -1);
    padding: 0.1rem 0 0.1rem var(--axis);
    text-align: left;
    background-color: var(--background-color);
  }

  /* Centres the text on the axis; the band stays put. */
  .year-label > span,
  .month-label > span {
    display: inline-block;
    transform: translateX(-50%);
  }

  /* Stacking, bottom to top: spine (0) < dots (6) < month < year. Dots and
     spine must vanish behind a pinned band as they scroll past it, and an
     outgoing month must vanish behind the year pinned above it. */
  .month-label {
    z-index: 7;
  }
  .year-label {
    z-index: 8;
  }

  /* Year pins first, month directly beneath — the same pairing as desktop,
     where the year holds while its months advance past it. The month's
     `top` must equal the year band's exact height or the spine shows
     through the gap between them: 0.1rem*2 padding + a 1.35rem/1.2 line
     = 1.82rem. Keep the two in step if either value changes. */
  .year-label {
    top: 0;
    margin-top: 1.2rem;
  }
  .year-group:first-child .year-label {
    margin-top: 0;
  }
  .month-label {
    top: 1.82rem;
    /* Cancel the wide-layout offset that centres the label on its first dot;
       sitting on the line above its group, it just runs inline. */
    margin-top: 0;
  }

  /* Clear the dot and keep a right-hand gutter so text never reaches the
     device edge. */
  .timeline-content {
    padding: 0 0.5rem 0 16px;
  }

  /* Stacked, the labels sit BETWEEN the month bodies rather than beside them,
     so each body's line no longer meets the next and the spine breaks up.
     Extend every segment down through the following label to close the gap;
     the last group in each year stops at its own dot as before. The bridged
     distance is a month band (1.82rem), or at a year boundary that plus the
     year's own 1.82rem band and 1.2rem margin — size for the larger case and
     let the shorter one run behind the opaque band. */
  .month-body:before {
    bottom: auto;
    height: calc(100% + 4.9rem);
  }
  .month-group:last-child .month-body:before {
    height: 100%;
  }
}

.view-more-wrap {
    display: flex;
    justify-content: center;
    padding: 1rem 0 0.5rem 0;
}

/* Qualified with the element type so this beats the global a:link/a:visited
   rule on specificity — a plain .view-more-btn class rule loses that fight
   and renders blue regardless of what color is set here. */
a.view-more-btn:link,
a.view-more-btn:visited {
    color: var(--accent-color);
    font-size: 0.85rem;
    font-weight: 500;
    padding: 0;
    border: none;
    background: none;
    /* Inline, not inline-flex: a flex `gap` is layout space with no inline
       content in it, so text-decoration skips it and the underline breaks
       before the arrow. Plain inline text keeps the run continuous. */
    display: inline;
    text-decoration: none;
}

a.view-more-btn:hover {
    color: var(--default-color);
    text-decoration: underline;
    text-underline-offset: 3px;
}

.view-more-btn .arrow {
    /* inline-block so translateX applies — transform is ignored on
       non-replaced inline boxes. */
    display: inline-block;
    transition: transform 0.15s ease;
}

a.view-more-btn:hover .arrow {
    transform: translateX(3px);
}


/*-------------------------------------------------------------------
# Quotes
--------------------------------------------------------------------*/
.quotes-disclaimer {
    text-align: right;
    font-weight: 100;
    font-size: 0.8rem;
    font-style: italic;
    color: var(--accent-color);
    padding: 0.5rem 0;
    margin-bottom: 1rem;
    border-radius: 8px;
}

.quotes-disclaimer p {
  text-align: center;
}

/* Width comes from the enclosing .page-col.

   clip contains the scatter-in transforms: cards start offset by up to 140px
   in a random direction, and transforms extend the scrollable overflow area
   even though they don't affect layout. On a phone that widened the page past
   the viewport, which zoomed the page out and carried the position:fixed
   hamburger off-screen with it. */
.quote-grid {
    columns: 3;
    column-gap: 1.5rem;
    width: 100%;
    overflow: clip;
    /* room for the cards' hover shadow, which would otherwise be clipped */
    overflow-clip-margin: 1.5rem;
}

@media (max-width: 900px) {
    .quote-grid { columns: 2; }
}

@media (max-width: 600px) {
    .quote-grid { columns: 1; }
}

.quote-card {
    break-inside: avoid;
    position: relative;
    border-radius: 1rem;
    margin: 0 0 1.5rem 0;
    padding: 1.75rem 2rem;
    border: 1px solid color-mix(in srgb, var(--default-color), transparent 90%);
    box-shadow: 0px 0px 20px 2px rgba(0, 0, 0, 0.06);
    transition: all 0.3s ease;
}

.quote-card:hover {
    box-shadow: 0px 0px 24px 4px rgba(0, 0, 0, 0.1);
    transform: translateY(-2px);
    border-color: transparent;
}

.quote-content {
    font-size: 1.1rem;
    font-style: italic;
    position: relative;
    margin: 0 0 1rem 0;
    display: inline-block;
    line-height: 1.5;
}

.quote-content::before,
.quote-content::after {
    content: '"';
    font-size: 1.8rem;
    color: var(--accent-color);
    opacity: 0.5;
}

.quote-content::before {
    position: absolute;
    left: -1rem;
    top: -0.4rem;
}

.quote-content::after {
    position: static;
    margin-left: 0.1rem;
    line-height: 0;
    vertical-align: bottom;
}

.quote-author {
    text-align: right;
    font-weight: 500;
    font-size: 0.95rem;
    color: var(--accent-color);
    margin: 0;
}

.quote-source {
    text-align: right;
    font-size: 0.78rem;
    font-style: italic;
    color: var(--accent-color);
    opacity: 0.8;
    margin: 0.15rem 0 0 0;
}

.quote-card.scatter-init {
    opacity: 0;
    transform: translate(var(--dx), var(--dy)) rotate(var(--drot)) scale(0.85);
}

.quote-card.scatter-in {
    opacity: 1;
    transform: translate(0, 0) rotate(0) scale(1);
    transition:
        opacity 0.6s ease-out,
        transform 0.7s cubic-bezier(0.22, 1, 0.36, 1);
    transition-delay: var(--delay);
}

/* No quotes found message */
.no-quotes {
    text-align: center;
    font-style: italic;
    color: var(--accent-color);
    margin: 3rem 0;
}

.chatbot-model-info {
  background: var(--contrast-color, #eee);
  color: var(--default-color, #444);
  font-size: 12px;
  padding: 6px 18px;
  border-bottom: 1px solid var(--contrast-color, #ddd);
  display: flex;
  align-items: center;
  gap: 7px;
}

.chatbot-model-info i {
  color: var(--accent-color, #666);
  font-size: 14px;
}

.chatbot-model-info a {
  color: inherit;
  text-decoration: underline;
}

/*--------------------------------------------------------------
# Preloader
--------------------------------------------------------------*/
#preloader {
  position: fixed;
  inset: 0;
  z-index: 9999;
  overflow: hidden;
  background-color: var(--background-color);
  transition: all 0.6s ease-out;
  width: 100%;
  height: 100vh;
}

#preloader:before,
#preloader:after {
  content: "";
  position: absolute;
  border: 4px solid var(--accent-color);
  border-radius: 50%;
  animation: animate-preloader 2s cubic-bezier(0, 0.2, 0.8, 1) infinite;
}

#preloader:after {
  animation-delay: -0.5s;
}

@keyframes animate-preloader {
  0% {
    width: 10px;
    height: 10px;
    top: calc(50% - 5px);
    left: calc(50% - 5px);
    opacity: 1;
  }

  100% {
    width: 72px;
    height: 72px;
    top: calc(50% - 36px);
    left: calc(50% - 36px);
    opacity: 0;
  }
}

/*-------------------------------------------------------------------
# Resources
--------------------------------------------------------------------*/

.resources-page {
  padding-top: 0;
}

.resources-page h1 {
  font-size: 1.75rem;
  font-weight: 400;
  letter-spacing: -0.01em;
  margin: 2.5rem 0 0.6rem 0;
  padding-top: 2.5rem;
  border-top: 1px solid color-mix(in srgb, var(--default-color), transparent 85%);
}

.resources-page h1:first-of-type {
  margin-top: 0;
  padding-top: 0;
  border-top: none;
}

.resources-page > p,
.resources-page > ul {
  margin-bottom: 0;
}

.resources-page h2 {
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent-color);
  margin: 2rem 0 0.85rem 0;
}
