/*
 * The page wants to read like the terminal it is showing, without pretending
 * to be a terminal emulator: one monospace family, block characters for the
 * bars and the histograms, and no colour carrying meaning on its own.
 */

:root {
  --ink: #e8e2d4;
  --ink-dim: #9a9383;
  --paper: #14110d;
  --paper-raised: #1d1913;
  --rule: #332c22;
  --steel: #c8a34a;
  --blood: #a8452f;
  --mono: ui-monospace, "SF Mono", "Cascadia Mono", Menlo, Consolas, monospace;
}

@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
    --ink: #221c14;
    --ink-dim: #5f5748;
    --paper: #f4efe4;
    --paper-raised: #fffdf7;
    --rule: #d8cfbb;
    --steel: #8a6d1f;
    --blood: #8c3423;
  }
}

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

/*
 * The torch's position, registered so it can be animated at all.
 *
 * An unregistered custom property has no type, so the browser cannot
 * interpolate between two values of it and a transition on it does nothing —
 * the mask simply snaps to each new coordinate. Declaring the syntax is what
 * turns the jump into a glide, and it is the whole reason these two are here
 * rather than written straight into the gradient.
 */
@property --tx {
  syntax: "<length-percentage>";
  inherits: false;
  initial-value: 70%;
}

@property --ty {
  syntax: "<length-percentage>";
  inherits: false;
  initial-value: 25%;
}

/*
 * The blade, lit by the pointer.
 *
 * The darkness is the mask's, not the image's: the picture is a lit blade on
 * black, and what makes it "barely there" is that only a circle of it is ever
 * let through. Opacity is capped because the steel carries real highlights and
 * the tables have to stay readable over them.
 */
.blade {
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-image: url("blade.jpg");
  background-size: cover;
  background-position: center;
  /*
   * A wide, soft, dim torch reads as haze rather than steel: the grain is lost
   * to the falloff long before the eye finds it. A tighter circle with a
   * harder edge and more contrast shows the forge marks, and keeps the lit
   * area small enough that the text it crosses stays legible.
   */
  /*
   * Held as variables so the light theme can swap the treatment without
   * restating the geometry below it.
   */
  --blade-opacity: 0.3;
  --blade-filter: contrast(1.35) brightness(0.72);
  --blade-blend: normal;

  opacity: var(--blade-opacity);
  filter: var(--blade-filter);
  mix-blend-mode: var(--blade-blend);

  /* Set by the pointer; these are the fallback until it moves. */
  --tx: 70%;
  --ty: 25%;

  /*
   * The light trails the pointer rather than teleporting to it. Linear, not
   * eased: the torch is chasing a moving target and an ease-out reads as lag.
   * Each update interrupts the one before it, which is what makes a stream of
   * discrete coordinates look like one continuous sweep.
   */
  transition:
    --tx 140ms linear,
    --ty 140ms linear;

  /*
   * The gradient is written out at both properties rather than held in a
   * custom property and pulled in with var(). Safari has long been unreliable
   * about an unregistered custom property standing in for a whole mask value,
   * and the failure is not a missing torch but a missing mask — the full
   * rectangle of steel over the page.
   */
  -webkit-mask-image: radial-gradient(
    circle 240px at var(--tx) var(--ty),
    #000 0%,
    rgba(0, 0, 0, 0.72) 45%,
    transparent 78%
  );
  mask-image: radial-gradient(
    circle 240px at var(--tx) var(--ty),
    #000 0%,
    rgba(0, 0, 0, 0.72) 45%,
    transparent 78%
  );
}

/*
 * A browser that cannot mask at all would otherwise show the whole plate of
 * steel across the page. Nothing is better than that.
 */
@supports not (
    (mask-image: radial-gradient(#000, transparent)) or
    (-webkit-mask-image: radial-gradient(#000, transparent))
  ) {
  .blade {
    display: none;
  }
}

/*
 * A finger is a pointer. Touch fires pointermove while it is down, so dragging
 * or scrolling sweeps the torch across the steel, which is the same gesture
 * the mouse makes and wants no special case. What a phone does need is a
 * wider, brighter circle: the screen is small, often held in daylight, and a
 * 240px window at the opacity a monitor wants is invisible out there.
 */
@media (pointer: coarse), (hover: none) {
  .blade {
    /*
     * Only the circle widens here. This block used to set `opacity` straight,
     * which beat the themed `opacity: var(--blade-opacity)` above it — so a
     * phone in light mode wore the highest opacity in the stylesheet instead
     * of the lowest. A theme's treatment has to survive the device's.
     */
    /*
     * Longer, because a finger arrives rather than travels: a tap is a single
     * coordinate a long way from the last one, and at 140ms that still reads
     * as a jump. At 320ms the light sweeps across to meet it.
     */
    transition:
      --tx 320ms ease-out,
      --ty 320ms ease-out;
    -webkit-mask-image: radial-gradient(
      circle 300px at var(--tx) var(--ty),
      #000 0%,
      rgba(0, 0, 0, 0.75) 50%,
      transparent 82%
    );
    mask-image: radial-gradient(
      circle 300px at var(--tx) var(--ty),
      #000 0%,
      rgba(0, 0, 0, 0.75) 50%,
      transparent 82%
    );
  }
}

/*
 * A light that chases the pointer is motion, and some people have asked for
 * none. !important because the pointer writes --tx and --ty as inline styles,
 * and an inline style beats a stylesheet rule — without it this block would be
 * a comment rather than a behaviour.
 */
@media (prefers-reduced-motion: reduce) {
  .blade {
    --tx: 72% !important;
    --ty: 22% !important;
    transition: none !important;
  }
}

/*
 * Light mode gets the same steel read as a shadow rather than a gleam.
 *
 * Inverted, the picture's black ground turns white and disappears into the
 * paper, and the lit bevel becomes the dark mark the torch finds; multiplied,
 * it darkens the page where the blade is instead of laying grey over it. This
 * block used to be `display: none`, on the untested assumption that dark steel
 * on pale paper could only be a smudge — which meant every visitor whose
 * device prefers light never saw the effect at all.
 */
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) .blade {
    --blade-opacity: 0.2;
    --blade-filter: invert(1) contrast(1.2) brightness(1.02);
    --blade-blend: multiply;
  }
}

/* The same, for a page told explicitly which theme to wear. */
:root[data-theme="light"] .blade {
  --blade-opacity: 0.2;
  --blade-filter: invert(1) contrast(1.2) brightness(1.02);
  --blade-blend: multiply;
}

body {
  margin: 0;
  padding: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--mono);
  font-size: 15px;
  line-height: 1.6;
}

main {
  max-width: 62rem;
  margin: 0 auto;
  padding: 2.5rem 16px 4rem;
}

h1 {
  margin: 0;
  font-size: clamp(2rem, 8vw, 3.2rem);
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

h2 {
  margin: 2.5rem 0 0.75rem;
  font-size: 1.05rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--steel);
}

.tagline {
  margin: 0.25rem 0 1.5rem;
  color: var(--ink-dim);
}

.quote {
  margin: 0 0 1.5rem;
  padding-left: 1rem;
  border-left: 2px solid var(--blood);
  color: var(--ink);
}

.attribution {
  display: block;
  color: var(--ink-dim);
}

.note,
.aside,
.lede {
  color: var(--ink-dim);
  max-width: 62ch;
}

.lede {
  color: var(--ink);
}

/* The menu is the terminal's menu: one screen visible at a time. */
.menu {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin: 2rem 0 1rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--rule);
}

.menu-item {
  display: inline-flex;
  align-items: baseline;
  gap: 0.5rem;
  padding: 0.5rem 0.85rem;
  background: var(--paper-raised);
  color: var(--ink);
  border: 1px solid var(--rule);
  border-radius: 2px;
  font: inherit;
  cursor: pointer;
}

.menu-item:hover {
  border-color: var(--steel);
}

.menu-item.active {
  border-color: var(--steel);
  color: var(--steel);
}

.menu-item:focus-visible {
  outline: 2px solid var(--steel);
  outline-offset: 2px;
}

.menu-number {
  color: var(--ink-dim);
}

.panel {
  padding-top: 0.5rem;
}

/* Tables carry the numbers, and scroll rather than reflow on a phone. */
table {
  width: 100%;
  margin: 1rem 0;
  border-collapse: collapse;
  font-variant-numeric: tabular-nums;
  display: block;
  overflow-x: auto;
  white-space: nowrap;
}

th,
td {
  padding: 0.4rem 0.6rem;
  text-align: left;
  border-bottom: 1px solid var(--rule);
}

th {
  color: var(--ink-dim);
  font-weight: normal;
  letter-spacing: 0.04em;
}

td.num,
th:has(+ td.num) {
  text-align: right;
}

.num {
  text-align: right;
}

.warrior {
  display: block;
}

.address,
.title {
  display: block;
  color: var(--ink-dim);
  font-size: 0.85em;
}

.bar,
.spark {
  color: var(--steel);
  letter-spacing: -0.05em;
}

.legend {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 0.2rem 1rem;
  margin: 1.5rem 0 0;
  color: var(--ink-dim);
}

.legend dt {
  color: var(--steel);
}

.legend dd {
  margin: 0;
}

.candidates {
  padding-left: 1.2rem;
}

.candidates ul {
  padding-left: 1.2rem;
  list-style: none;
}

pre {
  margin: 1rem 0;
  padding: 0.9rem 1rem;
  background: var(--paper-raised);
  border: 1px solid var(--rule);
  border-radius: 2px;
  overflow-x: auto;
}

code,
kbd {
  background: var(--paper-raised);
  border: 1px solid var(--rule);
  border-radius: 2px;
  padding: 0 0.3em;
}

kbd {
  color: var(--steel);
}

.disclaimer {
  padding-left: 1.2rem;
  max-width: 68ch;
}

footer {
  margin-top: 3rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--rule);
}

a {
  color: var(--steel);
}
