/* imadm.ca - personal profile
 *
 * A deliberate sibling of airescue.ca. Same superfamily (IBM Plex Sans +
 * Mono), same rule that machine text is mono and human text is sans, same
 * measure discipline, and since 2026-08-13 the same accent red. This page held
 * a teal until then, to keep it from reading as an airescue landing page. That
 * separation is no longer wanted: the two domains are now linked both ways and
 * declare one owner, so they share a palette. docs/profile-page.md has it.
 *
 * ── Why this file is not called style.css ──────────────────────────────────
 * The 14 legacy posts under /posts/ each carry `href="../../style.css"`, which
 * resolves to /style.css. That path still serves the OLD 2023 stylesheet out
 * of /home/smol/www/imadm.ca (see nginx/imadm.ca.conf). Naming this file
 * style.css would silently hand every one of those pages a stylesheet written
 * for a completely different layout: no error, no log line, just 14 pages
 * quietly rendered wrong. The rename is the fix, and it must stay renamed.
 *
 * ── The theme has three states, not two ────────────────────────────────────
 * No stored choice is NOT the same as "light". With nothing in localStorage
 * the system preference decides, and it changes on its own between day and
 * night. So the palette is declared three times:
 *
 *   :root                                    the light palette, always
 *   @media dark + :root:not([data-theme=light])   system says dark, unoverridden
 *   :root[data-theme="dark"]                 the button said dark
 *
 * The `:not([data-theme="light"])` guard is the load-bearing part. Without it
 * the media query outranks nothing, and a visitor whose system is dark cannot
 * choose light: the button appears to do nothing at all. Same shape as
 * airescue.ca, and for the same reason.
 *
 * This page ran with no JavaScript at all until 2026-08-13, when the toggle
 * and the obfuscated address were added. That reintroduced the CSP hash
 * coupling deliberately: devtools/csp.py --check is now load-bearing again.
 */

/* ---------------------------------------------------------------- fonts -- */

/* The same four subset files airescue serves. Two sans weights carry the whole
 * page; the mono is for labels, dates and anything that names a system. */

@font-face {
  font-family: "IBM Plex Sans";
  src: url(fonts/plexsans-400.woff2) format("woff2");
  font-weight: 400;
  font-display: swap;
}
@font-face {
  font-family: "IBM Plex Sans";
  src: url(fonts/plexsans-600.woff2) format("woff2");
  font-weight: 600;
  font-display: swap;
}
@font-face {
  font-family: "IBM Plex Mono";
  src: url(fonts/plexmono-400-latin.woff2) format("woff2");
  font-weight: 400;
  font-display: swap;
}
@font-face {
  font-family: "IBM Plex Mono";
  src: url(fonts/plexmono-500-latin.woff2) format("woff2");
  font-weight: 500;
  font-display: swap;
}

/* --------------------------------------------------------------- tokens -- */

:root {
  /* Read by the toggle to find the *effective* theme, which is not the same as
   * the stored one: with nothing stored, the system preference wins. Reading a
   * computed custom property is the only way to ask that question without
   * duplicating the media query in JavaScript. */
  --is-dark: 0;

  --paper:   #FFFFFF;
  --surface: #F4F4F3;
  --ink:     #111111;
  --muted:   #6B6B6B;
  --rule:    #E2E2E0;
  --accent:  #C8102E;   /* the only colour on the page */

  --sans:    "IBM Plex Sans", system-ui, -apple-system, sans-serif;
  --mono:    "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

  --measure: 36rem;     /* the widest a paragraph is allowed to be */
}

/* The accent is airescue.ca's red, shared on purpose: one person, one
 * operation. It was a teal until 2026-08-13, and docs/profile-page.md carries
 * why that changed.
 *
 * Measured against the backgrounds they actually sit on: #C8102E is 5.88:1 on
 * white and 5.35:1 on --surface; #EE4B5E is 5.26:1 on the dark paper and
 * 4.81:1 on the dark --surface; --muted is 5.33:1 light / 6.76:1 dark.
 * Nothing on this page drops below AA, including the dates, which are the
 * smallest type here.
 *
 * That 4.81:1 is the tightest number here and it is a real state, not a
 * hypothetical: `.links a:hover` puts accent text on --surface. The teal had
 * more headroom. If either red is ever darkened, check that pair first. */

/* System says dark, and the visitor has not overridden it. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --is-dark: 1;
    --paper:   #101010;
    --surface: #1A1A1A;
    --ink:     #EDEDED;
    --muted:   #9A9A9A;
    --rule:    #2C2C2C;
    --accent:  #EE4B5E;
  }
}

/* The button said dark, whatever the system thinks. Repeated rather than
 * factored into a shared selector list: a CSS selector list is dropped whole
 * if any one selector in it fails to parse, so the media-query variant and
 * this one are kept independent on purpose. */
:root[data-theme="dark"] {
  --is-dark: 1;
  --paper:   #101010;
  --surface: #1A1A1A;
  --ink:     #EDEDED;
  --muted:   #9A9A9A;
  --rule:    #2C2C2C;
  --accent:  #EE4B5E;
}

/* ------------------------------------------------------------- elements -- */

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

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

body {
  margin: 0;
  padding: clamp(2.5rem, 8vw, 5.5rem) clamp(1.25rem, 6vw, 2rem) 4rem;
  background: var(--paper);
  color: var(--ink);
  font: 400 1.0625rem/1.65 var(--sans);
  font-synthesis: none;
}

b, strong { font-weight: 600; }

::selection { background: var(--accent); color: var(--paper); }

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

/* Body links are underlined in grey and inherit ink. The accent is reserved for
 * the things that mean "go here": the mark, and the contact links at the end. */
a { color: inherit; text-decoration-color: var(--muted); text-underline-offset: 3px; }
a:hover { text-decoration-thickness: 2px; }

/* --------------------------------------------------------------- layout -- */

.wrap {
  max-width: 44rem;
  margin-inline: auto;
}

/* ----------------------------------------------------------------- head -- */

.masthead {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 2.5rem;
}

.mark {
  font: 500 0.8125rem/1 var(--mono);
  letter-spacing: 0.08em;
  color: var(--muted);
}
.mark span { color: var(--accent); }

/* Screen-reader-only. Each option carries a real text label this way, so the
 * control announces "Follow system setting" / "Light" / "Dark" rather than
 * leaving a blind user with two unlabelled graphics. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* --------------------------------------------------- theme, three states -- */

/* A fieldset, so it needs its browser defaults stripped before it will sit on
 * one line. */
.themeset {
  display: flex;
  margin: 0;
  padding: 0;
  border: 1px solid var(--rule);
  border-radius: 2px;
  overflow: hidden;
}

.themeopt {
  display: grid;
  place-items: center;
  min-width: 2.5rem;
  height: 1.9rem;
  padding: 0 0.5rem;
  font: 500 0.75rem/1 var(--mono);
  color: var(--muted);
  cursor: pointer;
}

.themeopt + .themeopt { border-left: 1px solid var(--rule); }

/* The input is the accessible control and the label is the visible one, so the
 * input is hidden without leaving the accessibility tree - `display: none`
 * would take it out of both, breaking keyboard and screen-reader use. It is
 * stacked under the label content rather than positioned away, which keeps the
 * focus ring where the eye already is. */
.themeopt input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
  margin: 0;
}

.themeopt:hover { color: var(--ink); }

/* :has() carries the checked state up to the label, which is the element that
 * paints. Supported everywhere this site is checked; where it is not, the
 * control still works and simply shows no selection, which is a degradation
 * rather than a break. */
.themeopt:has(input:checked) {
  background: var(--surface);
  color: var(--ink);
}

.themeopt:has(input:focus-visible) {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

/* The label must be the hit target, never its contents. Without this, the
 * topmost element at the centre of an option is whatever is drawn there - the
 * SVG <path> on the two icons, the <span> on "Sys" - and both of those are
 * aria-hidden decoration. A real click still bubbles up to the label and
 * works, so this is not a user-facing bug; it makes hit-testing honest and it
 * stops anything that targets an element's centre from aiming at a child that
 * is not the control. Found twice on 2026-08-13, on the icon first and then
 * on the text. */
.themeset svg,
.themeopt span { pointer-events: none; }

/* Photo and name on one line. The photo is small on purpose: the source is
 * 281px square, and 5.5rem is 88px, so it is never scaled up. */
header {
  display: flex;
  align-items: center;
  gap: 1.25rem;
}

header img {
  flex: none;
  width: 5.5rem;
  height: auto;
  border-radius: 50%;
}

h1 {
  margin: 0;
  font: 600 clamp(1.75rem, 6vw, 2.875rem)/1.1 var(--sans);
  letter-spacing: -0.02em;
}

.role {
  margin: 0.5rem 0 0;
  max-width: var(--measure);
  font: 400 1rem/1.5 var(--mono);
  color: var(--muted);
}

/* Under about 26rem the photo and a two-word name cannot share a line without
 * the name breaking mid-word, so the photo goes above it. */
@media (max-width: 26rem) {
  header { display: block; }
  header img { margin-bottom: 1rem; }
}

.lead {
  margin: 2rem 0 0;
  max-width: var(--measure);
  font-size: 1.1875rem;
  line-height: 1.6;
}

.lead + .lead { margin-top: 1.25rem; }

/* ------------------------------------------------------------- sections -- */

section {
  margin-top: 3.5rem;
  padding-top: 1.75rem;
  border-top: 1px solid var(--rule);
}

h2 {
  margin: 0 0 1.5rem;
  font: 500 0.75rem/1 var(--mono);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
}

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

/* One row per job, project or affiliation. The name is the only sans-600 on
 * the row; the role and the dates are mono, which is the same sans/mono split
 * the rest of the site uses -- a job title is a label, not prose. */

.entry {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 0.25rem 1.5rem;
  align-items: baseline;
  padding: 1rem 0;
  border-bottom: 1px solid var(--rule);
}
.entry:first-of-type { padding-top: 0; }
.entry:last-of-type  { border-bottom: 0; padding-bottom: 0; }

.entry-name {
  font-weight: 600;
  font-size: 1.0625rem;
}

.entry-when {
  font: 400 0.8125rem/1.4 var(--mono);
  color: var(--muted);
  white-space: nowrap;
}

.entry-what {
  grid-column: 1 / -1;
  margin: 0;
  max-width: var(--measure);
  color: var(--muted);
  font-size: 0.9688rem;
  line-height: 1.55;
}

/* The dates sit under the name on narrow screens rather than squeezing the
 * name into a column too thin to hold a company name. */
@media (max-width: 30rem) {
  .entry { grid-template-columns: 1fr; }
  .entry-when { white-space: normal; }
}

/* ---------------------------------------------------------------- posts -- */

/* Everything from here down serves the generated pages under /posts/. The
 * profile page uses none of it except a.mark. */

/* The mark is a <span> on the profile page and an <a> home on every generated
 * page. Anchors here inherit the body rule, which underlines them in grey, so
 * the link form needs the underline taken off to look like the span form. */
a.mark { text-decoration: none; }
a.mark:hover { color: var(--ink); }

/* `header { display: flex }` further up is an element selector: it matches the
 * post title block too, and without this override the title and the date sit
 * side by side on one row instead of stacking. */
.post-head { display: block; }

/* Smaller than the name on the profile page. A post title is a heading on a
 * page, not the subject of the whole site. */
.post-head h1 { font-size: clamp(1.625rem, 5vw, 2.25rem); }

.post-meta {
  margin: 0.75rem 0 0;
  font: 400 0.8125rem/1.4 var(--mono);
  color: var(--muted);
}

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

/* --------------------------------------------------------------- prose -- */

/* Spacing is owned by the parent, not by each element: every direct child gets
 * its margins cleared, then a following sibling gets the gap back. Markdown
 * emits a long and open-ended list of element types, and this way a new one -
 * a <dl>, a <figure> - is spaced correctly the first time it appears rather
 * than the first time someone notices it is not.
 *
 * The three overrides below are more specific than the sibling rule, so they
 * win regardless of order. */
.prose {
  margin-top: 2.5rem;
  max-width: var(--measure);
}

.prose > * { margin: 0; }
.prose > * + * { margin-top: 1.25rem; }
.prose > h2 { margin-top: 2.75rem; }
.prose > h3 { margin-top: 2rem; }
.prose > hr { margin-top: 2.5rem; }

/* h2 is a section label everywhere else on this site: mono, uppercase, 0.75rem,
 * muted. Inside prose it has to be a heading again, so every one of those five
 * declarations is unset here rather than only the obvious ones. */
.prose h2 {
  font: 600 1.375rem/1.3 var(--sans);
  letter-spacing: -0.01em;
  text-transform: none;
  color: var(--ink);
}

.prose h3 {
  font: 600 1.0625rem/1.4 var(--sans);
  color: var(--ink);
}

.prose ul,
.prose ol { padding-left: 1.25rem; }
.prose li + li { margin-top: 0.4rem; }

.prose blockquote {
  padding-left: 1rem;
  border-left: 2px solid var(--rule);
  color: var(--muted);
}
.prose blockquote p { margin: 0; }
.prose blockquote p + p { margin-top: 0.75rem; }

.prose hr {
  border: 0;
  border-top: 1px solid var(--rule);
}

.prose img {
  max-width: 100%;
  height: auto;
  border-radius: 2px;
}

/* A markdown table is emitted bare, with nothing around it to scroll. Making
 * the table itself the scroll container is the only fix that does not require
 * wrapping every table by hand in the source. */
.prose table {
  display: block;
  overflow-x: auto;
  border-collapse: collapse;
  font-size: 0.9375rem;
}
.prose th,
.prose td {
  padding: 0.45rem 0.7rem 0.45rem 0;
  text-align: left;
  vertical-align: top;
  border-bottom: 1px solid var(--rule);
}
.prose th {
  font: 500 0.75rem/1.4 var(--mono);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  white-space: nowrap;
}

/* Footnotes: python-markdown emits <div class="footnote"> with its own <hr>. */
.prose .footnote {
  margin-top: 3rem;
  font-size: 0.9375rem;
  color: var(--muted);
}
.prose .footnote ol { padding-left: 1.1rem; }
.prose .footnote li + li { margin-top: 0.5rem; }

/* ----------------------------------------------------------------- code -- */

/* em, not rem: inline code inside a heading should track the heading's size. */
.prose code {
  padding: 0.1em 0.3em;
  border: 1px solid var(--rule);
  border-radius: 2px;
  background: var(--surface);
  font: 400 0.875em/1.5 var(--mono);
}

/* The block, from the codehilite extension. It keeps the prose measure and
 * scrolls sideways rather than breaking out of it: at this size the measure
 * holds about 72 characters, which is a line length code is already written
 * for. */
.prose .codehilite {
  padding: 0.9rem 1rem;
  border: 1px solid var(--rule);
  border-radius: 2px;
  background: var(--surface);
  overflow-x: auto;
}
.prose .codehilite pre { margin: 0; }

/* Inside a block the border and background belong to the container, so the
 * inline-code decoration has to come back off. */
.prose .codehilite code {
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  font-size: 0.8125rem;
  line-height: 1.6;
}

/* Highlighting, mapped onto the three colours this site already has. No new
 * hues: the accent is still the only colour on the page, and the contrast
 * pairs are the ones already measured in the token block above - accent on
 * --surface is the tightest at 4.81:1 dark, and every string and number below
 * sits on exactly that.
 *
 * Comments are muted and upright, never italic. `font-synthesis: none` on the
 * body means an italic that the font does not have is simply not drawn, and
 * the Plex Mono subsets here are regular and medium only - so an italic rule
 * would be silently ignored on this site while looking correct on another. */
.prose .c, .prose .c1, .prose .cm, .prose .ch, .prose .cs, .prose .cp {
  color: var(--muted);
}
.prose .k, .prose .kc, .prose .kd, .prose .kn, .prose .kp, .prose .kr,
.prose .kt, .prose .ow, .prose .nt {
  font-weight: 500;
}
.prose .s, .prose .s1, .prose .s2, .prose .sb, .prose .sc, .prose .sd,
.prose .se, .prose .sh, .prose .si, .prose .sx, .prose .sr, .prose .ss,
.prose .m, .prose .mi, .prose .mf, .prose .mh, .prose .mo, .prose .il {
  color: var(--accent);
}
.prose .nf, .prose .fm { font-weight: 500; }
.prose .o, .prose .p, .prose .na { color: var(--muted); }
.prose .err { color: var(--accent); }
.prose .gd { color: var(--muted); }
.prose .gi { color: var(--accent); }

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

.post-nav {
  margin-top: 3.5rem;
  padding-top: 1.75rem;
  border-top: 1px solid var(--rule);
  font: 500 0.9375rem/1 var(--mono);
}
.post-nav a {
  color: var(--accent);
  text-decoration: none;
}
.post-nav a:hover { text-decoration: underline; }

/* --------------------------------------------------------------- links -- */

.links {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.links a {
  display: inline-block;
  padding: 0.5rem 0.9rem;
  border: 1px solid var(--rule);
  border-radius: 2px;
  font: 500 0.9375rem/1 var(--mono);
  color: var(--accent);
  text-decoration: none;
}
.links a:hover {
  border-color: var(--accent);
  background: var(--surface);
}

/* Only ever rendered with JavaScript off, so the address is spelled out rather
 * than linked: a harvester that runs no script cannot read it, and a human
 * can. */
.nojs {
  margin: 1rem 0 0;
  font: 400 0.9375rem/1.5 var(--mono);
  color: var(--muted);
}

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

footer {
  margin-top: 3.5rem;
  padding-top: 1.75rem;
  border-top: 1px solid var(--rule);
  font: 400 0.875rem/1.6 var(--mono);
  color: var(--muted);
}

footer a { text-decoration-color: var(--rule); }
