/* Shared by index.html and artefacts.html: tokens, base type and grid, the
   sticky header with its theme switch, the case rail layout, the footer and
   the paper grain. Anything only one page uses stays in that page. */

/* FONT-STACK */
:root {
  --display-font: "Fraunces", Georgia, serif;
  --body-font: "Newsreader", Georgia, "Times New Roman", serif;
  --body-size: 19px;
  --body-leading: 1.6;
  --label-font: "Segoe UI", Helvetica, Arial, sans-serif;
}
/* Dark for everyone. Light is opt-in via the toggle, which sets
   data-theme="light" on <html> and remembers it in localStorage. There is
   deliberately no prefers-color-scheme rule: a device set to light would
   otherwise override the choice this page is making. */
:root {
  --ink: #e9eaec;
  --steel: #99a2a9;
  --line: #333a40;
  --accent: #71c294;
  --rule: var(--accent);
  --bg: #16191c;
  --route: var(--accent);
  --time: #99a1ab;
  --slow: #8b939d;
  --label: var(--accent);
  --after: var(--ink);
  --focus: var(--accent);
  /* the mat behind a screenshot, and how much the screenshot is calmed down */
  --mat: #1c2025;
  --shot-dim: 0.88;
}
:root[data-theme="light"] {
  --ink: #15181c;
  --steel: #6b7280;
  --line: #dcdfe3;
  --accent: #1c5a3a;
  --bg: #ffffff;
  --time: #5f6771;
  --slow: #7a828c;
  --after: var(--ink);
  /* In light the mat is a hair off-white: enough to separate the screenshot
     from the page without turning it back into a card. No dimming: there is
     no glare to take off a light screenshot on a light page. */
  --mat: #f4f5f6;
  --shot-dim: 1;
}
/* THEME-END */
:root {
  --page-max: 1400px;
  --side: 210px;
  --main: 640px;
  --gutter: clamp(28px, 4.5vw, 68px);
  --edge-l: clamp(20px, 9vw, 168px);
  --edge-r: clamp(20px, 4vw, 56px);
}
* { box-sizing: border-box; }
body {
  /* Past 1400px the grid stops growing and centres, so the asymmetry stays
     inside a fixed measure instead of stranding the text in the left quarter
     of a wide window while the rules run the full viewport. */
  max-width: var(--page-max);
  margin: 0 auto;
  background: var(--bg);
  color: var(--ink);
  font: var(--body-size)/var(--body-leading) var(--body-font);
  padding: 0 var(--edge-r) 0 var(--edge-l);
}
h1, h2, h3 {
  font-family: var(--display-font);
  font-optical-sizing: auto;
  line-height: 1.12;
  margin: 0;
  font-weight: 600;
}
/* Sized just above the case study headings, not far above them: the work
   should carry the emphasis, and the name only has to read as the top of
   the page. The artefacts page's title uses the same size. */
h1 {
  font-size: clamp(1.9rem, 2.6vw, 2.2rem);
  font-weight: 600;
  letter-spacing: -0.01em;
  line-height: 1.04;
}
p { margin: 0 0 1em; }
p:last-child { margin-bottom: 0; }
a { color: var(--route); text-underline-offset: 3px; }
a:focus-visible { outline: 3px solid var(--focus); outline-offset: 3px; }

/* Two-track band. Headings and labels sit left, prose sits right. */
.band {
  display: grid;
  grid-template-columns: minmax(120px, var(--side)) minmax(0, var(--main));
  column-gap: var(--gutter);
}
.band > .side { grid-column: 1; text-align: left; }
.band > .main { grid-column: 2; }
.band > .full { grid-column: 1 / -1; }
.rule-top { border-top: 2px solid var(--rule); margin-top: clamp(48px, 8vh, 90px); }

/* ---- header ---- */
/* Only the name line is pinned (name and LinkedIn on the left, email and the
   theme switch on the right): the tagline and intro would cover most of a
   phone screen if they stayed too. The space above the name is a margin,
   not padding, so it scrolls away with the page instead of being pinned.
   The negative side margins carry the solid background out over the body's
   edge padding, so text can't show beside the bar either. */
header {
  position: sticky; top: 0; z-index: 10;
  background: var(--bg);
  margin: calc(clamp(56px, 11vh, 118px) - 14px) calc(-1 * var(--edge-r)) 0 calc(-1 * var(--edge-l));
  padding: 14px var(--edge-r) 18px var(--edge-l);
  transition: padding 0.25s ease, box-shadow 0.25s ease;
}
/* The same grain as the page, so the bar doesn't read as a darker strip. */
header::before {
  content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none;
  opacity: var(--grain); background-image: var(--grain-img);
}
header.scrolled {
  padding-top: 9px; padding-bottom: 9px;
  box-shadow: 0 1px 0 var(--line), 0 6px 16px rgba(0, 0, 0, 0.18);
}
/* The name. On the front page it is the h1; on the artefacts page, where the
   page has its own title, it is a plain line. Both get the same look. */
.hdr-name {
  font-family: var(--display-font);
  font-optical-sizing: auto;
  margin: 0;
  font-size: clamp(1.9rem, 2.6vw, 2.2rem);
  font-weight: 600;
  letter-spacing: -0.01em;
  line-height: 1.04;
  transition: font-size 0.25s ease;
}
header.scrolled .hdr-name { font-size: calc(clamp(1.9rem, 2.6vw, 2.2rem) * 0.62); }

/* The name starts flush with the headings' left edge, full size or shrunk;
   the second track runs to the right edge for the icons and switch. */
header.band {
  grid-template-columns: minmax(120px, var(--side)) minmax(0, 1fr);
  align-items: center;
}
header .side { display: flex; align-items: baseline; justify-content: flex-start; }
header .side .hdr-name { white-space: nowrap; }
/* The email and LinkedIn icons are a matched pair: both square, both sized
   from the name's font size so they scale and shrink with it, and spaced by
   the same gap. The ::after widens each tap area without moving anything,
   and stays short of the gap so neighbouring tap areas never overlap. */
.hdr-icon {
  position: relative; display: block; color: var(--ink);
  font-size: clamp(1.9rem, 2.6vw, 2.2rem); line-height: 1;
  transition: font-size 0.25s ease;
}
.hdr-icon svg { display: block; width: 0.6em; height: 0.6em; fill: currentColor; }
.hdr-icon::after { content: ""; position: absolute; inset: -6px; }
.hdr-icon:hover { color: var(--route); }
header.scrolled .hdr-icon { font-size: calc(clamp(1.9rem, 2.6vw, 2.2rem) * 0.62); }
/* Email links are completed by the script; until then they stay hidden. */
a[data-u][hidden] { display: none; }
/* The name is a link (back to the top on the front page, home from the
   artefacts page) but keeps the heading's look: no colour or underline, just
   a slight fade on hover. Keyboard focus uses the site's usual outline. */
.hdr-top { color: inherit; text-decoration: none; transition: opacity 0.15s ease; }
.hdr-top:hover { opacity: 0.75; }
/* Set only while the name's back-to-top scroll runs: the header growing back
   near the top would otherwise make the browser push the page down again,
   so it stopped short of the top. */
html.to-top, html.to-top body { overflow-anchor: none; }
.hdr-end {
  grid-column: 2; justify-self: end;
  display: flex; align-items: center; gap: 22px;
}

/* Theme switch. A real button with role="switch", so Enter and Space work
   and it sits in the tab order; aria-checked carries the state and the label
   stays "Light theme" rather than swapping under the user. Square-cornered
   like everything else on the page. The mark for the current theme brightens. */
.themeswitch {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px; margin: -4px; border: 0; background: transparent;
  color: var(--steel); cursor: pointer;
}
.themeswitch svg { display: block; width: 13px; height: 13px; }
.ts-track {
  position: relative; display: block; width: 34px; height: 18px;
  border: 1px solid var(--steel);
}
.ts-thumb {
  position: absolute; top: 2px; left: 2px; width: 12px; height: 12px;
  background: var(--steel); transition: transform 0.2s ease;
}
.themeswitch[aria-checked="true"] .ts-thumb { transform: translateX(16px); background: var(--ink); }
.themeswitch[aria-checked="false"] .ts-moon,
.themeswitch[aria-checked="true"] .ts-sun { color: var(--ink); }
.themeswitch:hover .ts-track { border-color: var(--ink); }
.themeswitch:focus-visible { outline: 3px solid var(--focus); outline-offset: 3px; }

@media (max-width: 860px) {
  header.band { grid-template-columns: auto minmax(0, 1fr); }
}
/* On phones each icon gets a 40px-square tap area without looking any
   bigger: the link is 40px wide with the icon centred in it, and its ::after
   runs 40px tall, above and below the header's content, so the header keeps
   its height. The two links sit side by side, so their tap areas never
   overlap. The margin after LinkedIn keeps the space either side of it equal,
   and moves with the icon's size as the header shrinks. */
@media (max-width: 600px) {
  .hdr-end { gap: 0; }
  .hdr-icon { display: flex; justify-content: center; width: 40px; }
  /* nudged down 1px: in the shrunk header the icons sit 19px from the top of
     the screen, and a centred area would lose its top pixel off the edge */
  .hdr-icon::after { inset: auto 0; top: 50%; height: 40px; transform: translateY(calc(-50% + 1px)); }
  .hdr-in { margin-right: calc(20px - 0.3em); }
}
/* At 360px and below there isn't room for the name, two 40px tap areas and
   the full switch, so the switch drops its sun and moon marks; the track,
   the thumb and the switch's label still carry the state. */
@media (max-width: 360px) {
  .themeswitch .ts-moon, .themeswitch .ts-sun { display: none; }   /* beats .themeswitch svg */
}
@media (prefers-reduced-motion: reduce) { header, .hdr-name, .hdr-icon, .hdr-top, .ts-thumb { transition: none; } }
@media print {
  header { position: static; box-shadow: none; }
  header.scrolled .hdr-name, header.scrolled .hdr-icon { font-size: clamp(1.9rem, 2.6vw, 2.2rem); }
  .themeswitch { display: none; }
}

/* ---- case rails ---- */
/* On desktop each case is a rail and a story. The rail (label, heading, key
   points) is wider than the page's side column so the points read as lines
   rather than a narrow ragged strip. It pins below the shrunk header while
   its case is on screen and lets go at the end of the case; the script only
   adds .pin when the rail fits in the window, so a tall one just scrolls. */
@media (min-width: 861px) {
  .study { grid-template-columns: clamp(210px, 24vw, 300px) minmax(0, var(--main)); }
  .study > .side { align-self: start; }
  .study > .side.pin { position: sticky; top: calc(var(--hdr-h, 42px) + 28px); }
}

/* ---- footer ---- */
footer {
  border-top: 2px solid var(--rule);
  margin-top: clamp(56px, 9vh, 100px);
  padding: 26px 0 80px;
  color: var(--steel);
  font-size: 0.88rem;
}
footer p { max-width: 62ch; }
/* The footer's only line: copyright, and the note on screenshots and code. */
.copyright { margin-top: 0; font-size: 0.8rem; line-height: 1.5; color: var(--steel); }

/* ---- narrow screens: one column ---- */
@media (max-width: 860px) {
  :root { --edge-l: clamp(18px, 5vw, 32px); }
  body { font-size: 18px; }
  .band { grid-template-columns: minmax(0, 1fr); row-gap: 0; }
  .band > .side, .band > .main, .band > .full { grid-column: 1; text-align: left; }
}

/* Paper grain. Generated by an SVG filter in a data URI, so nothing external
   loads: about 498 bytes of CSS, no request. It sits on a fixed layer behind
   the content rather than over it, so text contrast is untouched, and the
   opacity is a theme token because the same grain reads much stronger on
   white than on charcoal. */
:root {
  --grain: 0.055;
  --grain-img: url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%27180%27%20height%3D%27180%27%3E%3Cfilter%20id%3D%27n%27%20x%3D%270%27%20y%3D%270%27%3E%3CfeTurbulence%20type%3D%27fractalNoise%27%20baseFrequency%3D%270.82%27%20numOctaves%3D%273%27%20stitchTiles%3D%27stitch%27%2F%3E%3CfeColorMatrix%20type%3D%27saturate%27%20values%3D%270%27%2F%3E%3C%2Ffilter%3E%3Crect%20width%3D%27180%27%20height%3D%27180%27%20filter%3D%27url%28%23n%29%27%2F%3E%3C%2Fsvg%3E");
}
:root[data-theme="light"] { --grain: 0.025; }
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  opacity: var(--grain);
  background-image: var(--grain-img);
  background-repeat: repeat;
}
@media print { body::before, header::before { display: none; } }
