/*
 * bsmi.tech — the apex landing page, drawn as an engineering sheet.
 *
 * 🔴 THIS FILE MUST STAY SELF-CONTAINED. THE SITE'S OWN CSP FORBIDS EVERY EXTERNAL HOST.
 * The vhost (devops/nginx/conf/bsmi.tech.conf) sends:
 *
 *   style-src 'self' 'unsafe-inline'      → no cdnjs, no fonts.googleapis.com, no @import
 *   script-src 'self' 'unsafe-inline' ...  → no code.jquery.com
 *   font-src  'self' data:                 → no webfont from anywhere but this host
 *   img-src   'self' data: https:          → ../img/whiteboard.svg is same-origin, so it loads
 *
 * An earlier version ignored the first three. It pulled animate.css and jQuery from CDNs and
 * `@import`ed Roboto from Google Fonts, and the browser blocked all three on every single load —
 * four console errors, a `$ is not defined` from the site's only script, and an entrance
 * animation that never once played. Nothing looked broken, so nothing got fixed.
 *
 * So: system fonts, CSS-only motion, zero network requests beyond this file, the HTML and one
 * same-origin SVG. If you add a webfont, self-host it here and add nothing to the CSP.
 *
 * ── THE SHEET, AND WHY ──────────────────────────────────────────────────────
 * Third design. The first was a six-card product grid; the second, after that was called out as
 * the generic machine-made landing page, was an editorial-minimal rebuild — serif heading,
 * hairline rules, three tidy columns. It was rejected for exactly the same reason, and the
 * operator was right twice: the tell was never the card borders, it was the RHYTHM. Hero, three
 * equal columns, one filled accent button. Taking the boxes off did not change the rhythm.
 *
 * So this is a different object rather than a restyle. The page is a drawing sheet, and each
 * piece of its furniture replaces something generic with something that does the same job:
 *
 *   generic                        →  drawing
 *   ───────────────────────────────────────────────────────────────────────
 *   underline / gradient on a word →  a DIMENSION LINE, arrows and all
 *   filled accent button           →  a CALLOUT with a balloon, as a sheet reference
 *   three feature columns          →  NUMBERED NOTES, one column
 *   footer                         →  a TITLE BLOCK, ruled fields
 *   soft page background           →  a SHEET FRAME with zone markers
 *
 * Rules that hold the theme together, in rough order of how easily they get broken:
 *
 *   1. EVERY CORNER IS SQUARE. `border-radius` appears exactly once in this file, on the
 *      callout balloon, which is a circle because a drawing balloon is a circle.
 *   2. ONE TYPEFACE, MONOSPACE, THROUGHOUT. A drawing is lettered in a single technical hand.
 *      Mixing in a proportional face for "readability" is what turns this back into a website.
 *   3. NO FILLS. Rules and ink, not surfaces. The one filled thing is the callout on hover.
 *   4. THE PALETTE IS TWO REAL DRAWING MEDIA, not a light/dark toggle of one idea: dark is a
 *      BLUEPRINT (pale line on process blue), light is INK ON VELLUM (graphite on warm paper).
 *      Do not neutralise either toward grey — grey on white is where the last version died.
 */

:root {
  color-scheme: light dark;

  /* Light: ink on vellum. */
  --paper: #f2eee2;
  --ink: #23272b;
  /* Darkened from #5b6068, which measured 4.27:1 against the worst backdrop on this sheet
     (vellum + a major grid line + a sketch stroke) and so missed AA. This is 5.30. */
  --muted: #4d525a;
  --rule: rgba(35, 39, 43, 0.42);
  --rule-soft: rgba(35, 39, 43, 0.16);
  --accent: #9c3512;
  --accent-ink: #f7f4ea;

  /* Fine grid, then a heavier one every fifth line, as on drafting paper. */
  --grid: rgba(35, 39, 43, 0.055);
  --grid-major: rgba(35, 39, 43, 0.085);
  --board-opacity: 0.05;

  --sheet-gap: clamp(0.75rem, 2.2vw, 1.75rem);
  --maxw: 74rem;
}

@media (prefers-color-scheme: dark) {
  :root {
    /* Dark: a blueprint. Pale line on process blue, not a website inverted. */
    --paper: #0a2136;
    --ink: #dce9f4;
    /* Lifted from #8fadc6, which measured 4.46:1 against the worst backdrop on this sheet
       (blueprint + a major grid line + a sketch stroke) and so missed AA. This is 5.05. */
    --muted: #9bb8cf;
    --rule: rgba(220, 233, 244, 0.42);
    --rule-soft: rgba(220, 233, 244, 0.17);
    --accent: #f0a35e;
    --accent-ink: #0a2136;

    --grid: rgba(220, 233, 244, 0.06);
    --grid-major: rgba(220, 233, 244, 0.1);
    --board-opacity: 0.07;
  }
}

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

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

body {
  margin: 0;
  /* Rule 2: one technical face, everywhere. No webfont — the CSP blocks every font host. */
  font-family: ui-monospace, "Cascadia Mono", "Segoe UI Mono", "SF Mono", Menlo, Consolas,
    "Liberation Mono", monospace;
  font-size: 15px;
  line-height: 1.65;
  color: var(--ink);
  background: var(--paper);
  /* dvh, not vh: on a phone `100vh` sits under the browser chrome and clips the title block. */
  min-height: 100dvh;
  overflow-x: hidden;
}

/* ── the drawing surface ─────────────────────────────────────────────────────
 *
 * Two fixed layers behind everything:
 *
 *   ::before   the drafting grid, drawn with gradients so it costs no request
 *   ::after    the repeating sketch tile (../img/whiteboard.svg), as pencil working
 *
 * Both sit at `z-index: -1`, which paints them above the page background and below all content.
 * That works only because nothing up to `body` creates a stacking context — do not put
 * `opacity`, `filter` or `transform` on `body`, or both layers vanish behind it.
 *
 * Neither animates. A landing page that burns a compositor layer forever is a cost paid by
 * every visitor for something nobody asked to watch.
 */

body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-image:
    linear-gradient(var(--grid-major) 1px, transparent 1px),
    linear-gradient(90deg, var(--grid-major) 1px, transparent 1px),
    linear-gradient(var(--grid) 1px, transparent 1px),
    linear-gradient(90deg, var(--grid) 1px, transparent 1px);
  background-size: 80px 80px, 80px 80px, 16px 16px, 16px 16px;
}

/*
 * The sketch tile is a MASK, not an image, so the layer is painted in `var(--ink)` and recolours
 * itself with the medium — graphite on vellum, pale line on blueprint — from one file.
 *
 * Guarded by @supports because a browser without masking paints the layer UNMASKED, a flat sheet
 * of ink over the whole page. Absent is the right way for this to fail.
 *
 * 🔴 What @supports does NOT protect you from is a malformed SVG. Masking is supported; the file
 * is simply broken, and the browser then masks everything out and says nothing. If the sketches
 * disappear, check that the SVG still parses before you touch a line in here. §5.107.
 */
@supports ((mask-image: url("../img/whiteboard.svg")) or (-webkit-mask-image: url("../img/whiteboard.svg"))) {
  body::after {
    content: "";
    position: fixed;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    background-color: var(--ink);
    -webkit-mask-image: url("../img/whiteboard.svg?v=2026-08-28");
    mask-image: url("../img/whiteboard.svg?v=2026-08-28");
    /* Painted larger than the tile's own 256 so a screen holds fewer repeats and the lattice
       reads as working on a wall rather than as wallpaper. */
    -webkit-mask-size: 304px 304px;
    mask-size: 304px 304px;
    -webkit-mask-repeat: repeat;
    mask-repeat: repeat;
    opacity: var(--board-opacity);
  }
}

a {
  color: inherit;
}

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

/* ── sheet frame ───────────────────────────────────────────────────────────── */

/*
 * The double rule is the sheet edge. `min-height: calc(100dvh - 2 * gap)` plus a column layout
 * is what pins the title block to the bottom of a short page without absolute positioning.
 */
.sheet {
  position: relative;
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: var(--maxw);
  min-height: calc(100dvh - (var(--sheet-gap) * 2));
  margin: var(--sheet-gap) auto;
  border: 1px solid var(--rule);
  /* A true double rule, as on a sheet edge: two thin lines with air between, not one slab.
     A 3px outline here read as a grey bar across the top of the page. */
  outline: 1px solid var(--rule);
  outline-offset: 4px;
  padding: clamp(1.25rem, 3.5vw, 2.5rem) clamp(1.25rem, 4vw, 3rem);
}

/*
 * Zone markers, as on an ISO sheet. Absolutely positioned against the frame rather than placed
 * in flow: they are furniture on the border, and nothing in the layout should move for them.
 */
.zones {
  position: absolute;
  display: flex;
  color: var(--muted);
  font-size: 0.625rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.zones-x {
  top: 0.3rem;
  left: 0;
  right: 0;
  justify-content: space-around;
}

.zones-y {
  left: 0.35rem;
  top: 0;
  bottom: 0;
  flex-direction: column;
  justify-content: space-around;
}

/* Below the sheet's own inner padding the markers would sit on top of the text. */
@media (max-width: 40rem) {
  .zones {
    display: none;
  }
}

/* ── masthead ──────────────────────────────────────────────────────────────── */

.masthead {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem 1.5rem;
  padding-bottom: clamp(1rem, 3vw, 1.75rem);
  border-bottom: 1px solid var(--rule);
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 0.7rem;
  /* 44px floor: the wordmark is a link, and a 26px mark does not make a tap target. */
  min-height: 44px;
  text-decoration: none;
  letter-spacing: 0.1em;
  font-size: 0.8125rem;
}

.brand svg {
  display: block;
  width: 26px;
  height: 26px;
  flex: none;
}

.masthead-ref {
  display: flex;
  gap: 1.5rem;
  margin: 0;
  color: var(--muted);
  font-size: 0.6875rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

/* ── the drawing ───────────────────────────────────────────────────────────── */

.drawing {
  padding: clamp(2rem, 7vw, 4.5rem) 0 clamp(1.75rem, 5vw, 3rem);
}

.discipline {
  margin: 0 0 1.5rem;
  color: var(--muted);
  font-size: 0.6875rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
}

h1 {
  margin: 0;
  font-size: clamp(1.9rem, 5.6vw, 3.4rem);
  line-height: 1.14;
  font-weight: 500;
  letter-spacing: -0.01em;
}

/*
 * The dimension line: witness ticks, fixed arrowheads, a flexible shaft, and the label sitting
 * in a break in the rule.
 *
 * 🔴 THE HEAD AND THE SHAFT MUST BE SEPARATE ELEMENTS. The first attempt drew both in one SVG
 * stretched across the gap with `preserveAspectRatio="none"`, which scales the ARROWHEAD too:
 * an 8-unit box across 217px is an 18x horizontal stretch, and the heads rendered as long thin
 * spindles. Nothing errors, it just looks wrong, and it looks wrong in a way that reads as a
 * rendering glitch rather than a mistake. The head is now a fixed 8px box and the shaft is a
 * 1px background on a flex child, so the head keeps its proportions at every width.
 */
.dim {
  display: flex;
  align-items: center;
  max-width: 34rem;
  margin: 1.6rem 0 0;
  color: var(--accent);
}

.dim-tick {
  width: 1px;
  height: 15px;
  flex: none;
  background: currentColor;
}

.dim-head {
  flex: none;
  width: 8px;
  height: 8px;
}

.dim-head svg {
  display: block;
  width: 8px;
  height: 8px;
}

.dim-rule {
  flex: 1 1 0;
  min-width: 1rem;
  height: 1px;
  background: currentColor;
}

.dim-label {
  flex: none;
  padding: 0 0.85rem;
  font-size: 0.6875rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
}

.lede {
  margin: 1.75rem 0 0;
  max-width: 52ch;
  font-size: 0.9375rem;
  line-height: 1.75;
  color: var(--muted);
}

/*
 * The callout. A drawing refers you elsewhere with a balloon and a sheet reference, so that is
 * what the one link on this page is. Not a filled button — the fill happens on hover, which is
 * also the only fill in the file.
 */
.callout {
  display: inline-flex;
  align-items: center;
  gap: 0.9rem;
  min-height: 44px;
  margin-top: 2.25rem;
  padding: 0.6rem 1.1rem 0.6rem 0.7rem;
  border: 1px solid var(--rule);
  text-decoration: none;
  transition: background-color 140ms linear, color 140ms linear, border-color 140ms linear;
}

.callout-balloon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  flex: none;
  /* Rule 1's single exception: a drawing balloon is a circle. */
  border: 1px solid currentColor;
  border-radius: 50%;
  font-size: 0.75rem;
  letter-spacing: 0.04em;
}

.callout-text {
  display: flex;
  flex-direction: column;
  line-height: 1.35;
}

.callout-k {
  color: var(--muted);
  font-size: 0.625rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  transition: color 140ms linear;
}

.callout-v {
  font-size: 0.875rem;
}

.callout-arrow {
  width: 17px;
  height: 17px;
  flex: none;
}

.callout:hover {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--paper);
}

.callout:hover .callout-k {
  color: var(--paper);
}

/* ── notes ─────────────────────────────────────────────────────────────────── */

/*
 * One column, numbered. A drawing carries notes; it does not carry three equal feature columns,
 * and that rhythm is the specific thing this page was rebuilt to stop repeating.
 */
.notes {
  padding: 0 0 clamp(2rem, 6vw, 3.5rem);
  border-top: 1px solid var(--rule);
}

.notes h2 {
  margin: 1.25rem 0 1rem;
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--muted);
}

.notes ol {
  margin: 0;
  padding: 0;
  max-width: 68ch;
  list-style: none;
  counter-reset: note;
}

.notes li {
  position: relative;
  counter-increment: note;
  padding: 0.7rem 0 0.7rem 3.25rem;
  border-top: 1px solid var(--rule-soft);
  font-size: 0.875rem;
  color: var(--muted);
}

.notes li:first-child {
  border-top: 0;
}

.notes li::before {
  content: counter(note, decimal-leading-zero);
  position: absolute;
  left: 0;
  top: 0.7rem;
  color: var(--accent);
  font-size: 0.75rem;
  letter-spacing: 0.1em;
}

.notes b {
  color: var(--ink);
  font-weight: 500;
}

/* ── title block ───────────────────────────────────────────────────────────── */

/*
 * `margin-top: auto` inside the sheet's flex column is what drops this to the bottom edge on a
 * short page, which is where a title block belongs.
 */
.titleblock {
  margin-top: auto;
}

.tb {
  display: grid;
  grid-template-columns: 2fr 1fr 1fr 1fr 1fr;
  border: 1px solid var(--rule);
}

/* One shared rule between cells: each cell draws its own left and top, the grid does the rest. */
.tb-cell {
  padding: 0.6rem 0.8rem;
  border-left: 1px solid var(--rule-soft);
  min-width: 0;
}

.tb-cell:first-child,
.tb-title {
  border-left: 0;
}

.tb-ref {
  grid-column: 2 / -1;
  border-left: 0;
  border-top: 1px solid var(--rule-soft);
}

.tb-k {
  display: block;
  color: var(--muted);
  font-size: 0.5625rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
}

.tb-v {
  display: block;
  margin-top: 0.2rem;
  font-size: 0.8125rem;
  overflow-wrap: anywhere;
}

a.tb-v {
  color: var(--accent);
  text-decoration: none;
  /* 44px floor, same as every other link here. An earlier 30px was a compromise to keep the
     title-block cell compact; the cell can grow instead. The floor is not the thing to trade. */
  min-height: 44px;
  display: flex;
  align-items: center;
}

a.tb-v:hover {
  text-decoration: underline;
}

.tb-foot {
  margin: 0.9rem 0 0;
  color: var(--muted);
  font-size: 0.625rem;
  letter-spacing: 0.1em;
}

@media (max-width: 48rem) {
  .tb {
    grid-template-columns: 1fr 1fr;
  }

  .tb-cell {
    border-top: 1px solid var(--rule-soft);
  }

  .tb-title {
    grid-column: 1 / -1;
    border-top: 0;
  }

  .tb-cell:nth-child(2n) {
    border-left: 1px solid var(--rule-soft);
  }

  .tb-cell:nth-child(2n + 1) {
    border-left: 0;
  }

  .tb-ref {
    grid-column: 1 / -1;
    border-left: 0;
  }
}

/* ── motion ────────────────────────────────────────────────────────────────── */

@keyframes rise {
  from {
    opacity: 0;
    transform: translateY(8px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

.rise {
  animation: rise 520ms cubic-bezier(0.22, 1, 0.36, 1) both;
}

.d1 { animation-delay: 60ms; }
.d2 { animation-delay: 130ms; }
.d3 { animation-delay: 200ms; }
.d4 { animation-delay: 270ms; }

/*
 * Reduced motion removes the movement but NOT the content. `animation: none` on a `both`-filled
 * keyframe would leave the element stuck at `opacity: 0` — invisible, permanently, for exactly
 * the people who asked for less motion.
 *
 * `animation-iteration-count` is capped too. Nothing here loops today, but squashing a
 * hypothetical infinite animation to 1ms without capping the count does not stop it — it
 * restarts it a thousand times a second, aimed at the people who asked for less of exactly this.
 */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    animation-delay: 0ms !important;
    transition-duration: 1ms !important;
  }
}

/*
 * CONTRAST, measured in a browser against the real stack rather than against the flat token:
 * paper → a major grid line → a sketch stroke sitting directly behind the glyph, which is the
 * worst case anywhere on the sheet. Both media clear AA on every text colour:
 *
 *                              blueprint   vellum
 *   heading / note lead-in         8.46     10.14
 *   muted body, keys, zones        5.05      5.30
 *   accent (dimension, numbers)    5.03      4.83   ← least headroom, check this first
 *
 * 🔴 BOTH MUTED VALUES WERE WRONG ON THE FIRST PASS AND BOTH MISSED AA — 4.46 on the blueprint,
 * 4.27 on the vellum — because they were picked against the FLAT paper token and this sheet has
 * two more layers on top of it. A grid line plus a sketch stroke is worth roughly 0.6 of a
 * ratio point here. Measure against what is actually behind the text, not against --paper.
 *
 * If you raise --board-opacity, strengthen --grid-major, or touch either --muted, re-measure.
 */
