/*
 * TensorActivity — one stylesheet for the whole site.
 *
 * ---------------------------------------------------------------------------
 * The system, in one paragraph
 * ---------------------------------------------------------------------------
 * Everything here is taken from the mark: an orange square with a black
 * chevron cut into it, sitting on paper. That gives three materials and a rule
 * for each. Paper is the ground and is never covered up. Orange is a *block* —
 * a filled field, never thin text. Black is the mark drawn on top, and carries
 * all the reading. Structure is made from hairlines and space, not from boxes.
 *
 * The previous version of this file took its palette from the Veltax privacy
 * page — a muted #9f4c2a — on the grounds that matching was cheaper than
 * deciding. The cost of that shortcut was a site that did not match its own
 * favicon. The accent here is #fb6302, the colour actually in the logo.
 *
 * ---------------------------------------------------------------------------
 * Why orange is never text
 * ---------------------------------------------------------------------------
 * #fb6302 on the paper ground is 2.76:1. That fails WCAG AA for body text and
 * fails it for large text too, so no amount of "but it's only a heading" saves
 * it. Black on orange, however, is 6.07:1 — comfortably passing.
 *
 * Every pairing in this file was computed rather than eyeballed, and one of
 * them moved as a result: --ink-faint used to be #857d74, which is 3.67:1 and
 * fails for every size it is actually used at.
 *
 * So the contrast maths and the logo agree: orange is a surface you put black
 * on. That is a constraint worth having, because it stops the accent being
 * sprinkled over everything and makes each appearance of it mean something.
 * Links get an orange *underline* — decoration, exempt, and legible — and
 * become an orange block on hover.
 */

/* ------------------------------------------------------------------- type --
 *
 * Self-hosted, and that is a design decision rather than a performance one. A
 * page whose argument is "software that does not need to know you" cannot ask
 * every reader's browser to announce itself to a font CDN in order to render
 * the sentence. Fetched once at build time, served from our own origin.
 *
 * Both are variable fonts, so one file per family covers every weight we use.
 * Newsreader carries an optical-size axis: it thins and sharpens on its own at
 * display sizes and stays sturdy at reading sizes, which is the part a static
 * font cannot do and the reason it was chosen over a static serif.
 *
 * `optional`, not `swap`, and that was a bug fixed rather than a preference.
 *
 * With `swap` the browser renders the fallback immediately and replaces it the
 * moment the real face arrives. On a fast connection you never see it. On a
 * real one the page spends seconds in Times, then re-renders in Newsreader with
 * different metrics — different line breaks, different rhythm, everything
 * below it moving. That does not read as a font loading. It reads as the site
 * showing you one page and then a different one, which is precisely how it was
 * reported.
 *
 * An earlier version of this comment asserted the fallbacks were "metrically
 * close enough that the reflow is small". That was assumed, never measured,
 * and it is wrong — and it cannot be fixed by measuring, because the fallback
 * that actually renders depends on the visitor's operating system.
 *
 * `optional` removes the swap window entirely: the browser gives the font about
 * 100ms, and if it does not arrive, that page view uses the fallback and keeps
 * it. No replacement, no reflow, nothing moves. Combined with the preload in
 * every page's <head>, the font almost always wins that race.
 *
 * The cost, stated plainly: a first visit on a slow connection renders in the
 * fallback serif rather than Newsreader. That page looks less considered than
 * intended — but it looks like one page instead of two, and the font is cached
 * for every navigation after it.
 */

@font-face {
  font-family: "Newsreader";
  src: url("/assets/fonts/newsreader-latin.woff2") format("woff2-variations");
  font-weight: 200 700;
  font-style: normal;
  font-display: optional;
}

@font-face {
  font-family: "Inter Tight";
  src: url("/assets/fonts/intertight-latin.woff2") format("woff2-variations");
  font-weight: 400 700;
  font-style: normal;
  font-display: optional;
}

:root {
  color-scheme: light dark;

  /* Paper, and the two tones of it. `--sunk` is a slightly deeper paper for
     the rare inset; there is deliberately no white surface, because a white
     card on cream cancels the cream out and that ground is the whole look. */
  --paper: #f7f3ed;
  --paper-sunk: #efe8dd;
  --paper-deep: #e7ded1;

  --ink: #16130f;
  --ink-soft: #57504a;
  --ink-faint: #736b61;

  --orange: #fb6302;
  --orange-deep: #d94f00;

  /* Hairlines. Two weights: one you are meant to notice, one you are not. */
  --rule: #16130f;
  --rule-faint: #d9d0c3;

  --danger: #a3231b;
  --danger-soft: #f6e5e2;

  --serif: "Newsreader", ui-serif, Georgia, "Times New Roman", serif;
  --sans: "Inter Tight", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  /* The reading column. 66-ish characters at the body size — the measure a
     long argument needs to stay readable, and the reason nothing on the
     homepage is allowed to be wider. */
  --measure: 40rem;
  --page: 74rem;
}

@media (prefers-color-scheme: dark) {
  :root {
    --paper: #14120f;
    --paper-sunk: #1c1915;
    --paper-deep: #24201b;

    --ink: #f2ece3;
    --ink-soft: #b2a89c;
    --ink-faint: #8a8076;

    /* Orange lifts slightly on a dark ground: #fb6302 against near-black is
       6.0:1 and legible, but a touch of extra luminance keeps it from
       vibrating against the paper at large sizes. */
    --orange: #ff7324;
    --orange-deep: #fb6302;

    --rule: #f2ece3;
    --rule-faint: #322c25;

    --danger: #ef6a5e;
    --danger-soft: #2a1917;
  }
}

* { box-sizing: border-box; }

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

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--serif);
  font-size: 1.1875rem;
  line-height: 1.68;
  font-synthesis-weight: none;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* The grain. The one thing about the old site worth keeping was that the
   background read as paper, and it was doing that on a hex value alone. This
   is a tiled turbulence field at very low opacity — enough fibre to feel like
   a surface, not enough to read as texture or to be noticed as an effect.
   `fixed` so it belongs to the sheet rather than scrolling with the words. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  opacity: .055;
  mix-blend-mode: multiply;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 180px 180px;
}

@media (prefers-color-scheme: dark) {
  /* Multiply on a dark ground darkens nothing visible, so the grain has to
     come from the other direction. It also has to be much weaker: the same
     amplitude that reads as fibre on cream reads as sensor noise near black,
     because there is far more perceptual resolution per code value down
     there. Measured rather than guessed — matched until the two themes had
     comparable standard deviation over a blank patch. */
  body::before { mix-blend-mode: screen; opacity: .018; }
}

/* ----------------------------------------------------------------- layout */

.wrap  { max-width: var(--page); margin: 0 auto; padding: 0 clamp(1.25rem, 5vw, 3rem); }
.column { max-width: var(--measure); }

/* The reading column is offset, not centred. A block of text centred in a wide
   page has two equal margins and no anchor; hung against the left with the
   numerals in the gutter, it reads as a printed page and gives the section
   markers somewhere to live. */
main { padding-top: clamp(2.5rem, 6vw, 4.5rem); }
main .column { margin-left: max(0px, min(12vw, 9rem)); }

@media (max-width: 60rem) {
  main .column { margin-left: 0; }
}

/* -------------------------------------------------------------------- nav */

header.site {
  border-bottom: 1px solid var(--rule);
  background: transparent;
}

header.site .wrap {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  min-height: 4.25rem;
  flex-wrap: wrap;
}

.brand {
  font-family: var(--sans);
  font-weight: 600;
  font-size: 1.0625rem;
  letter-spacing: -.022em;
  color: var(--ink);
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  gap: .625rem;
}
/* The mark is decorative here — the word beside it already says the name, so
   it carries an empty alt and must not add a second reading of it. Square,
   not rounded: the mark has hard corners and softening them here would make
   the favicon and the header disagree about what the logo is. */
.brand img { display: block; border-radius: 0; }
/* The wordmark is one flex item however it is marked up, so a name split
   across a text node and a <span> cannot have the mark's gap opened inside
   it — which is exactly what "Tensor Activity" was. */
.brand > span { display: contents; }
.brand:hover { color: var(--ink); }

nav.site {
  display: flex;
  gap: 1.5rem;
  align-items: center;
  flex-wrap: wrap;
  font-family: var(--sans);
  font-size: .875rem;
  letter-spacing: -.005em;
}
nav.site a {
  color: var(--ink-soft);
  text-decoration: none;
  padding: .15em 0;
  border-bottom: 1px solid transparent;
}
nav.site a:hover { color: var(--ink); border-bottom-color: var(--orange); }

/* ------------------------------------------------------------- typography */

h1 {
  font-size: clamp(2.25rem, 4.5vw, 3.25rem);
  font-weight: 400;
  line-height: .98;
  letter-spacing: -.035em;
  margin: 0 0 1.5rem;
  text-wrap: balance;
}

h2 {
  font-size: clamp(1.75rem, 3.4vw, 2.375rem);
  font-weight: 500;
  line-height: 1.12;
  letter-spacing: -.024em;
  margin: 0 0 .875rem;
  color: var(--ink);
  text-wrap: balance;
}

h3 {
  font-family: var(--sans);
  font-size: .8125rem;
  font-weight: 600;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--ink-soft);
  margin: 0 0 .625rem;
}

p { margin: 0 0 1.15rem; }

.lead {
  font-size: clamp(1.25rem, 2.1vw, 1.5rem);
  line-height: 1.5;
  color: var(--ink-soft);
  letter-spacing: -.012em;
}

.muted { color: var(--ink-soft); }
.faint { color: var(--ink-faint); }
.small { font-size: .9375rem; line-height: 1.6; }

/* Sans is for furniture — anything you operate rather than read. Keeping the
   two jobs on two families is what stops a form control reading as prose. */
.ui, label, th, .pill, .stamp, button, .button, input, select, nav.site, footer.site {
  font-family: var(--sans);
}

a {
  color: var(--ink);
  text-decoration: underline;
  text-decoration-color: var(--orange);
  text-decoration-thickness: 1.5px;
  text-underline-offset: .2em;
}
/* Hover promotes the underline into the block the accent is actually for. */
a:hover {
  background: var(--orange);
  color: #16130f;
  text-decoration-color: transparent;
  box-shadow: 0 0 0 .12em var(--orange);
}

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

/* --------------------------------------------------------------- sections --
 *
 * Numbered, with the numeral hung in the left gutter where the offset column
 * leaves room for it. On a narrow screen there is no gutter, so it drops back
 * inline above the heading rather than overlapping the text. */

.section {
  position: relative;
  margin: 0;
  padding: 3.25rem 0;
  border-top: 1px solid var(--rule-faint);
}
.section:first-of-type { border-top: 0; }

.section > .num {
  font-family: var(--sans);
  font-size: .75rem;
  font-weight: 600;
  letter-spacing: .1em;
  font-variant-numeric: tabular-nums;
  color: var(--ink-faint);
  display: block;
  margin-bottom: .75rem;
}

@media (min-width: 60rem) {
  .section > .num {
    position: absolute;
    left: max(-9rem, -12vw);
    top: 3.6rem;
    margin: 0;
  }
}

/* The square is the mark, reduced to its ground. It is the only decorative
   use of orange on the page and it is the same shape as the logo. */
.section > .num::before {
  content: "";
  display: inline-block;
  width: .5rem;
  height: .5rem;
  background: var(--orange);
  margin-right: .5rem;
  vertical-align: baseline;
}

/* ----------------------------------------------------------------- pieces */

/* A run of points, separated by hairlines rather than boxed into cards. Four
   bordered rounded rectangles in a grid is the shape of a directory listing,
   which is the exact thing this site is not. */
.points { margin: 2rem 0 0; }

.point {
  padding: 1.5rem 0;
  border-top: 1px solid var(--rule-faint);
  display: grid;
  grid-template-columns: minmax(0, 15rem) minmax(0, 1fr);
  gap: .5rem 2.5rem;
  align-items: start;
}
.point > h3 { margin: .3rem 0 0; }
.point > p  { margin: 0; }

@media (max-width: 44rem) {
  .point { grid-template-columns: 1fr; gap: .4rem; }
}

/* A quiet state label. Not a pill — a stamp: squared off, hairline, uppercase,
   the way a status is printed on a form rather than a badge on a dashboard. */
.stamp {
  display: inline-block;
  font-size: .6875rem;
  font-weight: 600;
  letter-spacing: .1em;
  text-transform: uppercase;
  padding: .25em .6em;
  border: 1px solid var(--rule-faint);
  color: var(--ink-soft);
  background: transparent;
}
.stamp.on { background: var(--orange); border-color: var(--orange); color: #16130f; }

/* Kept because pages still reference it, redefined so it cannot reintroduce
   the card look: paper, one hairline, square corners, no shadow. */
.card {
  background: var(--paper-sunk);
  border: 1px solid var(--rule-faint);
  border-radius: 2px;
  padding: 1.5rem;
}

.grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
  gap: 1.25rem;
}

.rule { border: 0; border-top: 1px solid var(--rule-faint); margin: 2.5rem 0; }

/* ------------------------------------------------------------------ input */

button, .button {
  font-family: var(--sans);
  font-size: .9375rem;
  font-weight: 550;
  letter-spacing: -.008em;
  border-radius: 3px;
  border: 1px solid var(--ink);
  background: var(--ink);
  color: var(--paper);
  padding: .7rem 1.15rem;
  cursor: pointer;
  text-decoration: none;
  display: inline-block;
  transition: background .12s ease, color .12s ease, border-color .12s ease;
}
/* Black is the resting state and orange is the reward for reaching for it.
   That is the mark's own logic — a black form on an orange ground — and it
   means the accent appears exactly where a person is already looking. */
button:hover, .button:hover {
  background: var(--orange);
  border-color: var(--orange);
  color: #16130f;
  box-shadow: none;
}

button.secondary, .button.secondary {
  background: transparent;
  color: var(--ink);
  border-color: var(--rule);
}
button.secondary:hover, .button.secondary:hover {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--paper);
}

/* Actions that sit inside a block of content rather than ending one. Same
   button, less of it — so a row of them reads as controls without competing
   with the primary action on the page. */
button.small, .button.small {
  font-size: .8125rem;
  padding: .45rem .8rem;
  letter-spacing: -.004em;
}

button.quiet, .button.quiet {
  background: transparent;
  color: var(--ink-soft);
  border-color: transparent;
  padding-left: 0;
  padding-right: 0;
  text-decoration: underline;
  text-decoration-color: var(--orange);
  text-underline-offset: .2em;
}
button.quiet:hover, .button.quiet:hover { background: transparent; color: var(--ink); }

button.danger { background: transparent; border-color: var(--danger); color: var(--danger); }
button.danger:hover { background: var(--danger); border-color: var(--danger); color: var(--paper); }
button.danger.solid { background: var(--danger); border-color: var(--danger); color: var(--paper); }
button.danger.solid:hover { background: var(--danger); color: var(--paper); opacity: .85; }

button[disabled] { opacity: .45; cursor: not-allowed; }
button[disabled]:hover { background: var(--ink); border-color: var(--ink); color: var(--paper); }

.row { display: flex; gap: .625rem; flex-wrap: wrap; align-items: center; }

label {
  display: block;
  margin: 1rem 0 .35rem;
  font-size: .8125rem;
  font-weight: 500;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

input, select {
  font-family: var(--sans);
  font-size: 1rem;
  width: 100%;
  padding: .7rem .8rem;
  border-radius: 3px;
  border: 1px solid var(--rule-faint);
  background: var(--paper-sunk);
  color: var(--ink);
}
input:focus, select:focus {
  outline: 2px solid var(--orange);
  outline-offset: 1px;
  border-color: transparent;
}

/* ------------------------------------------------------------------ table */

table { width: 100%; border-collapse: collapse; font-family: var(--sans); }
th, td {
  text-align: left;
  padding: .7rem .5rem .7rem 0;
  border-bottom: 1px solid var(--rule-faint);
  font-size: .9375rem;
  vertical-align: baseline;
}
th {
  color: var(--ink-faint);
  font-weight: 600;
  font-size: .75rem;
  letter-spacing: .08em;
  text-transform: uppercase;
  width: 12rem;
}
tr:last-child td, tr:last-child th { border-bottom: none; }

.scroll { overflow-x: auto; }

/* ---------------------------------------------------------------- notices */

.notice {
  border-left: 3px solid var(--orange);
  padding: .9rem 1.1rem;
  margin: 1rem 0;
  background: var(--paper-sunk);
  font-size: .9375rem;
  line-height: 1.6;
}
.notice.bad { border-left-color: var(--danger); background: var(--danger-soft); }

.mono, code {
  font-family: var(--mono);
  font-size: .875em;
  letter-spacing: -.01em;
}

.hidden { display: none !important; }

/* ----------------------------------------------------------------- footer */

footer.site {
  border-top: 1px solid var(--rule);
  padding: 2rem 0 3rem;
  color: var(--ink-soft);
  font-size: .8125rem;
  letter-spacing: -.005em;
}
footer.site .wrap { display: flex; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }
footer.site a {
  color: var(--ink-soft);
  text-decoration: none;
  border-bottom: 1px solid transparent;
  padding-bottom: 1px;
}
footer.site a:hover {
  background: transparent;
  box-shadow: none;
  color: var(--ink);
  border-bottom-color: var(--orange);
}

/* ---------------------------------------------------------------- respect */

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; scroll-behavior: auto !important; }
}

@media print {
  body::before { display: none; }
  header.site, footer.site { border-color: #000; }
  a { text-decoration-color: #000; }
}
