/* marcoscraviotto.com — single stylesheet for the whole site */

/* ---------- Fonts ----------
   Self-hosted, so there is no third-party request and no flash of a
   different typeface on navigation. See fonts/README.md. */

@font-face {
  font-family: "IBM Plex Sans";
  src: url("/fonts/ibm-plex-sans-400.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "IBM Plex Sans";
  src: url("/fonts/ibm-plex-sans-500.woff2") format("woff2");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "IBM Plex Sans";
  src: url("/fonts/ibm-plex-sans-600.woff2") format("woff2");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "IBM Plex Mono";
  src: url("/fonts/ibm-plex-mono-400.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

/* ---------- Theme ----------
   Dark is the default. A saved "light" preference sets data-theme="light"
   on <html> before the page paints (see the script in each page's <head>). */

:root {
  color-scheme: dark;
  --bg: #141414;
  --text: #EFE9DC;   /* cream */
  --muted: #9A9488;
  --rule: #2E2E2B;

  /* Body text, and a mono companion for the small stuff: dates, notes, nav. */
  --font: "IBM Plex Sans", system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-meta: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
}

:root[data-theme="light"] {
  color-scheme: light;
  --bg: #FAF8F3;     /* warm paper */
  --text: #111;
  --muted: #6B6B66;
  --rule: #E2DED4;
}

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

/* 112.5% of the browser's default is 18px, but stated as a ratio so it still
   follows a reader who has changed their own base size. Every rem below is
   measured from here. */
html {
  font-size: 112.5%;
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  font-size: 1rem;
  line-height: 1.65;
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
}

/* ---------- Layout ----------
   Sidebar and content are one block — 144px nav, 81px gap, 621px text —
   centred in the window, with the gap fixed so the two always read as a pair.
   The text column is sized to a ~69-character measure; the nav column to its
   longest label, so the rules above and below the links stop where the words
   do. Widths are in rem for the same reason the spacing is: everything grows
   together. */

.page {
  display: grid;
  grid-template-columns: 8rem 34.5rem;
  /* The second row stays 1fr so the footer sits directly under the nav
     instead of drifting down as the content column grows. */
  grid-template-rows: auto 1fr;
  grid-template-areas:
    "nav    content"
    "footer content";
  column-gap: 4.5rem;
  justify-content: center;
  /* Top-anchored, not vertically centred: centring sizes each page against
     its own content, so the nav and the name landed at a different height on
     every page and the whole layout jumped on navigation. */
  padding: 5.33rem 2rem 8.3rem;
}

.sidebar {
  grid-area: nav;
  /* Puts the top rule level with the cap height of the name, measured from
     the font's own metrics rather than guessed. */
  padding-top: 0.54rem;
}

.content {
  grid-area: content;
  min-width: 0;
}

/* ---------- Sidebar ---------- */

/* Everything in the mono face runs a notch smaller: monospace carries a
   larger x-height, so matching the body size would make it look bigger. */
.nav a,
.note,
.entry-meta,
.step-year,
.book-author,
footer {
  font-family: var(--font-meta);
  font-size: 0.83rem;
  letter-spacing: -0.01em;
  /* Slashed zero. Plex Mono's default zero is dotted; "zero" swaps in the
     crossed one. Only reachable because these files are self-hosted —
     Google's build of Plex Mono has this feature stripped out. */
  font-feature-settings: "zero" 1;
}

/* A rule above and below the links, and nothing else. The top rule is offset
   to the name's cap height (see .sidebar), which is what aligns the two. */
.nav {
  padding: 1rem 0;
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
}

.nav a {
  display: block;
  margin-bottom: 0.55rem;
  color: var(--muted);
  text-decoration: none;
}

.nav a:last-child { margin-bottom: 0; }

.nav a:hover { color: var(--text); }

.nav a[aria-current="page"] {
  color: var(--text);
  font-weight: 500;
}

/* ---------- Theme switch ----------
   Top right of the content column, level with the name. It shares the content
   grid area rather than being positioned absolutely: two items in one area
   overlap, so the switch parks itself on the column's right edge without
   taking up space or pushing anything down, and it keeps following that edge
   if the column is ever resized. Hidden without JS. */

.theme-toggle {
  grid-area: content;
  justify-self: end;
  align-self: start;
  /* .content shares this area and comes later in the source, so without this
     it would paint over the switch and swallow the click. Grid items take
     z-index without needing to be positioned. */
  z-index: 1;
  /* Half the difference between the h1's line box (1.78rem x 1.25) and the
     switch, so the two are optically centred on each other. */
  margin-top: 0.39rem;
  display: flex;
  align-items: center;
  width: 2.56rem;
  height: 1.44rem;
  padding: 0.11rem;
  border: 1px solid var(--rule);
  border-radius: 999px;
  background: none;
  cursor: pointer;
}

/* An author `display` beats the UA stylesheet's `[hidden] { display: none }`
   regardless of specificity, so `display: flex` above was showing the switch
   even with the attribute set — a dead control for anyone without JS. */
.theme-toggle[hidden] { display: none; }

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

.theme-toggle:focus-visible {
  outline: 1px solid var(--muted);
  outline-offset: 3px;
}

.theme-toggle .knob {
  display: grid;
  place-items: center;
  width: 1.11rem;
  height: 1.11rem;
  border-radius: 50%;
  background: var(--text);
  color: var(--bg);
  /* The one movement on the site. A switch that does not travel reads as
     broken rather than restrained. */
  transform: translateX(1rem);
  transition: transform 160ms ease;
}

:root[data-theme="light"] .theme-toggle .knob { transform: translateX(0); }

@media (prefers-reduced-motion: reduce) {
  .theme-toggle .knob { transition: none; }
}

.theme-toggle .icon { display: none; }

.theme-toggle .moon { display: block; }

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

:root[data-theme="light"] .theme-toggle .sun { display: block; }

/* ---------- Typography ---------- */

h1 {
  margin: 0 0 0.44rem;
  font-size: 1.78rem;
  font-weight: 500;
  line-height: 1.25;
  letter-spacing: -0.01em;
  /* Evens out the line lengths instead of leaving one word on the last line. */
  text-wrap: balance;
}

/* An h1 followed straight by content needs more air than the gap that sits
   above an intro line. */
.content > h1 + section,
.content > h1 + .list,
.content > h1 + .timeline,
.content > h1 + p:not(.intro) {
  margin-top: 2rem;
}

/* 24px against 18px body and the 32px name — a 4:3 step either side, so the
   three sizes read as one scale. At the old 1.06rem/500 a section heading was
   6% larger than the text under it, which reads as an accident rather than a
   hierarchy. 600 is the same emphasis weight <strong> uses. */
h2 {
  margin: 0 0 0.5rem;
  font-size: 1.33rem;
  font-weight: 600;
  line-height: 1.3;
  letter-spacing: -0.015em;
}

h3 {
  margin: 0;
  font-size: 1rem;
  font-weight: 500;
  line-height: 1.5;
}

p {
  margin: 0 0 1.25em;
  text-wrap: pretty;
}

/* 600, not the browser's default 700 — there is no 700 here, and asking for
   one makes the browser smear a real cut into a synthetic bold. 500 was the
   first attempt and it works on paper, but it disappears on the dark theme:
   light type on a dark ground optically spreads, so 400 already looks heavier
   than it is and the step up to 500 closes to nothing. 600 is far enough to
   read against that, and still a real cut of the face. */
strong {
  font-weight: 600;
}

/* Whatever ends the column drops its trailing margin — the page's bottom
   padding provides that space. Targeting `section:last-of-type` instead
   collapsed the gap before the CV link on /experience, where a paragraph
   follows the last section. */
.content > :last-child { margin-bottom: 0; }

.note {
  color: var(--muted);
}

/* Content links: underlined, same colour as text, muted on hover. */
.content a,
footer a {
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-thickness: 1px;
  /* --rule is a border colour: at 1.35:1 on the dark background it reads as
     no underline at all. --muted clears 5:1 in both themes. */
  text-decoration-color: var(--muted);
}

.content a:hover,
footer a:hover {
  color: var(--muted);
  text-decoration-color: currentColor;
}

.nav a:focus-visible,
.content a:focus-visible,
footer a:focus-visible {
  outline: 1px solid var(--muted);
  outline-offset: 3px;
  border-radius: 2px;
}

/* ---------- Sections ---------- */

/* 40px of air before the first paragraph. */
.intro { margin-bottom: 2.22rem; }

section { margin-bottom: 3.11rem; }

.entry { margin-bottom: 2rem; }

.entry:last-child { margin-bottom: 0; }

.entry-meta {
  margin: 0 0 0.5rem;
  color: var(--muted);
}

.entry p { margin: 0 0 0.7rem; }

.entry p:last-child { margin-bottom: 0; }

/* ---------- Lists ---------- */

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

.list li { margin-bottom: 0.9rem; }

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

.list .note { display: block; }

/* ---------- Journey ----------
   Year, rail, content. Every measurement hangs off the four values below, so
   the rail, the markers and the year all stay aligned if any one of them
   changes. --row is the title's line box: markers centre on it, which is what
   puts a marker on its title rather than on the top of the row. */

.timeline {
  --year: 2.2rem;
  --marker: 1.22rem;
  --gap: 1.1rem;
  --row: 1.6rem;

  margin: 0;
  padding: 0;
  list-style: none;
}

.step {
  position: relative;
  display: grid;
  grid-template-columns: var(--year) var(--marker) 1fr;
  column-gap: var(--gap);
  /* Also the length of the rail between one marker and the next, less the
     two halos — so this is the dial for how much rail shows. */
  padding-bottom: 3.4rem;
}

/* The rail. One segment per entry, spanning the entry's full height so that
   consecutive segments meet edge to edge and read as a single unbroken line
   from the first marker to the last. The markers sit on top of it. */
.step::before {
  content: "";
  position: absolute;
  left: calc(var(--year) + var(--gap) + var(--marker) / 2);
  top: 0;
  bottom: 0;
  width: 1px;
  /* `left` puts the rule's left edge on the marker centre, which leaves it
     half a pixel to the right of it. This centres the 1px on the line. */
  transform: translateX(-50%);
  background: var(--rule);
}

/* The line starts and ends on the timeline rather than running off it: the
   first segment begins at its marker, the last one stops at its marker. */
.step:first-child::before { top: calc(var(--row) / 2); }

.step:last-child::before { bottom: calc(100% - var(--row) / 2); }

.step:last-child { padding-bottom: 0; }

.marker {
  /* The rail is absolutely positioned, which puts it above its static
     siblings in paint order — so without this it drew straight over the
     markers and the halos below masked nothing. */
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  height: var(--row);
}

/* No halo around either marker: the rail runs behind them and is hidden by
   them, so the line meets each marker's edge and the whole thing reads as one
   connected run. What made the rail look like it ran *over* the logos was
   paint order, fixed on .marker above — not a lack of clearance. */
.dot {
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  background: var(--muted);
}

.logo {
  width: var(--marker);
  height: var(--marker);
  object-fit: cover;
  border-radius: 2px;
}

.step-year {
  margin: 0;
  /* Absolute, to match the title's box: same height from the same top edge
     puts the two baselines within a pixel of each other despite the
     different sizes and faces. */
  line-height: var(--row);
  color: var(--muted);
}

.step-title {
  margin: 0 0 0.25rem;
  font-size: 1.17rem;
  font-weight: 600;
  line-height: var(--row);
  letter-spacing: -0.01em;
}

.step-body p { margin: 0; }

/* Beats `.step-body p` on specificity, so the lead-in clears the paragraph it
   follows. Muted, with the papers listed under it at full contrast. */
.step-body .step-links-intro {
  margin-top: 0.7rem;
  color: var(--muted);
}

.step-links {
  margin: 0.15rem 0 0;
  padding: 0;
  list-style: none;
}

.step-links li { margin-bottom: 0.1rem; }

.step-links li:last-child { margin-bottom: 0; }

/* A pair side by side, each half the body column. The images are already cut
   to 4:3, but declaring it here reserves the space before they load, so the
   entry below never jumps. */
.step-photos {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.44rem;
  margin-top: 1rem;
}

.step-photos img {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  border: 1px solid var(--rule);
  border-radius: 2px;
}

/* Too narrow for three columns: the marker keeps the left, the year moves
   above the title, and the rail moves with it. */
@media (max-width: 26rem) {
  .step { grid-template-columns: var(--marker) 1fr; }
  .step::before { left: calc(var(--marker) / 2); }
  .marker    { grid-column: 1; grid-row: 1; }
  .step-year { grid-column: 2; grid-row: 1; }
  .step-body { grid-column: 2; grid-row: 2; }
}

/* ---------- Bookshelf ----------
   Cover left at a fixed width, everything else right. The files are already
   cut to 2:3, but declaring the ratio reserves the space before they load so
   the list never jumps, and `cover` keeps any future file in line. */

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

.book {
  display: grid;
  grid-template-columns: 7rem 1fr;
  column-gap: 1.3rem;
  padding-bottom: 2.4rem;
}

.book:last-child { padding-bottom: 0; }

.book-cover {
  display: block;
  /* Grid items stretch by default, which overrides the aspect ratio and
     pulls the cover down to the height of the whole row. */
  align-self: start;
  width: 7rem;
  /* The width/height attributes on the tag are presentational hints. Setting
     width alone leaves the height hint (450px) in force and it beats the
     aspect ratio, so the covers render at full height. */
  height: auto;
  aspect-ratio: 2 / 3;
  object-fit: cover;
  border: 1px solid var(--rule);
  border-radius: 2px;
}

/* One step below a section heading and level with the journey's entry
   titles: both are items in a list, not headings over one. */
.book-title {
  margin: 0;
  font-size: 1.17rem;
  font-weight: 600;
  line-height: 1.35;
  letter-spacing: -0.01em;
}

.book-author { margin: 0 0 0.4rem; }

.book-body p:last-child { margin-bottom: 0; }

/* The text column runs out of room before the cover does. 30rem rather than
   26rem because a media query's rem is the browser default (16px), not this
   page's 18px - so this switches at 480px, while the block is still wide
   enough that the text does not drop to a couple of words a line. */
@media (max-width: 30rem) {
  .book { grid-template-columns: 4.4rem 1fr; column-gap: 1rem; }
  .book-cover { width: 4.4rem; height: auto; }
}

/* ---------- Footer ---------- */

.page > footer {
  grid-area: footer;
  align-self: start;
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
  margin-top: 1.78rem;
  color: var(--muted);
}

footer a { color: inherit; }

/* ---------- Narrow screens ----------
   The block needs ~918px to stand up (846px block + 36px either side).
   Below that the nav becomes a centred row above the content, and the footer
   moves to the bottom of the page. */

@media (max-width: 940px) {
  .page {
    display: flex;
    flex-direction: column;
    max-width: 37rem;
    margin: 0 auto;
    padding: 3rem 1.3rem 5rem;
  }

  /* Back into the flow, centred above the nav. */
  .theme-toggle { margin: 0 auto 1.1rem; }


  .sidebar {
    order: 1;
    /* No top margin: the 2.4rem that used to be here was only clearing the
       toggle back when it floated over this corner. */
    margin-bottom: 2.6rem;
    padding-top: 0;
    text-align: center;
  }

  .content { order: 2; }

  .page > footer {
    order: 3;
    flex-direction: row;
    flex-wrap: wrap;
    justify-content: center;
    gap: 0.2rem 1.05rem;
    margin-top: 3.11rem;
  }

  .nav {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 0.2rem 1.05rem;
    padding: 0.85rem 0;
  }

  .nav a,
  .nav a:last-child { margin-bottom: 0; }
}
