/* ==========================================================================
   dsy.sh — virtual business card
   Centred masthead: photo, name, then role and company. Colour lives in the
   link buttons — every fill carries a text colour verified to clear WCAG AA.
   "Things I do" is the signature block. Background is a fine engineering dot
   grid rather than a flat field.
   ========================================================================== */

/* --------------------------------------------------------------- fonts --- */
@font-face {
  font-family: "Archivo Expanded";
  src: url("fonts/archivo-expanded-700.woff2") format("woff2");
  font-weight: 700;
  font-stretch: 125%;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "IBM Plex Sans";
  src: url("fonts/plex-sans-var.woff2") format("woff2");
  font-weight: 100 700; /* variable file — one download covers 400/500/600 */
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "IBM Plex Mono";
  src: url("fonts/plex-mono-500.woff2") format("woff2");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}

/* --------------------------------------------------------------- tokens --- */
/* Light is the base. Dark is applied by two selectors — the system preference
   (unless the toggle has forced light) and the toggle's own data-theme. Plain
   CSS cannot share one declaration block across two selectors, so the dark
   values are written twice on purpose; keep the two blocks in sync. */
:root {
  --font-display: "Archivo Expanded", "Archivo", system-ui, sans-serif;
  --font-body: "IBM Plex Sans", system-ui, -apple-system, sans-serif;
  --font-mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

  --paper: #f4f6f8;
  --surface: #ffffff;
  --ink: #101418;
  --ink-2: #333b45;
  --meta: #545e6b;
  --accent: #0f6e5a;
  --on-accent: #ffffff;
  --rule: #dde2e8;
  --rule-strong: #c3cbd4;
  --dot: rgb(16 20 24 / 0.16);   /* light paper needs more punch to read */
  --dot-size: 2px;
  --grid: 34px;
  --glow: rgb(15 110 90 / 0.10);
  --shade: rgb(16 20 24 / 0.55);

  --radius: 5px;
  --s1: 0.25rem; --s2: 0.5rem;  --s3: 0.75rem; --s4: 1rem;
  --s5: 1.5rem;  --s6: 2rem;    --s7: 2.75rem;

  --tap: 44px;
  color-scheme: light dark;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --paper: #101725;
    --surface: #18202f;
    --ink: #eef2f7;
    --ink-2: #dde4ec;  /* 12.7:1 — near-white, was a mid grey */
    --meta: #c2ccd8;   /* 10.0:1 — was 6.6:1 and read as faint grey */
    --accent: #4ccfad;
    --on-accent: #101725;
    --rule: #2a3444;
    --rule-strong: #44536a;
    --dot: rgb(255 255 255 / 0.10);
    --glow: rgb(76 207 173 / 0.10);
    --shade: rgb(4 7 13 / 0.72);
  }
  :root:not([data-theme="light"]) .b-x      { --fill: #eef1f5; --on-fill: #101418; }
  :root:not([data-theme="light"]) .b-github { --fill: #dfe4ea; --on-fill: #101418; }
}

:root[data-theme="dark"] {
  --paper: #101725;
  --surface: #18202f;
  --ink: #eef2f7;
  --ink-2: #dde4ec;
  --meta: #c2ccd8;
  --accent: #4ccfad;
  --on-accent: #101725;
  --rule: #2a3444;
  --rule-strong: #44536a;
  --dot: rgb(255 255 255 / 0.10);
  --glow: rgb(76 207 173 / 0.10);
  --shade: rgb(4 7 13 / 0.72);
}
:root[data-theme="dark"] .b-x      { --fill: #eef1f5; --on-fill: #101418; }
:root[data-theme="dark"] .b-github { --fill: #dfe4ea; --on-fill: #101418; }

/* Keep native UI (scrollbars, form controls) in step with a forced theme. */
:root[data-theme="light"] { color-scheme: light; }
:root[data-theme="dark"]  { color-scheme: dark; }

/* ---------------------------------------------------------------- reset --- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  min-height: 100vh;
  padding: var(--s5) var(--s4) var(--s7);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 1rem;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;

  /* Fine dot grid — engineering paper, not a flat field. Pure CSS, no request.
     A soft accent glow at the top keeps the page from reading as a slab. */
  background-color: var(--paper);
  background-image:
    radial-gradient(58rem 26rem at 50% -6rem, var(--glow), transparent 72%),
    radial-gradient(circle at center, var(--dot) var(--dot-size), transparent 0);
  background-size: auto, var(--grid) var(--grid);
  background-repeat: no-repeat, repeat;
  background-attachment: fixed, fixed;
}

img, svg { display: block; max-width: 100%; }

/* Sprite paths carry no fill attribute, so without this they render SVG's
   default black and `color` has no effect — icons vanished on the black fills. */
svg { fill: currentColor; }

ul { margin: 0; padding: 0; list-style: none; }
dl, dd, dt, figure, p, h1, h2, h3, h4 { margin: 0; }
a { color: inherit; }

.sr-only {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap; border: 0;
}

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

/* ----------------------------------------------------------------- card --- */
.card {
  position: relative; /* anchors the theme toggle */
  max-width: 34rem;
  margin-inline: auto;
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: var(--s6) var(--s5) var(--s5);
}

@media (min-width: 34em) {
  .card { padding: var(--s7) var(--s6) var(--s6); }
}

/* ---------------------------------------------------------- theme toggle --- */
.theme-toggle {
  position: absolute;
  top: var(--s2);
  right: var(--s2);
  width: var(--tap); height: var(--tap);
  display: grid;
  place-items: center;
  background: none;
  border: 0;
  border-radius: var(--radius);
  color: var(--meta);
  cursor: pointer;
}

.theme-toggle:hover {
  color: var(--accent);
  background: color-mix(in oklab, var(--ink) 8%, transparent);
}

.theme-toggle .icon { width: 19px; height: 19px; }

/* -------------------------------------------------------------- masthead --- */
/* Photo, then name, then role and company — centred. The data blocks below
   stay left-aligned, because a spec sheet has to align to read. */
.masthead {
  text-align: center;
  margin-bottom: var(--s5);
}

.avatar {
  width: 144px; height: 144px;
  margin: 0 auto var(--s4);
  border-radius: 50%;
  object-fit: cover;
  border: 1px solid var(--rule-strong);
}

.name {
  font-family: var(--font-display);
  font-weight: 700;
  font-stretch: 125%;
  font-size: clamp(1.875rem, 9.5vw, 2.75rem);
  line-height: 0.94;
  letter-spacing: -0.015em;
  text-transform: uppercase;
  margin-bottom: var(--s3);
  overflow-wrap: break-word;
}

/* Role and employer on one line. Sized to hold its own against the name, but
   it scales down on narrow screens so the single line survives as long as
   possible before wrapping (51 characters cannot fit a 320px phone). */
.eyebrow {
  font-family: var(--font-mono);
  /* 51 characters at 15px overran the 480px card by ~10px and wrapped. 14px
     with tighter tracking fits on one line at card width; on a phone it must
     wrap regardless, so the separators are bound with &nbsp; in the markup. */
  font-size: clamp(0.6875rem, 2.9vw, 0.875rem);
  font-weight: 500;
  line-height: 1.65;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--meta);
  text-wrap: balance; /* if it must wrap, wrap evenly under a centred name */
}

.eyebrow a {
  color: var(--accent);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  text-decoration-color: color-mix(in oklab, var(--accent) 45%, transparent);
}

.eyebrow a:hover { text-decoration-color: currentColor; }

/* ------------------------------------------------------------------ creed --- */
/* The one-line thesis, given hero treatment: a tinted panel, a step up in
   size, and a deliberate break between the two clauses rather than whatever
   the viewport decides. */
.creed {
  margin: 0 0 var(--s6);
  padding: var(--s5) var(--s4);
  background: color-mix(in oklab, var(--accent) 8%, transparent);
  border: 1px solid color-mix(in oklab, var(--accent) 22%, transparent);
  border-radius: var(--radius);
  text-align: center;
}

/* Three lines of one sentence: two quoted statements with "so" between them.
   All three share the same face, size, weight and colour — the connective is
   part of the sentence, not a label about it. Minimum size is set by the
   longest line (31 characters) against a 320px screen. */
.creed-line,
.creed-pivot {
  margin: 0 auto;
  max-width: 30ch;
  font-size: clamp(0.8125rem, 4.2vw, 1.625rem);
  font-weight: 500;
  line-height: 1.3;
  letter-spacing: -0.012em;
  color: var(--ink);
}

.creed-pivot { margin-block: var(--s1); }

/* ---------------------------------------------------- section rule/label --- */
.rule {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s2) var(--s3);
  margin-bottom: var(--s3);
}

.rule-label,
.rule-meta {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--meta);
  flex: none;
}

/* min-width keeps the hairline visible instead of collapsing to nothing when
   the label and meta text nearly fill the row on narrow screens. */
.rule-line { flex: 1 1 auto; min-width: 1.5rem; height: 1px; background: var(--rule-strong); }

/* ----------------------------------------------------------- things I do --- */
.scope {
  margin-bottom: var(--s6);
  display: grid;
  grid-template-columns: minmax(9.5rem, max-content) 1fr;
  /* No column-gap, so each row's two top borders meet as one hairline. */
  column-gap: 0;
}

.scope dt,
.scope dd {
  padding: var(--s4) 0;
  border-top: 1px solid var(--rule);
}

/* Label sits optically centred against its sentence instead of hanging from
   the top of a two-line row. */
.scope dt {
  display: flex;
  align-items: center;
  padding-right: var(--s4);
  font-family: var(--font-mono);
  font-size: 0.75rem;
  font-weight: 500;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--accent);
  line-height: 1.5;
}

.scope dt:first-of-type,
.scope dt:first-of-type + dd { border-top: 0; padding-top: 0; }

.scope dd { font-size: 0.9375rem; line-height: 1.5; color: var(--ink-2); }

/* The detail lines are sentences, so they need full width sooner. */
@media (max-width: 34em) {
  .scope { display: block; }
  .scope dt { display: block; border-top: 1px solid var(--rule); padding-bottom: 0; }
  .scope dt:first-of-type { border-top: 0; }
  .scope dd { border-top: 0; padding-top: var(--s1); }
  .scope dt:first-of-type + dd { padding-top: var(--s1); }
}

/* ---------------------------------------------------------------- links --- */
/* Filled colour buttons. Each row sets --fill and --on-fill; every pair is
   contrast-checked, and where a brand colour failed with both white and dark
   text it was darkened until white passed (Facebook). Amazon's orange takes
   dark text at 8.6:1 — it was the worst offender at 2.1:1 before. */
.links {
  display: grid;
  gap: var(--s2);
  margin-bottom: var(--s6);
}

.link {
  display: flex;
  align-items: center;
  gap: var(--s3);
  width: 100%;
  min-height: 48px;
  padding: var(--s3) var(--s4);
  background-color: var(--fill);
  color: var(--on-fill);
  border: 0;
  border-radius: var(--radius);
  text-decoration: none;
  font-family: var(--font-body);
  font-size: 0.9375rem;
  font-weight: 500;
  line-height: 1.4;
  text-align: left;
  cursor: pointer;
  transition: background-image 140ms ease, transform 120ms ease;
}

.link .icon { width: 18px; height: 18px; flex: none; }
.link-label { flex: 1 1 auto; }
.link .chev { width: 13px; height: 13px; flex: none; opacity: 0.6; transition: transform 160ms ease; }

/* Hover washes the fill with its own text colour. Because --on-fill is always
   the contrasting choice, this darkens light fills and lightens dark ones — so
   black X and off-white X both get a visible state from one rule. */
.link:hover {
  background-image: linear-gradient(
    color-mix(in srgb, currentColor 15%, transparent),
    color-mix(in srgb, currentColor 15%, transparent));
}
.link:hover .chev { transform: translate(2px, -2px); }
.link:active { transform: translateY(1px); }

/* The accent ring would vanish on the green and teal fills, so the link ring
   uses --ink and sits offset on the card surface instead. */
.link:focus-visible {
  outline: 3px solid var(--ink);
  outline-offset: 2px;
}

.b-fund      { --fill: #0f6e5a; --on-fill: #ffffff; } /* 6.18:1 */
.b-reading   { --fill: #b45309; --on-fill: #ffffff; } /* 5.02:1 */
.b-blog      { --fill: #c2410c; --on-fill: #ffffff; } /* 5.18:1 */
.b-website   { --fill: #6d28d9; --on-fill: #ffffff; } /* 7.10:1 */
.b-linkedin  { --fill: #0a66c2; --on-fill: #ffffff; } /* 5.69:1 */
.b-x         { --fill: #000000; --on-fill: #ffffff; } /* 21.0:1 */
.b-github    { --fill: #181717; --on-fill: #ffffff; } /* 17.9:1 */
.b-instagram { --fill: #c13584; --on-fill: #ffffff; } /* 5.11:1 */
.b-facebook  { --fill: #1671e5; --on-fill: #ffffff; } /* 4.64:1 */
.b-amazon    { --fill: #ff9900; --on-fill: #101418; } /* 8.64:1 */
/* .b-schedule was #15803d (5.02:1) — re-add with the calendar icon if the
   booking flow gets built into the site later. */

/* -------------------------------------------------------------- contact --- */
.facts {
  margin-bottom: var(--s4);
  display: grid;
  grid-template-columns: minmax(5rem, auto) 1fr;
  column-gap: var(--s4);
  row-gap: var(--s2);
  font-size: 0.9375rem;
}

.facts dt {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--meta);
  line-height: 2.2;
}

/* min-width:0 lets the grid track shrink; overflow-wrap is the last resort for
   the email address, which is one 25-character word with no break opportunity
   and pushed the page 12px wide at 320px. */
.facts dd { color: var(--ink-2); min-width: 0; overflow-wrap: anywhere; }

/* Stack label over value on narrow screens so the address gets full width. */
@media (max-width: 34em) {
  .facts { display: block; }
  .facts dt { line-height: 1.6; margin-top: var(--s3); }
  .facts dt:first-of-type { margin-top: 0; }
}

.facts a {
  /* inline-flex + min-height clears the 24px WCAG 2.5.8 floor */
  display: inline-flex;
  align-items: center;
  min-height: 24px;
  color: var(--ink);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
  text-decoration-color: var(--rule-strong);
}

.facts a:hover { text-decoration-color: var(--accent); color: var(--accent); }

.save {
  width: 100%;
  min-height: var(--tap);
  margin-bottom: var(--s5);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s2);
  padding: var(--s3) var(--s4);
  background: var(--accent);
  color: var(--on-accent);
  border: 0;
  border-radius: var(--radius);
  font-family: var(--font-body);
  font-size: 0.9375rem;
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
  transition: background-color 140ms ease, transform 120ms ease;
}

.save:hover { background: color-mix(in oklab, var(--accent) 86%, var(--ink)); }
.save:active { transform: translateY(1px); }
.save .icon { width: 18px; height: 18px; }

/* --------------------------------------------------------------- footer --- */
.foot {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2) var(--s4);
  align-items: baseline;
  justify-content: space-between;
  padding-top: var(--s4);
  border-top: 1px solid var(--rule);
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--meta);
}

.foot button {
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  color: var(--meta);
  background: none;
  border: 0;
  padding: var(--s2) 0;
  min-height: var(--tap);
  cursor: pointer;
  text-decoration: underline;
  text-decoration-color: var(--rule-strong);
  text-underline-offset: 3px;
}

.foot button:hover { color: var(--accent); text-decoration-color: var(--accent); }

/* --------------------------------------------------------------- dialog --- */
.reading {
  width: min(32rem, calc(100vw - 2rem));
  max-height: min(85vh, 44rem);
  padding: 0;
  border: 1px solid var(--rule-strong);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--ink);
  overflow: auto;
  overscroll-behavior: contain;
}

.reading::backdrop { background: var(--shade); }

.reading-head {
  position: sticky;
  top: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s3);
  padding: var(--s4) var(--s5);
  background: var(--surface);
  border-bottom: 1px solid var(--rule);
}

.reading-title {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--meta);
}

.close {
  width: var(--tap); height: var(--tap);
  flex: none;
  display: grid;
  place-items: center;
  margin: calc(var(--s2) * -1) calc(var(--s2) * -1) calc(var(--s2) * -1) 0;
  background: none;
  border: 0;
  border-radius: var(--radius);
  color: var(--meta);
  cursor: pointer;
}

.close:hover { color: var(--ink); background: color-mix(in oklab, var(--ink) 7%, transparent); }
.close .icon { width: 17px; height: 17px; }

.reading-body { padding: var(--s5); display: grid; gap: var(--s5); }

.book { display: grid; grid-template-columns: 74px 1fr; gap: var(--s4); }

.book img {
  width: 74px;
  height: auto;
  border-radius: 3px;
  border: 1px solid var(--rule);
}

.book-kind {
  font-family: var(--font-mono);
  font-size: 0.625rem;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: var(--s1);
}

.book-title { font-size: 0.9375rem; font-weight: 600; line-height: 1.35; }
.book-author { font-size: 0.8125rem; color: var(--meta); margin-bottom: var(--s2); }
.book-why { font-size: 0.875rem; color: var(--ink-2); line-height: 1.5; }
.book-why b { font-weight: 600; color: var(--ink); }

@media (max-width: 26em) {
  .book { grid-template-columns: 58px 1fr; gap: var(--s3); }
  .book img { width: 58px; }
  .reading-body, .reading-head { padding-inline: var(--s4); }
}

/* ------------------------------------------------------- motion / print --- */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

@media print {
  body { background: #fff; color: #000; padding: 0; }
  .card { border: 0; max-width: none; padding: 0; }
  .save, .chev, .foot button, .reading, .theme-toggle { display: none; }
  .links { gap: 0; }
  .link {
    background: none !important;
    color: #000 !important;
    min-height: 0;
    padding: 2px 0;
  }
  .link::after {
    content: " " attr(href);
    font-family: var(--font-mono);
    font-size: 0.6rem;
    color: #555;
  }
}
