/* Personal site — the owner's monospace CV identity (see personalos cv.css) turned into a
   public page: warm paper, DejaVu Sans Mono, '#'-prefixed headings, right-aligned dates,
   "< E O F >" footer. Light theme only (owner rule) — no dark tokens, no toggle.

   Readability notes: monospace prose gets heavy fast, so the page leans on a few
   deliberate focal points (chips, stat cards, ledes) and folds depth into <details>
   rather than stacking uniform paragraphs. Everything is tuned mobile-first. */

@font-face { font-family: 'DejaVu Sans Mono'; src: local('DejaVu Sans Mono'), url('/static/fonts/DejaVuSansMono.ttf') format('truetype'); font-weight: 400; font-style: normal; font-display: swap; }
@font-face { font-family: 'DejaVu Sans Mono'; src: local('DejaVu Sans Mono Bold'), url('/static/fonts/DejaVuSansMono-Bold.ttf') format('truetype'); font-weight: 700; font-style: normal; font-display: swap; }
@font-face { font-family: 'DejaVu Sans Mono'; src: local('DejaVu Sans Mono Oblique'), url('/static/fonts/DejaVuSansMono-Oblique.ttf') format('truetype'); font-weight: 400; font-style: italic; font-display: swap; }

:root {
  color-scheme: light;
  --bg: #f2f0ea;
  --surface: #faf8f3;
  --surface-2: #f0ede5;
  --border: #e0dcd1;
  --text: #33373f;          /* headings, strong — the ink that leads */
  --text-body: #4c515b;     /* body prose, one step back from the ink */
  --text-dim: #8a8f9a;      /* meta, markers, labels */
  --accent: #3b6fe0;
  font-family: 'DejaVu Sans Mono', ui-monospace, Menlo, Consolas, monospace;
}

* { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text-body);
  font-family: inherit;
  font-size: 15px;
  line-height: 1.62;
  -webkit-font-smoothing: antialiased;
}

.page { max-width: 720px; margin: 0 auto; padding: 0 20px 56px; }

/* ---------- header: compact, sticky, one line ---------- */

.top {
  position: sticky;
  top: 0;
  z-index: 10;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 12px 0 10px;
  margin-bottom: 20px;
  background: var(--bg);
  border-bottom: 1px solid var(--border);
}
.brand { font-weight: 700; color: var(--text); text-decoration: none; white-space: nowrap; }
.brand::before { content: '$ '; color: var(--text-dim); font-weight: 400; }
/* On a phone the full name crowds the nav off the row — fall back to the monogram
   the favicon uses. The <h1> right below still carries the name. */
.brand .mono-mark { display: none; }
@media (max-width: 619px) {
  .brand .full { display: none; }
  .brand .mono-mark { display: inline; }
}
.top nav {
  display: flex;
  gap: 13px;
  font-size: .9em;
  overflow-x: auto;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}
.top nav::-webkit-scrollbar { display: none; }
.top nav a { color: var(--text-dim); text-decoration: none; white-space: nowrap; }
.top nav a:hover, .top nav a:focus-visible { color: var(--accent); }

/* ---------- sections: air between them, so each starts fresh ---------- */

section { scroll-margin-top: 60px; }
section + section { margin-top: 46px; }

/* ---------- headings — markdown-flavoured, like the CV ---------- */

.cv h1, .cv h2, .cv h3, .cv h4 { font-weight: 700; color: var(--text); line-height: 1.3; }
.cv h1::before, .cv h2::before, .cv h3::before, .cv h4::before { color: var(--border); font-weight: 400; margin-right: .5em; }
.cv h1::before { content: '#'; }
.cv h2::before { content: '##'; }
.cv h3::before { content: '###'; }
.cv h4::before { content: '####'; }

.cv h1 { font-size: 1.85em; margin: .4em 0 .35em; letter-spacing: -.01em; }
.cv h2 { font-size: 1.3em; margin: 0 0 .8em; padding-bottom: .3em; border-bottom: 1px solid var(--border); }
/* space-between worked for "Title · date" and threw every other h3 at the right
   margin, because the '###' prefix is the first flex item. Start them left and let
   the date claim the far edge on its own. */
.cv h3 {
  font-size: 1.02em;
  margin: 1.9em 0 .3em;
  display: flex;
  justify-content: flex-start;
  align-items: baseline;
  gap: .8em;
}
.cv h3 .t { flex: 1 1 auto; min-width: 0; }
.cv h3 .d { flex: none; margin-left: auto; font-weight: 400; color: var(--text-dim); font-size: .85em; letter-spacing: .02em; }
.cv h4 { font-size: 1em; margin: 1.2em 0 .1em; }

/* The line right after a section heading carries the section — give it weight. */
.cv h2 + p, .cv h1 + p { font-size: 1.06em; color: var(--text); }

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

.cv p { margin: 0 0 .9em; }
.cv a { color: var(--accent); text-decoration: none; border-bottom: 1px solid rgba(59, 111, 224, .3); }
.cv a:hover, .cv a:focus-visible { border-bottom-color: var(--accent); }
.cv strong { font-weight: 700; color: var(--text); }
.cv em { font-style: italic; color: var(--text-dim); }
.cv hr { border: 0; border-top: 1px solid var(--border); margin: 1.8em 0; }
.cv blockquote {
  margin: 1em 0;
  padding: .1em 0 .1em 1.1em;
  border-left: 2px solid var(--border);
  color: var(--text-dim);
  font-style: italic;
}
.kicker { color: var(--text-dim); margin: 0 0 .3em; letter-spacing: .04em; font-size: .9em; }

/* ---------- lists — "-" bullets like the CV ---------- */

.cv ul { list-style: none; margin: 0 0 1em; padding: 0; }
.cv li { margin: 0 0 .55em; padding-left: 1.4em; text-indent: -1.4em; }
.cv li::before { content: '-'; color: var(--border); display: inline-block; width: 1.4em; text-indent: 0; }
.cv ol { margin: 0 0 1em; padding-left: 1.6em; }
.cv ol li { padding-left: 0; text-indent: 0; }
.cv ol li::before { content: none; }

/* ---------- chips: a row of links, still links ----------
   Pills read as buttons and stopped reading as anchors; these stay classic blue +
   underline like every other link, just laid out in a row with generous tap area. */

.chips ul { display: flex; flex-wrap: wrap; gap: 4px 18px; margin: 1.1em 0 .4em; padding: 0; }
.chips li { margin: 0; padding: 0; text-indent: 0; }
.chips li::before { content: none; }
.chips a { display: inline-block; padding: 6px 0; color: var(--accent); border-bottom: 1px solid rgba(59, 111, 224, .3); }
.chips a:hover, .chips a:focus-visible { border-bottom-color: var(--accent); }

/* ---------- stats: the numbers, given somewhere to land ----------
   Ruled rows, not cards. Surfaces with radius and shadow belong to the dashboards on
   this machine; this page is a monospace CV and keeps to hairlines. */

.stats ul {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0;
  margin: 1.3em 0;
  padding: 0;
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
}
.stats li {
  margin: 0;
  padding: .75em 0;
  text-indent: 0;
  color: var(--text-dim);
  font-size: .92em;
  line-height: 1.45;
}
.stats li + li { border-top: 1px solid var(--border); }
.stats li::before { content: none; }
.stats li strong {
  display: block;
  font-size: 1.42em;
  line-height: 1.2;
  color: var(--text);
  letter-spacing: -.01em;
}

/* ---------- progressive disclosure ---------- */

details.more {
  margin: 1.2em 0;
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
}
details.more > summary {
  list-style: none;
  cursor: pointer;
  padding: 11px 0;
  color: var(--accent);
  font-size: .95em;
  display: flex;
  align-items: baseline;
  gap: .6em;
}
details.more > summary::-webkit-details-marker { display: none; }
details.more > summary::before {
  content: '+';
  color: var(--text-dim);
  font-weight: 700;
  width: 1em;
  flex: none;
}
details.more[open] > summary::before { content: '\2212'; }
details.more[open] > summary { color: var(--text-dim); }
.more-body { padding: 2px 0 10px; }
.more-body > :last-child { margin-bottom: 0; }

/* ---------- code, tables ---------- */

.cv code { font-family: inherit; background: var(--surface-2); border: 1px solid var(--border); border-radius: 4px; padding: 0 .3em; font-size: .93em; }
.cv pre { background: var(--surface); border: 1px solid var(--border); border-radius: 10px; padding: 12px 14px; overflow-x: auto; }
.cv pre code { background: none; border: 0; padding: 0; }
.cv table { border-collapse: collapse; width: 100%; margin: 1em 0; display: block; overflow-x: auto; }
.cv th, .cv td { text-align: left; vertical-align: top; padding: .35em .9em .35em 0; border-bottom: 1px solid var(--border); }
.cv th { color: var(--text-dim); font-weight: 400; text-transform: uppercase; font-size: .82em; letter-spacing: .05em; }

/* ---------- log cards ----------
   A grid of covers rather than a list of lines: four across on a desktop, two on a
   tablet, one on a phone. Hairline boxes, no fill and no radius, so they stay in the
   same drawing language as the stats row and the folds. */

.posts { list-style: none; display: grid; grid-template-columns: 1fr; gap: 14px; margin: 1.2em 0; padding: 0; }
.posts li { margin: 0; padding: 0; text-indent: 0; border: 1px solid var(--border); }
.posts li::before { content: none; }
.posts li > a {
  display: flex;
  flex-direction: column;
  gap: 5px;
  height: 100%;
  padding: 0 0 14px;
  color: var(--text-dim);
  text-decoration: none;
  border-bottom: 0;
}
.posts .cover {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 3 / 2;
  object-fit: cover;
  margin-bottom: 9px;
  border-bottom: 1px solid var(--border);
  background: var(--surface-2);
}
.posts .d { font-size: .8em; letter-spacing: .03em; padding: 0 14px; }
.posts .t { color: var(--text); border-bottom: 1px solid var(--border); align-self: flex-start; margin: 0 14px; line-height: 1.4; }
.posts li > a:hover { border-color: var(--accent); }
.posts li:hover { border-color: var(--accent); }
.posts li > a:hover .t, .posts li > a:focus-visible .t { color: var(--accent); border-bottom-color: var(--accent); }
.posts .sum {
  margin: 2px 14px 0;
  font-style: italic;
  font-size: .88em;
  line-height: 1.5;
  display: -webkit-box;
  -webkit-line-clamp: 4;
  line-clamp: 4;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* ---------- an entry's own cover ---------- */

.cv .lead {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 3 / 2;
  object-fit: cover;
  margin: 0 0 1.6em;
  border: 1px solid var(--border);
  background: var(--surface-2);
}

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

.eof { text-align: center; color: var(--border); letter-spacing: .5em; margin: 3.5em 0 0; font-size: .9em; }

:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 3px; }

/* ---------- wider screens ---------- */

@media (min-width: 620px) {
  body { font-size: 15.5px; }
  .page { padding: 0 24px 64px; }
  .stats ul { grid-template-columns: repeat(3, 1fr); column-gap: 26px; }
  .posts { grid-template-columns: repeat(2, 1fr); gap: 16px; }
  .stats li { font-size: .87em; }
  .stats li + li { border-top: 0; }
  section + section { margin-top: 58px; }
  .cv h1 { font-size: 2em; }
  .cv h2 { font-size: 1.35em; }
}

/* ---------- desktop: bigger type, same measure, section titles in the margin ----------

   The column holds 72 monospace characters, which is where prose wants to be, so a
   wide screen buys larger type instead of longer lines: 16px with the column grown to
   match keeps the character count identical.

   The 600px of dead margin gets the section titles. Pulling h2 out of the flow leaves
   a full-width rule between sections with the name set beside it, the way a CV is laid
   out on paper, and it gives back a line of vertical space per section. Below 1180px
   there is no room for the gutter and everything falls back into the flow. */

@media (min-width: 1180px) {
  body { font-size: 16px; }
  /* The page box carries the title gutter as padding, so the whole composition
     (titles + column) centres on the screen instead of the column alone. Only pages
     with something to put in the gutter get it; the log index keeps a plain column. */
  .page { max-width: 745px; }
  .page.sections, .page.article { max-width: 977px; padding-left: 256px; padding-right: 24px; }

  section { position: relative; }
  section + section {
    margin-top: 0;
    padding-top: 52px;
    border-top: 1px solid var(--border);
  }

  .cv h2 {
    position: absolute;
    top: 52px;
    left: -232px;
    width: 200px;
    margin: 0;
    padding: 0;
    border-bottom: 0;
    text-align: right;
    font-size: 1.1em;
    line-height: 1.35;
  }
  /* First section has no rule above it, so its title sits at the top. */
  section:first-child .cv h2, .cv > section:first-of-type h2 { top: 0; }

  /* The marker leads the title in the flow; in the margin it trails, so the titles
     stay flush against the column they belong to. */
  .cv h2::before { content: none; }
  .cv h2::after { content: ' ##'; color: var(--border); font-weight: 400; }

  /* Nothing has to clear the heading any more. */
  .cv h2 + p, .cv h2 + div, .cv h2 + ul, .cv h2 + details, .cv h2 + hr { margin-top: 0; }

  /* Three covers across needs more than 72 characters of width, so the grid alone
     reaches into the title gutter. Prose keeps the column. */
  .posts { grid-template-columns: repeat(3, 1fr); gap: 18px; }
  /* Line the grid's left edge up with the section titles, not with the page box, or
     it sits 24px left of centre while everything else is symmetric. Reaching into the
     gutter means reaching into the space the section's own title occupies, so the grid
     starts below it. */
  .page.sections .posts { margin-left: -232px; width: calc(100% + 232px); margin-top: 2.6em; }

  /* The log index has nothing to put in a gutter, so it doesn't get one: heading,
     lede and grid all start at the same left edge and the box centres as a whole. */
  .page.list { max-width: 977px; padding-left: 24px; padding-right: 24px; }
  .page.list .cv > :not(.posts) { max-width: 697px; }

  /* A fold at the end of a section draws its own bottom rule right above the section
     rule. Keep one line, not two. */
  details.more:last-child { border-bottom: 0; margin-bottom: 0; }

  /* On an entry the gutter takes the date, so the title starts the column clean. */
  article { position: relative; }
  .page.article .kicker {
    position: absolute;
    top: .95em;
    left: -232px;
    width: 200px;
    margin: 0;
    text-align: right;
  }
}

/* On a phone the '###' prefixes cost a lot of line width — keep them, shrink them. */
@media (max-width: 619px) {
  /* A stack of full-bleed covers turns seven entries into a very long scroll. Lay the
     card out sideways instead: square thumbnail, text beside it. */
  .posts li > a {
    display: grid;
    grid-template-columns: 96px 1fr;
    grid-template-rows: auto auto 1fr;
    column-gap: 12px;
    padding: 12px;
  }
  .posts .cover {
    grid-area: 1 / 1 / 4 / 2;
    align-self: start;
    aspect-ratio: 1;
    margin: 0;
    border: 1px solid var(--border);
  }
  .posts .d, .posts .t, .posts .sum { grid-column: 2; padding: 0; margin: 0; }
  .posts .t { justify-self: start; }

  .cv h3::before, .cv h4::before { font-size: .8em; margin-right: .35em; }
  .cv h3 { flex-wrap: wrap; gap: 0 .6em; }
  .cv h3 .d { font-size: .8em; }
}

/* ---------- paper ----------
   A flat hex value reads as a screen; a little grain reads as stock. Static, fixed and
   behind everything, so it costs one paint and never competes with a line of text.

   The drifting dot field, the ASCII cat and the hard-blinking cursor that were here
   are gone: three moving things on a CV page is a novelty act. What is left is the
   texture and one slow cursor in the header, which is the same terminal convention as
   the '$' beside it. */

body::before {
  content: '';
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  opacity: .035;
  mix-blend-mode: multiply;
  background-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22160%22%20height%3D%22160%22%3E%3Cfilter%20id%3D%22g%22%3E%3CfeTurbulence%20type%3D%22fractalNoise%22%20baseFrequency%3D%220.82%22%20numOctaves%3D%223%22%20stitchTiles%3D%22stitch%22%2F%3E%3CfeColorMatrix%20type%3D%22saturate%22%20values%3D%220%22%2F%3E%3C%2Ffilter%3E%3Crect%20width%3D%22160%22%20height%3D%22160%22%20filter%3D%22url%28%23g%29%22%20opacity%3D%220.5%22%2F%3E%3C%2Fsvg%3E");
  background-size: 160px 160px;
}

.brand::after {
  content: '\2588';
  margin-left: .1em;
  color: var(--text-dim);
  animation: cursor 2.4s ease-in-out infinite;
}
@keyframes cursor { 0%, 44% { opacity: .55; } 62%, 100% { opacity: 0; } }

@media (prefers-reduced-motion: reduce) {
  .brand::after { animation: none; opacity: .45; }
}
