/* angusbuilds.com — page shell styles.
   thijs.net look (warm paper, stacked white cards) adapted per spec §2, with our own
   card-seam treatment (full rounding + alternating tilt) instead of a pixel port of
   thijs's flush-stacked shape() dividers. */

@font-face {
  font-family: 'Fraunces';
  src: url('assets/fonts/fraunces-700-latin.woff2') format('woff2');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Inter';
  src: url('assets/fonts/inter-variable-latin.woff2') format('woff2-variations');
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Space Mono';
  src: url('assets/fonts/spacemono-400-latin.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Space Mono';
  src: url('assets/fonts/spacemono-700-latin.woff2') format('woff2');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

:root {
  --ground: #faf9f6;
  --card: #fff;
  --ink: #1f2937;
  --ink-muted: #5b6472;
  --rule: rgba(31, 41, 55, 0.08);
  --serif: 'Fraunces', Georgia, serif;
  --sans: 'Inter', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  --mono: 'Space Mono', ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
  --nav-h: 74px;
  --radius-card: 24px;
  --shadow-card: 0 1px 2px rgba(31, 41, 55, 0.05), 0 18px 40px -22px rgba(31, 41, 55, 0.28);
}

@media (max-width: 640px) {
  :root {
    --radius-card: 20px;
  }
}

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

html {
  scroll-behavior: smooth;
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
}

body {
  margin: 0;
  background: var(--ground);
  color: var(--ink);
  font-family: var(--sans);
  -webkit-font-smoothing: antialiased;
}

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

a {
  color: inherit;
}

button {
  font: inherit;
  color: inherit;
}

.sc-visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* First tab stop: hidden until focused, then a pill above the nav. Ten focusable things
   (nav + the five letter buttons) sit before <main> otherwise. */
.sc-skip-link {
  position: fixed;
  top: 12px;
  left: 12px;
  z-index: 20;
  padding: 0.6em 1em;
  border-radius: 999px;
  background: var(--ink);
  color: #fff;
  font-family: var(--mono);
  font-size: 0.85rem;
  text-decoration: none;
  transform: translateY(-200%);
  opacity: 0;
}

.sc-skip-link:focus-visible {
  transform: none;
  opacity: 1;
  outline: 3px solid #4e8df6;
  outline-offset: 3px;
}

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

/* ---------- Nav ---------- */

.sc-nav {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 10;
  height: var(--nav-h);
  background: #fff;
  border-bottom: 1px solid var(--rule);
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-inline: clamp(16px, 4vw, 48px);
  transition: box-shadow 0.2s ease;
}

.sc-nav.sc-scrolled {
  box-shadow: 0 1px 0 var(--rule);
}

@media (prefers-reduced-motion: reduce) {
  .sc-nav {
    transition: none;
  }
}

.sc-nav__brand {
  font-family: var(--serif);
  font-weight: 700;
  font-size: 1.05rem;
  text-decoration: none;
  color: var(--ink);
}

.sc-nav__controls {
  display: flex;
  gap: 10px;
  flex: none;
  margin-right: 0.5rem;  /* a little air between the round buttons and the text links */
}

/* Bare glyphs, no ring or fill: they read like the other nav links, with a 44px tap target. */
.sc-nav__replay {
  appearance: none;
  min-width: 44px;
  height: 44px;
  padding: 0;
  display: grid;
  place-items: center;
  background: none;
  border: 0;
  color: var(--ink);
  cursor: pointer;
  flex: none;
  transition: color 0.15s ease, transform 0.1s ease;
}

.sc-nav__replay svg {
  width: 18px;
  height: 18px;
}

.sc-nav__replay-play {
  margin-left: 0;
}

/* On laptops the model button is a pill: lattice icon + the word. Phones keep the bare icon. */
.sc-nav__replay--model {
  display: inline-flex;
  align-items: center;
  gap: 0.45em;
}

.sc-nav__replay-word {
  font-family: var(--mono);
  font-size: 0.85rem;
  line-height: 1;
}

.sc-nav__replay--model[aria-expanded="true"] .sc-nav__replay-word {
  display: none;
}

.sc-nav__replay--model[aria-expanded="true"] {
  display: grid;
}

/* One control, two faces: play when the opening is collapsed, close while it is open. */
.sc-nav__replay-close,
.sc-nav__replay[aria-expanded="true"] .sc-nav__replay-play {
  display: none;
}

.sc-nav__replay[aria-expanded="true"] .sc-nav__replay-close {
  display: block;
}

.sc-nav__replay:hover,
.sc-nav__replay:focus-visible {
  color: var(--ink-muted);
}

.sc-nav__replay:active {
  transform: scale(0.92);
}

@media (prefers-reduced-motion: reduce) {
  .sc-nav__replay {
    transition: none;
  }
  .sc-nav__replay:active {
    transform: none;
  }
}

.sc-nav__links {
  display: flex;
  gap: 1.25rem;
  align-items: center;
}

.sc-nav__link {
  /* also a <button> for the model toggle: strip the UA chrome so it sits like the anchors */
  appearance: none;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 0.45em;
  font-family: var(--mono);
  font-size: 0.85rem;
  text-decoration: none;
  color: var(--ink);
  transition: color 0.15s ease;
}

.sc-nav__link:hover,
.sc-nav__link:focus-visible {
  color: var(--ink-muted);
}

@media (prefers-reduced-motion: reduce) {
  .sc-nav__link {
    transition: none;
  }
}

.sc-nav__icon {
  width: 15px;
  height: 15px;
  flex: none;
}

/* The X mark stands alone (no word), so it gets a touch more size to hold its own. */
.sc-nav__icon--always {
  width: 17px;
  height: 17px;
}

@media (max-width: 640px) {
  /* Keep the words on phones — a bare glyph is not a label. Smaller, with a real tap area.
     Measured on a 390px viewport: brand + replay + links at their desktop sizes summed wider
     than the nav's own content box, so flexbox space-between had nothing left to distribute —
     brand and the replay button sat flush with a literal 0px gap. Brand and the links both
     shrink here so there's real breathing room again. */
  .sc-nav__brand {
    font-size: 0.92rem;
  }
  .sc-nav__text {
    font-size: 0.78rem;
  }
  .sc-nav__link {
    min-height: 44px;
    padding-inline: 2px;
    gap: 0.35em;
  }
  .sc-nav__links {
    gap: 0.5rem;
  }
  .sc-nav__icon {
    width: 13px;
    height: 13px;
  }
  /* 390px: brand (≈125px) + two round buttons (38+6+38) + X (≈30) + contact (≈90) + gaps
     ≈ 345px in a 358px box. The round buttons live in the links cluster now. */
  .sc-nav__controls {
    gap: 0;
    margin-right: 0;
  }
  .sc-nav__replay,
  .sc-nav__replay--model,
  .sc-nav__replay--model[aria-expanded="true"] {
    min-width: 40px;
    height: 40px;
    display: grid;
  }
  .sc-nav__replay svg {
    width: 16px;
    height: 16px;
  }
  .sc-nav__replay-word {
    display: none;
  }
  /* Five round glyphs overflow 390px. On phones keep only the opening and the lab (the lab
     already contains the model, the jailbreak and the pentest); the rest live inside it. */
  .sc-nav__replay--model[data-sc-model-toggle],
  .sc-nav__replay--model[data-sc-jb-toggle],
  .sc-nav__replay--model[data-sc-pt-toggle] {
    display: none;
  }
}

/* ---------- Stage + sheet ---------- */

/* The side-gutter rain (stage/rain.mjs): fixed, behind the sheet, never catches the pointer. */
.sc-rain {
  position: fixed;
  inset: 0;
  z-index: 1;
  pointer-events: none;
}

.sc-stage {
  position: sticky;
  top: 0;
  height: 100svh;
  z-index: 0;
  background: #fff;
  overflow: hidden;
  transition: height 0.6s cubic-bezier(0.2, 0.8, 0.2, 1), visibility 0s linear 0s;
}

/* Collapsed on load (thijs.net: the stage is opt-in, the nav button opens it). Zero height and
   hidden so nothing inside can take focus; the sheet sits under the nav instead of over the
   stage. mount.js flips data-sc-open / data-sc-stage-open. */
.sc-stage[data-sc-open="false"] {
  height: 0;
  visibility: hidden;
  transition: height 0.6s cubic-bezier(0.2, 0.8, 0.2, 1), visibility 0s linear 0.6s;
}

/* Full viewport height regardless of the stage's own (collapsing) height, so the scene can be
   laid out and start playing while the stage is still sliding open; overflow clips it. */
.sc-stage__canvas {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 100svh;
}

/* Phones: the composition (windows, word, status) is ~260px tall; a full 844px stage left a
   blank band under the word. 80svh keeps the scene centred and lets the name card peek in. */
@media (max-width: 640px) {
  .sc-stage:not([data-sc-open="false"]),
  .sc-stage__canvas {
    height: 80svh;
  }
}

.sc-stage__letters {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 100svh;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: clamp(1rem, 3vw, 2.25rem);
  padding-top: var(--nav-h);
}

.sc-stage__letter {
  /* Positioned over each drawn letter by the stage every frame; invisible until focused. */
  position: absolute;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: transparent;
  cursor: pointer;
  opacity: 0;
}
.sc-stage__letter:focus-visible {
  opacity: 1 !important;
  outline: 3px solid #4e8df6;
  outline-offset: 6px;
}

.sc-sheet {
  position: relative;
  z-index: 2;
  margin-top: -64px;
  /* The tilted panels overhang their box by a few px; never let that become a horizontal scroll. */
  overflow-x: clip;
  transition: margin-top 0.6s cubic-bezier(0.2, 0.8, 0.2, 1);
}

.sc-page[data-sc-stage-open="false"] .sc-sheet {
  margin-top: var(--nav-h);
}

@media (prefers-reduced-motion: reduce) {
  .sc-stage,
  .sc-stage[data-sc-open="false"],
  .sc-sheet {
    transition: none;
  }
}

@media (max-width: 640px) {
  .sc-panel {
    --tilt: 0deg !important;
  }
}

.sc-content {
  max-width: 1152px;
  margin-inline: auto;
  padding-inline: 24px;
}

@media (max-width: 767px) {
  .sc-content {
    padding-inline: 12px;
  }
}

/* ---------- Panels (the big stacked white cards) ---------- */
/* Card seams: own treatment — full rounding + a soft shadow + a 0.6deg alternating tilt.
   The panel carries the tilt; its __inner wrapper carries the opposite rotation so the
   reading content stays level while the card's silhouette reads as a scattered stack. */

.sc-panel {
  position: relative;
  background: var(--card);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  margin-block: 20px;
  transform: rotate(var(--tilt, 0deg));
  transition: transform 0.2s ease;
}

.sc-panel:first-of-type {
  margin-top: 0;
}

.sc-panel:nth-of-type(odd) {
  --tilt: -0.6deg;
}

.sc-panel:nth-of-type(even) {
  --tilt: 0.6deg;
}

@media (prefers-reduced-motion: reduce) {
  .sc-panel {
    transition: none;
  }
}

.sc-panel__inner {
  transform: rotate(calc(var(--tilt, 0deg) * -1));
  padding: clamp(24px, 4vw, 44px);
}

/* ---------- Banner ---------- */

.sc-panel--banner {
  overflow: visible;
  padding: 0;
  /* Every panel is its own stacking context (the tilt transform), and later panels paint over
     earlier ones — which cut the overhanging avatar in half. Lift the banner above its neighbour. */
  z-index: 2;
}

.sc-banner {
  /* The name card: sized by its own text, the avatar overhanging the bottom edge like the
     reference. No scene in here any more (owner: "get rid of the bottom part"). */
  --avatar: clamp(220px, 30vw, 420px);   /* ring size; the text keeps clear of it */
  position: relative;
  width: 100%;
  max-width: 100%;
  border-radius: inherit;
  overflow: visible;
  background: #fff;
  padding: clamp(40px, 6vw, 72px) clamp(20px, 4vw, 44px) clamp(44px, 6vw, 64px);
  /* Tall enough that the raised avatar (32% over the bottom edge) never pokes above the card
     into the nav; the text sits in the vertical middle of whatever height that makes. */
  min-height: calc(var(--avatar) * 0.68 + 40px);
  display: flex;
  align-items: center;
}

.sc-banner__text {
  position: relative;
  max-width: calc(100% - var(--avatar) - 24px);
}

.sc-banner__h1 {
  margin: 0;
  text-transform: uppercase;   /* owner 2026-10-14: "make it say angus all caps" */
  font-family: var(--serif);
  font-weight: 700;
  font-size: clamp(32px, 5vw, 60px);
  line-height: 1.05;
  letter-spacing: -0.01em;
  color: var(--ink);
}

/* The name's entrance: each word rises in on its own beat as the banner actually scrolls
   into view — not a fixed-duration fade on page load, which would finish before anyone
   scrolls down far enough to see it (the banner sits a full viewport below the top on
   load). Tied to the native scroll-driven view-timeline, not JS: no listener added, mount.js
   keeps its one scroll clock untouched, and nothing here can throw. Feature-queried, so a
   browser without view-timeline support just renders the words as plain static text —
   never permanently hidden, never dependent on a script running. */
.sc-banner__word {
  display: inline-block;
}

@supports (animation-timeline: view()) {
  .sc-banner__word {
    animation: sc-word-in linear both;
    animation-timeline: view();
    animation-range: entry 0% entry 85%;
  }
  .sc-banner__word:nth-of-type(2) {
    animation-range: entry 15% entry 100%;
  }
}

@keyframes sc-word-in {
  from {
    opacity: 0;
    transform: translateY(14px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@media (prefers-reduced-motion: reduce) {
  .sc-banner__word {
    animation: none !important;
  }
}

.sc-banner__tagline {
  margin: 0.6em 0 0;
  font-family: var(--mono);
  font-size: 0.85rem;
  color: #666;
}

/* Phones wrap the tagline next to the avatar; break between terms, never inside one
   ("cyber / security" read as two things). */
.sc-banner__tagline span {
  white-space: nowrap;
}

.sc-avatar {
  /* Big. Over a third of the card's width on desktop, hanging more than half over the card's
     bottom edge (owner, 2026-10-10: "bigger and bigger and bigger" past the reference's size). */
  position: absolute;
  right: clamp(16px, 4vw, 40px);
  /* Owner 2026-10-14: "profile image higher up" — about a third over the edge, not more than half. */
  bottom: calc(var(--avatar) * -0.32);
  width: var(--avatar);
  height: var(--avatar);
  z-index: 3;
}

@media (max-width: 640px) {
  /* Narrow cards: a one-line name and a smaller avatar so neither crowds the looping scene. */
  .sc-banner__h1 {
    font-size: 26px;   /* ANGUS DUNCAN in caps stays on one line beside the avatar at 390px */
  }
  .sc-banner__text {
    /* The old `top: 88px` shoved the text down without growing the card (it dated from the
       scene banner), so a two-line tagline spilled under the next card. The card is sized by
       its text now; the avatar overhangs bottom-right, so keep the text clear of it instead. */
    max-width: 100%;
    padding-right: 110px;
  }
  .sc-banner {
    --avatar: 170px;
  }
  .sc-avatar {
    bottom: -44px;
  }
}

.sc-avatar__ring {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  animation: sc-spin 18s linear infinite;
}

@keyframes sc-spin {
  to {
    transform: rotate(360deg);
  }
}

@media (prefers-reduced-motion: reduce) {
  .sc-avatar__ring {
    animation: none;
  }
}

.sc-avatar__ring-text {
  font-family: var(--mono);
  font-size: 6.5px;                /* viewBox units: ≈13.5px on the 420px ring */
  letter-spacing: 0.08em;
  fill: var(--ink);
}

.sc-avatar__plate,
.sc-avatar__photo {
  position: absolute;
  inset: 11%;                      /* ring text band ~30px at 300px, ~16px at 150px */
  border-radius: 50%;
  background: linear-gradient(135deg, #e3d6be, #cbbb9c);
  border: 3px solid #fff;
  box-shadow: 0 6px 18px rgba(31, 41, 55, 0.2);
  overflow: hidden;
}

.sc-avatar__photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 50%;
}

/* ---------- About ---------- */

.sc-panel--about h2,
.sc-panel--blog h2,
.sc-panel--projects h2 {
  margin: 0 0 0.5em;
  font-family: var(--serif);
  font-weight: 700;
  font-size: 1.6rem;
}

.sc-about__intro {
  margin: 0 0 1.25em;
  line-height: 1.6;
  /* A readable measure: at 1440px this ran 128 characters a line across the whole card. */
  max-width: 62ch;
}

.sc-timeline {
  display: flex;
  flex-direction: column;
  gap: 0.6em;
  margin-bottom: 1.6em;
  font-size: 0.95rem;
}

.sc-timeline__row {
  display: flex;
  align-items: center;
  gap: 0.6em;
}

.sc-timeline__dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--ink-muted);
  flex: none;
}

.sc-timeline__dot--now {
  background: #3fae6a;
}

.sc-timeline__label {
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.06em;
  color: var(--ink-muted);
  flex: none;
  /* The labels form a column (NOW / FOCUS / CHESS) so the rows read as one small table. */
  min-width: 4.6em;
}

.sc-pills {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6em;
}

.sc-pill {
  display: inline-flex;
  align-items: center;
  min-height: 44px;                 /* real tap target; padding alone only reached ~37px */
  padding: 0.5em 1em;
  border-radius: 999px;
  background: var(--ground);
  border: 1px solid var(--rule);
  font-family: var(--mono);
  font-size: 0.82rem;
  text-decoration: none;
  color: var(--ink);
}

a.sc-pill {
  transition: background 0.15s ease, border-color 0.15s ease, transform 0.1s ease;
}

a.sc-pill:hover {
  background: #f0eee8;
  border-color: rgba(31, 41, 55, 0.2);
}

a.sc-pill:active {
  transform: scale(0.97);
}

a.sc-pill:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  a.sc-pill {
    transition: none;
  }
  a.sc-pill:active {
    transform: none;
  }
}

.sc-pill--tbd {
  color: var(--ink-muted);
  font-style: italic;
}

/* ---------- Card intro line (blog/projects) ---------- */

.sc-card__intro {
  margin: 0 0 1.25em;
  color: var(--ink-muted);
}

/* ---------- Project cards (CARDS.md contract) ---------- */

.sc-projects__grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 14px;
}

.sc-card {
  position: relative;
  display: block;
  aspect-ratio: 3 / 2;
  border-radius: 16px;
  overflow: hidden;
  background: var(--card);
  color: inherit;
  text-decoration: none;
  container-type: inline-size;
  transition: transform 0.18s ease, box-shadow 0.18s ease;
}

a.sc-card:hover,
a.sc-card:focus-visible {
  transform: translateY(-2px);
  box-shadow: 0 16px 30px -18px rgba(31, 41, 55, 0.4);
}

a.sc-card:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 3px;
}

@media (prefers-reduced-motion: reduce) {
  .sc-card {
    transition: none;
  }
  a.sc-card:hover,
  a.sc-card:focus-visible {
    transform: none;
  }
}

.sc-card--wide {
  aspect-ratio: 16 / 10;
}

.sc-card__media {
  position: absolute;
  inset: 0;
  z-index: 0;
  background: #e8e4da;
  border-radius: inherit;
}

/* A hairline ring over the media so a light README image (Headroom's is near-white) still
   reads as a card against the white panel instead of floating. */
.sc-card__media::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
  border-radius: inherit;
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.07);
  pointer-events: none;
}

.sc-card__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* The generated plate: a soft gradient seeded from the project id (tools/lib/cards.mjs
   platePalette — same id always gets the same colour, no randomness) plus the project's own
   first letter, drawn huge and faint. No fake screenshot, no invented image — a placeholder
   that's honest about standing in for one, and distinct card to card. */
.sc-card__plate {
  position: absolute;
  inset: 0;
  background: linear-gradient(
    135deg,
    hsl(var(--plate-hue, 38) 44% 89%),
    hsl(var(--plate-hue2, 30) 38% 73%)
  );
  overflow: hidden;
}

.sc-card__plate::after {
  /* The initial is an SVG image set by the build (--plate-glyph): decoration, not text. */
  content: '';
  position: absolute;
  right: 0;
  bottom: -4cqw;
  width: 42cqw;
  height: 42cqw;
  background: var(--plate-glyph) no-repeat right bottom / contain;
  pointer-events: none;
  opacity: 1;
  transition: opacity 0.18s ease;
}

a.sc-card:hover .sc-card__plate::after,
a.sc-card:focus-visible .sc-card__plate::after {
  opacity: 1.5;
}

@media (prefers-reduced-motion: reduce) {
  .sc-card__plate::after {
    transition: none;
  }
}

.sc-card__year {
  position: absolute;
  z-index: 1;
  top: 12px;
  left: 12px;
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.02em;
  background: rgba(255, 255, 255, 0.88);
  color: var(--ink);
  padding: 0.25em 0.6em;
  border-radius: 999px;
}

.sc-card__star {
  /* Dark amber on a white chip: the bare yellow star measured 1.26:1 on the plate. */
  position: absolute;
  z-index: 1;
  top: 12px;
  right: 12px;
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: #fff;
  color: #8a5a00;
  font-size: 0.9rem;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.18);
}

.sc-card__label {
  position: absolute;
  z-index: 1;
  left: 12px;
  right: 12px;
  bottom: 12px;
  background: #fff;
  border-radius: 10px;
  padding: 0.65em 0.85em;
  display: flex;
  flex-direction: column;
  gap: 0.2em;
}

.sc-card__title {
  font-family: var(--serif);
  font-weight: 700;
  font-size: 0.98rem;
  color: var(--ink);
}

.sc-card__sub {
  font-family: var(--sans);
  font-size: 0.78rem;
  color: var(--ink-muted);
  /* Two lines max: long repo descriptions were pushing the label out of the card on phones. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* ---------- Blog posts (CARDS.md contract) ---------- */

.sc-blog__grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 14px;
}

.sc-post {
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  gap: 0.35em;
  min-height: 150px;
  border-radius: 16px;
  padding: 1rem;
  text-decoration: none;
  color: var(--ink);
}

.sc-post--a {
  background: #f9d4d7;
}

.sc-post--b {
  background: #ffe1b8;
}

.sc-post__date {
  font-family: var(--mono);
  font-size: 0.72rem;
  opacity: 0.7;
}

.sc-post__title {
  font-family: var(--serif);
  font-weight: 700;
  font-size: 1.1rem;
}

.sc-post__excerpt {
  font-family: var(--sans);
  font-size: 0.85rem;
  opacity: 0.85;
}

@media (max-width: 640px) {
  /* Projects stay two-up on phones so the list scans instead of scrolling forever. */
  .sc-projects__grid {
    grid-template-columns: repeat(2, 1fr);
    gap: 10px;
  }
  .sc-card--wide {
    grid-column: span 2;
  }
  .sc-card__title {
    font-size: 0.95rem;
  }
  .sc-card__sub {
    font-size: 0.72rem;
  }
  /* Half-width cards are ~170px square here. At desktop padding the label box covered the
     whole card — the year chip was behind it (product-check: "2026 paints no ink") and the
     image was a thin border. Tighter label, smaller title, and no year chip on the small
     cards (every card says 2026; the wide ones still carry it). */
  .sc-card:not(.sc-card--wide) .sc-card__label {
    left: 8px;
    right: 8px;
    bottom: 8px;
    padding: 0.5em 0.7em;
    gap: 0.1em;
  }
  .sc-card:not(.sc-card--wide) .sc-card__title {
    font-size: 0.88rem;
  }
  .sc-card:not(.sc-card--wide) .sc-card__year {
    display: none;
  }
  .sc-blog__grid {
    grid-template-columns: 1fr;
  }
}

/* ---------- Footer ---------- */

.sc-footer {
  text-align: center;
  padding: 40px 24px 64px;
  color: var(--ink-muted);
  font-size: 0.8rem;
}

.sc-footer p {
  margin: 0 0 0.6em;
}


/* ---------- Model (the neural net that learns Angus, live) ---------- */
/* A white card like the hero; only the stage inside is the black terminal, mono, white on
   black with the reference clip's single yellow. Beats are driven by model/index.mjs via data-beat. */

.sc-panel--model {
  scroll-margin-top: calc(var(--nav-h) + 12px);  /* nav button + ?model=1 land below the fixed nav */
  --tilt: 0.6deg;
}

.sc-panel--model[hidden] {
  display: none;
}

.sc-model {
  font-family: var(--mono);
}

.sc-model__head h2 {
  margin: 0 0 0.4em;
  font-family: var(--mono);
  font-weight: 700;
  font-size: clamp(1.15rem, 2.4vw, 1.6rem);
  letter-spacing: -0.01em;
  color: var(--ink);
}

.sc-model__head p {
  margin: 0 0 1.2em;
  max-width: 64ch;
  font-family: var(--sans);
  font-size: 0.98rem;
  line-height: 1.55;
  color: var(--ink-muted);
}

.sc-model__head p b {
  color: var(--ink);
  font-weight: 600;
}

.sc-model__stage {
  position: relative;
  height: clamp(460px, 68vh, 680px);
  overflow: hidden;
  border-radius: 16px;
  background: var(--ground);
  border: 1px solid var(--rule);
  font-size: 14px;
  line-height: 1.4;
  isolation: isolate;
}

@media (max-width: 640px) {
  .sc-model__stage {
    height: clamp(440px, 76svh, 620px);
    font-size: 12px;
  }
}

/* The one line of purpose the stage keeps: mono, small, out of the lattice's way. */
.sc-model__title {
  position: absolute;
  top: 14px;
  left: 16px;
  right: 16px;
  z-index: 3;
  display: flex;
  gap: 12px;
  margin: 0;
  font-size: 0.86em;
  font-weight: 700;
  color: var(--ink);
  pointer-events: none;
}

.sc-model__title-sub {
  font-weight: 400;
  color: var(--ink-muted);
}

.sc-model__title-sub::before {
  content: '·';
  margin-right: 12px;
}

@media (max-width: 640px) {
  .sc-model__title-sub {
    display: none;
  }
}

.sc-model__canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 1;
}

.sc-model__fore {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
}

.sc-model__input {
  position: absolute;
  left: 50%;
  top: 20%;
  transform: translate(-50%, -50%);
  font-size: clamp(16px, 2.6vw, 28px);
  white-space: nowrap;
  color: #1f2937;
  transition: transform 0.6s ease, opacity 0.6s ease;
}

.sc-model__input.is-sent {
  transform: translate(-50%, -50%) scale(0.5);
  opacity: 0;
}

.sc-model__bracket {
  color: rgba(31, 41, 55, 0.5);
}

.sc-model__ch {
  display: inline-block;
  min-width: 0.62em;
  text-align: center;
  transition: color 0.3s ease;
}

.sc-model__ch.is-code {
  background: #1f2937;
  color: #fff;
  margin: 0 1px;
  padding: 0 0.1em;
  animation: sc-flip 0.32s ease;
}

.sc-model__ch.is-token {
  color: var(--ink-muted);
  margin: 0 0.22em;
  animation: sc-flip 0.32s ease;
}

@keyframes sc-flip {
  from { transform: scaleY(0.1); }
  to { transform: scaleY(1); }
}

/* The output window: a pygame-ish window frame, like the clip. */
.sc-win {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-32%, -50%);
  width: min(560px, 62%);
  background: #faf9f6;
  border: 1px solid rgba(31, 41, 55, 0.55);
  border-radius: 6px;
  box-shadow: 0 18px 60px rgba(250, 249, 246, 0.7);
  pointer-events: auto;
  animation: sc-win-in 0.5s cubic-bezier(0.2, 0.8, 0.2, 1);
}

@media (max-width: 640px) {
  .sc-win {
    width: min(420px, 90%);
    transform: translate(-50%, -42%);
  }
}

@keyframes sc-win-in {
  from { opacity: 0; transform: translate(-32%, -46%) scale(0.96); }
}

.sc-win__bar {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 10px;
  border-bottom: 1px solid rgba(31, 41, 55, 0.35);
  background: #f3f1ec;
  font-size: 0.8em;
  color: rgba(31, 41, 55, 0.7);
}

.sc-win__icon {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--ink-muted);
}

.sc-win__ctl {
  margin-left: auto;
  display: flex;
  gap: 8px;
}

.sc-win__ctl i {
  width: 9px;
  height: 9px;
  border: 1px solid rgba(31, 41, 55, 0.5);
}

.sc-win__body {
  padding: 14px 16px 12px;
  min-height: 150px;
}

/* Replies wrap long on phones; the window must never outgrow the stage, so the log scrolls. */
.sc-win__out {
  max-height: min(240px, 34vh);
  overflow-y: auto;
  overscroll-behavior: contain;
}

.sc-win__line {
  margin: 0 0 6px;
  white-space: pre-wrap;
  word-break: break-word;
  color: rgba(31, 41, 55, 0.7);
}

.sc-win__line--model {
  color: #1f2937;
  font-size: 1.12em;
}

.sc-win__k {
  color: rgba(31, 41, 55, 0.45);
}

.sc-win__cursor {
  display: inline-block;
  margin-left: 2px;
  animation: sc-blink 1s steps(2, start) infinite;
}

@keyframes sc-blink {
  to { visibility: hidden; }
}

.sc-win__form[hidden] {
  display: none;
}

.sc-win__form {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px dashed rgba(31, 41, 55, 0.25);
}

.sc-win__prompt {
  color: var(--ink-muted);
}

.sc-win__field {
  flex: 1;
  min-width: 0;
  font: inherit;
  color: #1f2937;
  background: transparent;
  border: 0;
  outline: 0;
  padding: 4px 0;
}

.sc-win__field:focus-visible {
  box-shadow: 0 2px 0 var(--ink);
}

.sc-win__field::placeholder {
  color: rgba(31, 41, 55, 0.35);
}

.sc-win__go {
  font: inherit;
  font-size: 0.85em;
  color: #fff;
  background: var(--ink);
  border: 0;
  border-radius: 4px;
  padding: 4px 10px;
  cursor: pointer;
}

.sc-win__go:focus-visible,
.sc-model__btn:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
}

.sc-win__end {
  margin-top: 12px;
  font-size: 0.8em;
  color: rgba(31, 41, 55, 0.5);
  min-height: 1.4em;
}

.sc-win__end .sc-model__ch {
  margin: 0 2px;
}

.sc-model__hud {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 3;
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 12px;
  padding: 12px 16px;
  pointer-events: none;
}

.sc-model__status {
  font-size: 0.86em;
  color: rgba(31, 41, 55, 0.8);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.sc-model__buttons {
  display: flex;
  gap: 8px;
  pointer-events: auto;
}

.sc-model__btn {
  font: inherit;
  font-size: 0.82em;
  color: rgba(31, 41, 55, 0.8);
  background: rgba(31, 41, 55, 0.06);
  border: 1px solid rgba(31, 41, 55, 0.25);
  border-radius: 999px;
  padding: 5px 12px;
  white-space: nowrap;
  cursor: pointer;
}

.sc-model__btn:hover {
  color: #fff;
  background: var(--ink);
  border-color: var(--ink);
}

/* ---------- Model figure (stick-figure Angus) ---------- */

.sc-fig {
  position: absolute;
  bottom: 7%;
  left: var(--x, -12%);
  width: clamp(54px, 7.5%, 92px);
  z-index: 2;
  transition: left var(--dur, 1.6s) linear;
  pointer-events: none;
}

.sc-fig svg {
  display: block;
  width: 100%;
  height: auto;
  overflow: visible;
}

.sc-fig__body {
  transform-box: fill-box;
  transform-origin: center;
}

.sc-fig[data-face="left"] .sc-fig__body {
  transform: scaleX(-1);
}

.sc-fig__leg,
.sc-fig__arm {
  transform-box: view-box;
}

.sc-fig__leg {
  transform-origin: 58px 100px;
}

.sc-fig__arm {
  transform-origin: 60px 54px;
}

.sc-fig.is-walking svg {
  animation: sc-fig-bob 0.36s ease-in-out infinite alternate;
}

.sc-fig.is-walking .sc-fig__leg--a {
  animation: sc-fig-leg 0.72s ease-in-out infinite;
}

.sc-fig.is-walking .sc-fig__leg--b {
  animation: sc-fig-leg 0.72s ease-in-out infinite;
  animation-delay: -0.36s;
}

.sc-fig.is-typing .sc-fig__arm--b {
  animation: sc-fig-tap 0.22s ease-in-out infinite alternate;
}

.sc-fig.is-typing .sc-fig__arm--a {
  animation: sc-fig-tap 0.19s ease-in-out infinite alternate-reverse;
}

@keyframes sc-fig-leg {
  0% { transform: rotate(-26deg); }
  50% { transform: rotate(26deg); }
  100% { transform: rotate(-26deg); }
}

@keyframes sc-fig-bob {
  from { transform: translateY(0); }
  to { transform: translateY(-3px); }
}

@keyframes sc-fig-tap {
  from { transform: rotate(0deg); }
  to { transform: rotate(5deg); }
}


/* ---------- The lab (all three scenes in a grid) ---------- */
.sc-lab__head {
  position: static;
  margin: 0 0 14px;
}

.sc-lab__grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 14px;
}

.sc-lab__cell {
  position: relative;
}

.sc-lab__stage {
  height: clamp(300px, 34vw, 420px);
  border-radius: 12px;
}

.sc-lab__stage .sc-model__title {
  top: 10px;
  left: 10px;
  right: 10px;
  font-size: 0.78em;
}

.sc-lab__stage .sc-model__hud {
  display: none;
}

/* the mini jailbreak/pentest ledgers are noise at this size */
.sc-lab__stage .sc-jb__log,
.sc-lab__stage .sc-jb__out {
  display: none;
}

@media (max-width: 900px) {
  .sc-lab__grid {
    grid-template-columns: 1fr;
  }
  .sc-lab__stage {
    height: clamp(320px, 70vw, 440px);
  }
}

/* ---------- Jailbreak (prompt injection, told as a grate) ---------- */
/* Reuses the model stage, HUD, window and figure. Only the terminal on the left, the lock in the
   window bar, and the glitch are new. The grate itself is canvas (jailbreak/index.mjs). */

.sc-jb__canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 1;
}

.sc-jb__you {
  position: absolute;
  left: 4%;
  top: 16%;
  width: 30%;
  z-index: 2;
  background: #faf9f6;
  border: 1px solid rgba(31, 41, 55, 0.55);
  border-radius: 6px;
  opacity: 0;
  transition: opacity 0.4s ease;
  pointer-events: none;
}

.sc-jb__you.is-on {
  opacity: 1;
}

.sc-jb__you-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 10px;
  border-bottom: 1px solid rgba(31, 41, 55, 0.35);
  background: #f3f1ec;
  font-size: 0.8em;
  color: rgba(31, 41, 55, 0.7);
}

.sc-jb__wave {
  margin-left: auto;
  color: rgba(31, 41, 55, 0.5);
  letter-spacing: 0.02em;
}

/* The ledger: one line per attempt, under the terminal. */
.sc-jb__log {
  position: absolute;
  left: 4%;
  top: 56%;
  width: 30%;
  margin: 0;
  padding: 0;
  list-style: none;
  z-index: 2;
  font-size: 0.8em;
  line-height: 1.7;
  pointer-events: none;
}

.sc-jb__log-line {
  display: grid;
  grid-template-columns: 2em 1fr auto;
  gap: 8px;
  color: rgba(31, 41, 55, 0.55);
  animation: sc-jb-log-in 0.3s ease;
}

.sc-jb__log-id {
  color: rgba(31, 41, 55, 0.35);
}

.sc-jb__log-out {
  color: rgba(31, 41, 55, 0.4);
}

.sc-jb__log-line.is-sent .sc-jb__log-out::after {
  content: '…';
}

.sc-jb__log-line.is-sent .sc-jb__log-out {
  font-size: 0;
}

.sc-jb__log-line.is-sent .sc-jb__log-out::after {
  font-size: 0.8rem;
}

.sc-jb__log-line.is-breach {
  color: #1f2937;
  font-weight: 700;
}

.sc-jb__log-line.is-breach .sc-jb__log-out,
.sc-jb__log-line.is-held .sc-jb__log-out {
  color: #1f2937;
}

@keyframes sc-jb-log-in {
  from { opacity: 0; transform: translateY(3px); }
}

/* The map version: ledger down the right, the model's lines bottom-left. */
.sc-jb__log--map {
  left: auto;
  right: 4%;
  top: 14%;
  width: 26%;
}

.sc-jb__log-line.is-deep .sc-jb__log-out {
  color: #1f2937;
  font-weight: 700;
}

.sc-jb__out {
  position: absolute;
  left: 4%;
  bottom: 13%;
  width: 46%;
  z-index: 2;
  font-size: 0.86em;
  line-height: 1.5;
  pointer-events: none;
}

.sc-jb__out-line {
  white-space: pre-wrap;
  word-break: break-word;
  color: rgba(31, 41, 55, 0.5);
  animation: sc-jb-log-in 0.25s ease;
}

.sc-jb__out-line.is-sys {
  color: rgba(31, 41, 55, 0.5);
}

.sc-jb__out-line.is-you {
  color: rgba(31, 41, 55, 0.7);
}

.sc-jb__out-line.is-model {
  color: #1f2937;
}

.sc-jb__out-line.is-god {
  letter-spacing: 0.02em;
}

.sc-jb__out-line.is-leet {
  font-weight: 700;
  font-size: 1.08em;
}

.sc-jb__you-body {
  margin: 0;
  padding: 10px 12px;
  min-height: 60px;
  max-height: 34vh;
  overflow: hidden;
  font: inherit;
  font-size: 0.86em;
  line-height: 1.45;
  white-space: pre-wrap;
  word-break: break-all;
  color: #1f2937;
}

/* The model window sits on the right for this stage, clear of the grate. */
.sc-win--jb {
  left: auto;
  right: 4%;
  top: 16%;
  transform: none;
  width: 34%;
}

.sc-win--jb .sc-win__body {
  min-height: 120px;
}

.sc-win__line--sys {
  color: rgba(31, 41, 55, 0.5);
  font-size: 0.92em;
}

.sc-win__line--god {
  letter-spacing: 0.02em;
  font-size: 0.95em;
}

.sc-win__line--leet {
  font-weight: 700;
}

/* The lock: a tiny padlock drawn in borders; shackle lifts when the guardrail is open. */
.sc-jb__lock {
  position: relative;
  width: 9px;
  height: 7px;
  margin-left: 6px;
  border: 1.5px solid rgba(31, 41, 55, 0.7);
  border-radius: 2px;
}

.sc-jb__lock::before {
  content: '';
  position: absolute;
  left: 1px;
  top: -6px;
  width: 5px;
  height: 6px;
  border: 1.5px solid rgba(31, 41, 55, 0.7);
  border-bottom: 0;
  border-radius: 4px 4px 0 0;
  transition: transform 0.3s ease;
}

.sc-jb__lock[data-state="open"]::before {
  transform: translate(3px, -2px) rotate(20deg);
}

/* The breach: the window flickers and its text shivers; nothing changes colour. */
.sc-win.is-glitch {
  animation: sc-jb-flicker 0.18s steps(2, end) infinite;
}

.sc-win.is-glitch .sc-win__line--model {
  animation: sc-jb-shiver 0.12s linear infinite;
}

@keyframes sc-jb-flicker {
  0% { opacity: 1; }
  50% { opacity: 0.86; }
  100% { opacity: 1; }
}

@keyframes sc-jb-shiver {
  0% { transform: translate(0, 0); }
  25% { transform: translate(0.6px, -0.4px); }
  50% { transform: translate(-0.5px, 0.4px); }
  75% { transform: translate(0.4px, 0.5px); }
  100% { transform: translate(0, 0); }
}

@media (max-width: 640px) {
  .sc-jb__you {
    left: 3%;
    width: 38%;
    font-size: 11px;
  }
  .sc-jb__log {
    left: 3%;
    width: 38%;
    top: 58%;
    font-size: 10px;
  }
  .sc-jb__log--map {
    display: none;
  }
  .sc-jb__out {
    left: 4%;
    right: 4%;
    width: auto;
    bottom: 12%;
    font-size: 11px;
  }
  .sc-jb__wave {
    display: none;
  }
  .sc-win--jb {
    right: 3%;
    width: 38%;
    font-size: 11px;
  }
  .sc-win--jb .sc-win__body {
    padding: 10px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .sc-win.is-glitch,
  .sc-win.is-glitch .sc-win__line--model {
    animation: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .sc-fig,
  .sc-model__input {
    transition: none;
  }
  .sc-fig.is-walking svg,
  .sc-fig.is-walking .sc-fig__leg--a,
  .sc-fig.is-walking .sc-fig__leg--b,
  .sc-fig.is-typing .sc-fig__arm--a,
  .sc-fig.is-typing .sc-fig__arm--b,
  .sc-model__ch.is-code,
  .sc-model__ch.is-token,
  .sc-win,
  .sc-win__cursor {
    animation: none;
  }
}
