/* 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; }
}

/* --------------------------------------------------------------- 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); }
