/* ============================================================================
   FIBONACCI ARTS — fibonacciarts.com
   Design system: CONCEPT 16 · BLOOM (see design-ideas/16-bloom.html)

   The Fibonacci Labs system — measured grid, golden-spiral canvas, Fibonacci
   wave stack, terminal cue, boxed letter-mark, tracked mono annotation —
   carried over to Arts and taken off the cold blue onto saturated warm neon.
   The practice is addressable light; the page should look lit, not printed.

   Structure: one column. Title, a little summary, and a picture if the entry
   has one, and it just keeps going down. Nothing is a card.
   ========================================================================= */

/* ============================================================================
   1 · TOKENS
   ========================================================================= */
:root {
  /* The ground is very nearly black — a trace of violet in it, no more. It
     was a lit purple before, which raised the floor under everything and left
     the neon competing with its own background instead of against nothing.
     Every saturated hue on the page is measured against this, so it is the
     single biggest lever on how much the colour reads.

     --stock-rgb is the same colour as channels, for the handful of places
     that need the ground at partial alpha. They drifted apart once already;
     deriving them stops that. */
  --stock-rgb: 4, 2, 8;
  --stock: rgb(var(--stock-rgb));
  --stock-2: #100817;
  --ink: #fdeef6;
  --read: rgba(253, 238, 246, 0.78);
  --dim: rgba(253, 238, 246, 0.62);
  --rest: rgba(253, 238, 246, 0.44);

  /* TWO accents, and between them they are the only colours any text is ever
     set in. Both sit in the red end of warm — off plain orange, pushed toward
     pink — and both are left at full saturation.
       --hot   the loud one: the headline's lit word, the mark, the cue
       --warm  the working one: dates, read links, the annotation
     The rule stands: NOTHING YOU READ CARRIES A GRADIENT OR CHANGES COLOUR.
     Every hue that moves lives in the rules, grid and canvases. */
  --hot: #ff6f4e;   /* coral — warm, saturated, not red */
  --warm: #ffa445;  /* amber-orange */

  /* The palette is a ramp, CENTRED ON WARM: orange at dead centre, red and
     amber either side. It stops at DEEP PURPLE rather than running out to
     blue, and reaches only far enough the other way for a TOUCH OF GREEN.
     Saturation is held at 88% throughout — wherever colour appears it is
     saturated; restraint is carried by opacity instead.

     THREE palettes, each only FOUR BROAD BANDS rather than nine thin ones —
     the eye should read a sweep of colour, not a row of little bars:
       1 · WARM    red-coral → orange → amber → gold
       2 · VIOLET  magenta → pink → violet → deep purple
       3 · COOL    green-teal → teal → blue → indigo
     And they OSCILLATE rather than cycle: the window runs 1→2→3 and back
     again (`alternate` + `ease-in-out`), so the page eases out at warm,
     drifts through violet into cool, and eases back. No wrap, no repeated
     palette needed to close the loop. */
  --spectrum: linear-gradient(90deg,
    hsl(8 88% 60%) 0%, hsl(26 88% 60%) 9.78%, hsl(42 88% 60%) 19.56%,
    hsl(56 88% 60%) 29.33%, hsl(312 88% 60%) 33.33%,
    hsl(332 88% 60%) 43.11%, hsl(288 88% 60%) 52.89%,
    hsl(264 88% 60%) 62.67%, hsl(158 88% 60%) 66.67%,
    hsl(186 88% 60%) 76.44%, hsl(210 88% 60%) 86.22%,
    hsl(238 88% 60%) 96%);
  --spectrum-v: linear-gradient(180deg,
    hsl(8 88% 60%) 0%, hsl(26 88% 60%) 9.78%, hsl(42 88% 60%) 19.56%,
    hsl(56 88% 60%) 29.33%, hsl(312 88% 60%) 33.33%,
    hsl(332 88% 60%) 43.11%, hsl(288 88% 60%) 52.89%,
    hsl(264 88% 60%) 62.67%, hsl(158 88% 60%) 66.67%,
    hsl(186 88% 60%) 76.44%, hsl(210 88% 60%) 86.22%,
    hsl(238 88% 60%) 96%);

  --rule: rgba(253, 238, 246, 0.15);
  --rule-soft: rgba(253, 238, 246, 0.08);

  --sans: "Space Grotesk", "Helvetica Neue", Helvetica, Arial,
    "Liberation Sans", sans-serif;
  --mono: "JetBrains Mono", ui-monospace, "SF Mono", "DejaVu Sans Mono",
    "Liberation Mono", Menlo, "Courier New", monospace;

  --s1: 8px;
  --s2: 16px;
  --s3: 26px;
  --s4: 42px;
  --s5: 68px;
  --s6: 104px;

  /* The column grows with the screen rather than sitting at one width in the
     middle of a void. It steps rather than scaling fluidly, so the entry grid
     below changes proportion at points that were actually looked at. */
  --page-max: 1000px;
}

@media (min-width: 1200px) { :root { --page-max: 1180px; } }
@media (min-width: 1500px) { :root { --page-max: 1360px; } }
@media (min-width: 1900px) { :root { --page-max: 1500px; } }

/* ============================================================================
   2 · THE RAMP
   Every lit rule carries this: the palette rocking through itself. A
   one-palette-wide window sliding the length of the strip and back.
   `alternate` is what makes it an oscillation rather than a cycle, and it is
   why nothing has to repeat to close the loop.

   --t0 is set once on load from the wall clock (see the inline script in each
   page's <head>, which must run before first paint), so a reload drops you
   back into the same point of the oscillation instead of restarting it — the
   same trick the Labs site plays with its waves. Rules take a further phase
   offset from --i, so no two show the same palette at once.
   ========================================================================= */
@keyframes palettes {
  from { background-position: 0% 50%; }
  to   { background-position: 100% 50%; }
}
@keyframes palettesV {
  from { background-position: 50% 0%; }
  to   { background-position: 50% 100%; }
}

.ramp {
  background-size: 300% 100%;
  animation: palettes 40s ease-in-out infinite alternate;
  animation-delay: calc((var(--i, 0) * -4s) - var(--t0, 0s));
}
.ramp-v {
  background-size: 100% 300%;
  animation: palettesV 40s ease-in-out infinite alternate;
  animation-delay: calc((var(--i, 0) * -4s) - var(--t0, 0s));
}

/* The same walk as a FLAT COLOUR, for things too small to carry a gradient.

   A sliding window cannot guarantee one colour: .ramp's window is a
   percentage of the element, so on a 20px word it is most of the strip at
   once, and even a very narrow window still straddles the hard junction
   between palettes (gold → magenta) at some point in the cycle. So the small
   stuff walks the ramp by keyframe instead — one flat hue at a time, the
   same 40s oscillation, the same --t0 seeding. Anything that has to stay
   legible uses this; the rules and canvases keep the gradient. */
@keyframes walk {
  0%    { color: hsl(8 88% 62%); }
  12.5% { color: hsl(30 88% 62%); }
  25%   { color: hsl(50 88% 60%); }
  37.5% { color: hsl(325 88% 66%); }
  50%   { color: hsl(300 88% 68%); }
  62.5% { color: hsl(266 88% 70%); }
  75%   { color: hsl(170 80% 55%); }
  87.5% { color: hsl(200 88% 62%); }
  100%  { color: hsl(232 88% 68%); }
}

/* ============================================================================
   3 · BASE
   ========================================================================= */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

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

body {
  background: var(--stock);
  color: var(--ink);
  font-family: var(--sans);
  line-height: 1.6;
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

a { color: inherit; text-decoration: none; }

::selection {
  background: hsl(11 100% 65% / 0.3);
  color: var(--ink);
}

/* ============================================================================
   4 · THE BACKGROUND STACK
   The Labs background stack, in neon: measured grid, colour wash, the two
   live canvases, and just enough falloff at the edges to keep type crisp.
   ========================================================================= */
.grid-bg {
  position: fixed;
  inset: 0;
  z-index: -4;
  opacity: 0.2;
  background-image: var(--spectrum);
  -webkit-mask-image:
    linear-gradient(#fff 1px, transparent 1px),
    linear-gradient(90deg, #fff 1px, transparent 1px),
    linear-gradient(rgba(255, 255, 255, 0.5) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255, 255, 255, 0.5) 1px, transparent 1px);
  mask-image:
    linear-gradient(#fff 1px, transparent 1px),
    linear-gradient(90deg, #fff 1px, transparent 1px),
    linear-gradient(rgba(255, 255, 255, 0.5) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255, 255, 255, 0.5) 1px, transparent 1px);
  -webkit-mask-size: 100px 100px, 100px 100px, 20px 20px, 20px 20px;
  mask-size: 100px 100px, 100px 100px, 20px 20px, 20px 20px;
  background-size: 300% 100%;
  animation: palettes 40s ease-in-out infinite alternate;
  animation-delay: calc(0s - var(--t0, 0s));
}

/* Pulled well back. At .28 this was a colour cast over the entire page: it
   lifted the black everywhere, so the lit elements had a tinted ground to
   fight rather than a dark one to burn against. Now it reads as a few distant
   glows and the space between them stays black. */
.wash {
  position: fixed;
  inset: 0;
  z-index: -3;
  pointer-events: none;
  opacity: 0.16;
  background:
    radial-gradient(ellipse at 74% 16%, rgba(255, 64, 96, 0.5) 0, transparent 46%),
    radial-gradient(ellipse at 10% 26%, rgba(126, 48, 224, 0.48) 0, transparent 42%),
    radial-gradient(ellipse at 20% 76%, rgba(196, 56, 220, 0.42) 0, transparent 44%),
    radial-gradient(ellipse at 86% 74%, rgba(96, 214, 110, 0.26) 0, transparent 38%),
    radial-gradient(ellipse at 52% 94%, rgba(255, 132, 32, 0.48) 0, transparent 44%);
}

#spiral,
#waves {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: -2;
  pointer-events: none;
}
#spiral { opacity: 0.62; }
/* the wave stack sits under running text, so it is held back by OPACITY
   rather than by draining the colour out of it */
#waves { opacity: 0.9; }

.vignette {
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background: radial-gradient(ellipse at 50% 45%,
    transparent 46%, rgba(var(--stock-rgb), 0.62) 100%);
}

/* ============================================================================
   5 · PAGE SHELL & MASTHEAD
   ========================================================================= */
.page {
  position: relative;
  z-index: 1;
  max-width: var(--page-max);
  margin: 0 auto;
  padding: 0 40px;
}

/* Baseline, not centre and not top. The two sides are different shapes — a
   46px mark beside a two-line block of 11px mono — so aligning their boxes
   by either edge leaves the TYPE misaligned, which is the only part anyone
   reads. On the baseline, the wordmark and the first annotation line sit on
   one line and the second line hangs below it. */
.top {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--s4);
  padding: var(--s3) 0 0;
}
/* the mark keeps its optical centring against the wordmark; only the pair of
   them, as a unit, meets the annotation on the baseline */
.logo { align-items: center; }

.logo { display: flex; align-items: center; gap: 14px; }

/* The mark is a lit box, so it walks the palette like everything else lit on
   the page — but as ONE COLOUR AT A TIME, by keyframe. It is the smallest
   thing on the page and it has to be read; the whole ramp crushed into 46px
   was a smear rather than a colour.

   One animation drives all three parts through currentColor, so the ring,
   the letters and the glow are always the same hue and the mark reads as a
   single lit object. Under reduced motion the animation stops and it settles
   on --hot, which is where the mark started. */
.logo-mark {
  width: 46px;
  height: 46px;
  flex: none;
  border-radius: 10px;
  padding: 1.6px;
  color: var(--hot);
  background-color: currentColor;
  box-shadow: 0 0 20px -4px currentColor;
  animation: walk 40s ease-in-out infinite alternate;
  animation-delay: calc(0s - var(--t0, 0s));
}
.logo-mark i {
  display: grid;
  place-items: center;
  width: 100%;
  height: 100%;
  border-radius: 8.6px;
  background: var(--stock);
  font-family: var(--mono);
  font-style: normal;
  font-size: 1rem;
  font-weight: 500;
  letter-spacing: 0.02em;
  color: inherit;
}
/* no glow on the letters: at 16px it costs more legibility than it buys */
.logo-mark b { font-weight: 500; color: inherit; }

.logo-text { font-size: 1.3rem; font-weight: 400; letter-spacing: -0.02em; }

/* the Labs corner annotation — mono, tracked, quiet */
.corner {
  font-family: var(--mono);
  font-size: 0.65rem;
  line-height: 1.9;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  text-align: right;
}
.corner span {
  display: block;
  color: var(--warm);
  text-shadow: 0 0 10px hsl(31 100% 63% / 0.45);
}
.corner a { transition: color 0.2s ease; }
.corner a:hover { color: var(--ink); }

/* ============================================================================
   6 · THE STATEMENT (home)
   ========================================================================= */
/* the cue sits close under the masthead — the two read as one block */
.lede { padding: var(--s4) 0 var(--s5); }

/* the terminal line and the call to action share a line, so the button lands
   directly under the corner annotation on the same side of the page */
.cue {
  margin-bottom: var(--s2);
  font-family: var(--mono);
  font-size: 0.75rem;
  color: var(--hot);
  letter-spacing: 0.02em;
  text-shadow: 0 0 11px hsl(11 100% 65% / 0.5);
}
.cue::before { content: ">"; opacity: 0.85; margin-right: 0.45em; }

/* A caret sits ON the baseline, like the character it stands in for.

   This is why .cue is NOT a flex container: flex would make the caret a flex
   item, taking it out of inline layout, and the only alignments left are
   against the LINE BOX — which carries ascender and descender space the text
   itself is not using. Every value that fixes is a magic number tuned to one
   font's metrics, and it breaks the moment a different font loads.

   In inline layout there is no number to tune. An empty inline-block takes
   its baseline from its bottom margin edge, so the default `vertical-align:
   baseline` drops that edge exactly onto the text baseline — in whatever
   font the browser actually ended up with. Size is in `em` for the same
   reason: it tracks the type rather than a pixel guess. */
.blink {
  display: inline-block;
  vertical-align: baseline;
  width: 0.55em;
  height: 0.85em;
  margin-left: 0.3em;
  background: var(--hot);
  box-shadow: 0 0 11px hsl(11 100% 65% / 0.65);
  animation: blink 1.1s step-end infinite;
}
@keyframes blink {
  0%, 100% { opacity: 1; }
  50% { opacity: 0; }
}

h1 {
  font-size: clamp(2.3rem, 5.8vw, 4.1rem);
  font-weight: 300;
  line-height: 1.12;
  letter-spacing: -0.03em;
  max-width: 22ch;
}
/* the joins are the working accent, held back so they punctuate rather than
   compete with the word they join */
h1 b { font-weight: 300; color: var(--warm); opacity: 0.7; }

.tagline {
  position: relative;
  margin-top: var(--s3);
  font-family: var(--mono);
  font-size: 0.92rem;
  color: var(--read);
  padding-left: 1rem;
  max-width: 46ch;
  line-height: 1.8;
}
/* pseudo-elements take no class, so the bars carry the walk directly */
.tagline::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 2px;
  background-image: var(--spectrum-v);
  box-shadow: 0 0 10px 0 rgba(214, 133, 70, 0.45);
  background-size: 100% 300%;
  animation: palettesV 40s ease-in-out infinite alternate;
  animation-delay: calc(0s - var(--t0, 0s));
}

/* ============================================================================
   7 · COLUMN HEADER & THE LIT RULE
   ========================================================================= */
.hd {
  position: relative;
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--s3);
  padding: var(--s2) 0;
  font-family: var(--mono);
  font-size: 11.5px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--dim);
}

/* the lit rule — a tube, not a box.
   background-image, NOT the `background` shorthand — the shorthand resets
   background-size to auto, and this rule is declared after .ramp at equal
   specificity, so it would silently wipe the 300% window and squeeze all
   three palettes into every separator. */
.tube {
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  height: 1px;
  background-image: var(--spectrum);
  background-size: 300% 100%;
  box-shadow: 0 0 9px 0 rgba(214, 133, 70, 0.28);
}

/* ============================================================================
   8 · THE FEED — one column, every entry, picture or not
   Each entry owns a place on the ramp via --h, and they walk it: violet-blue
   at the top of the column, warm through the middle where most of the work
   sits, green at the foot.
   ========================================================================= */
.entry {
  position: relative;
  display: block;
  padding: var(--s4) 0 var(--s5);
  transition: background 0.2s ease;
}
/* a separator is at full strength standing still — hover adds light, it does
   not restore colour that was being withheld */
.entry .tube { transition: box-shadow 0.2s ease; }
.entry:hover .tube { box-shadow: 0 0 16px 1px hsl(var(--h) 88% 60% / 0.5); }
.entry:hover { background: hsl(var(--h) 88% 60% / 0.05); }
.entry:focus-visible {
  outline: 1px solid hsl(var(--h) 88% 68%);
  outline-offset: 6px;
}

.frame {
  position: relative;
  width: 100%;
  overflow: hidden;
  border-radius: 4px;
  background: var(--stock-2);
  border: 1px solid hsl(var(--h) 88% 68% / 0.22);
  margin-bottom: var(--s3);
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.entry:hover .frame {
  border-color: hsl(var(--h) 88% 70% / 0.6);
  box-shadow: 0 0 30px -8px hsl(var(--h) 88% 58% / 0.38);
}
.frame img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center top;
}

.r169 { aspect-ratio: 16 / 9; }
.r32 { aspect-ratio: 3 / 2; }

/* a photograph still to be made: an honest labelled slot in the page's own
   stock, never a decorative gradient standing in for a picture */
.slot {
  display: grid;
  place-items: center;
  background: repeating-linear-gradient(135deg,
    var(--stock-2) 0 11px, hsl(var(--h) 88% 68% / 0.075) 11px 12px);
}
.slot span {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--dim);
  border: 1px solid hsl(var(--h) 88% 68% / 0.3);
  padding: 9px 14px;
  background: rgba(var(--stock-rgb), 0.72);
}

.say { max-width: 60ch; }

/* a note with no picture keeps the Labs tagline rule, so the column still has
   something to hang on where an image would have been */
.entry.note .say { position: relative; padding-left: var(--s2); }
.entry.note .say::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 2px;
  background-image: var(--spectrum-v);
  box-shadow: 0 0 10px 0 rgba(214, 133, 70, 0.42);
  background-size: 100% 300%;
  animation: palettesV 40s ease-in-out infinite alternate;
  animation-delay: calc((var(--i, 0) * -4s) - var(--t0, 0s));
}

.meta {
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  display: flex;
  align-items: baseline;
  gap: 0.7rem;
  flex-wrap: wrap;
  color: var(--warm);
  text-shadow: 0 0 10px hsl(31 100% 63% / 0.45);
}
.meta i { font-style: normal; color: var(--rest); text-shadow: none; }
.meta b { font-weight: 400; color: var(--read); text-shadow: none; }

.entry h2 {
  font-family: var(--sans);
  font-size: clamp(1.3rem, 2.5vw, 1.72rem);
  font-weight: 400;
  letter-spacing: -0.025em;
  line-height: 1.24;
  margin: var(--s1) 0;
}

.sum { font-size: 0.98rem; font-weight: 300; color: var(--read); line-height: 1.75; }

.go {
  display: inline-block;
  margin-top: var(--s2);
  font-family: var(--mono);
  font-size: 0.8rem;
  color: var(--warm);
  transition: 0.2s ease;
}
.go em { font-style: normal; display: inline-block; transition: transform 0.2s ease; }
.entry:hover .go { text-shadow: 0 0 14px hsl(31 100% 63% / 0.7); }
.entry:hover .go em { transform: translateX(4px); }

/* A row whose page has not been written. It keeps its place in the column so
   the ramp still walks and the shape is honest, but it does not pretend to be
   a link: no read cue, no hover lift, nothing to click. */
.entry.stub { cursor: default; }
.entry.stub:hover { background: none; }
.entry.stub:hover .tube { box-shadow: 0 0 9px 0 rgba(214, 133, 70, 0.28); }
.entry.stub:hover .frame {
  border-color: hsl(var(--h) 88% 68% / 0.22);
  box-shadow: none;
}
.entry.stub .say { color: var(--dim); }

.draft-tag {
  display: inline-block;
  margin-top: var(--s2);
  padding: 5px 10px;
  border: 1px solid var(--rule);
  border-radius: 3px;
  font-family: var(--mono);
  font-size: 0.68rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--rest);
}

/* ============================================================================
   9 · BUTTON
   The one pressable thing on the site, so it is drawn as one: a bordered box
   in the page's own stock, mono and tracked like every other label. It lights
   on hover rather than filling — the page is lit, not printed.
   ========================================================================= */
/* Solid orange — the same --hot the terminal cue is set in, so the button
   reads as part of that line rather than as a third thing with its own
   colour. No walk: it held one colour at a time but still drifted through
   the palette, which pulled the eye off the type it belongs to.

   Letter-spacing puts a trailing gap after the final character, which on a
   short tracked label leaves the text visibly off-centre in its box. The
   right padding gives that width back so the label sits centred. */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 0.6em;
  padding: 13px 22px;
  padding-right: calc(22px - 0.08em);
  border: 1px solid var(--hot);
  border-radius: 4px;
  background: var(--stock);
  color: var(--hot);
  box-shadow: 0 0 22px -10px var(--hot);
  font-family: var(--mono);
  font-size: 0.78rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  white-space: nowrap;
  cursor: pointer;
  transition: box-shadow 0.2s ease;
}

/* the label and the envelope hold still at the accent, whatever the outline
   is currently doing */
.btn-label { color: var(--hot); transition: color 0.2s ease, text-shadow 0.2s ease; }
.btn .ico {
  width: 1.15em;
  height: auto;
  flex: none;
  fill: none;
  stroke: var(--hot);
  stroke-width: 1.3;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: transform 0.2s ease, stroke 0.2s ease;
}

.btn:hover,
.btn:focus-visible,
.close:hover .btn { box-shadow: 0 0 26px -6px currentColor; }
.btn:hover .btn-label,
.btn:focus-visible .btn-label,
.close:hover .btn .btn-label {
  color: var(--ink);
  text-shadow: 0 0 12px hsl(11 100% 65% / 0.5);
}
.btn:hover .ico,
.btn:focus-visible .ico,
.close:hover .btn .ico { stroke: var(--ink); transform: translateY(-1px); }

/* ============================================================================
   10 · CLOSE & COLOPHON
   ========================================================================= */
.close {
  position: relative;
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--s3);
  flex-wrap: wrap;
  padding: var(--s4) 0;
  border-bottom: 1px solid var(--rule-soft);
  font-family: var(--mono);
  font-size: 0.85rem;
  color: var(--read);
  transition: color 0.2s ease;
  width: 100%;
  background: none;
  border-left: 0;
  border-right: 0;
  border-top: 0;
  text-align: left;
  cursor: pointer;
}
.close:hover { color: var(--ink); }

.colo {
  padding: var(--s4) 0 var(--s6);
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--rest);
  line-height: 1.9;
}
.colo b { font-weight: 400; color: var(--dim); }
.colo a { color: var(--dim); transition: color 0.2s ease; }
.colo a:hover { color: var(--warm); }

/* ============================================================================
   11 · ARTICLE — an entry, opened
   The hero leads. Everything after it is one measured column on the same
   ramp: no sidebars, no cards, no pull-quotes competing with the picture.
   ========================================================================= */
.article { position: relative; z-index: 1; }

/* the article shares the home page's measure exactly, so the site has one
   column edge from top to bottom rather than a wide page and a narrow post */
.article-hero {
  position: relative;
  max-width: var(--page-max);
  margin: var(--s4) auto 0;
  padding: 0 40px;
}
/* Width alone would run the hero past the bottom of the screen: 16:9 at
   1480px is 833px tall, so on a 1080px display the reader scrolls a full
   screen of picture before reaching a word. Height is capped against the
   viewport instead, and the frame crops rather than growing — the picture
   stays the largest thing on the page without becoming the whole of it. */
.article-hero .frame {
  margin-bottom: 0;
  max-height: 72vh;
}

/* the caption drops back to the text measure and centres on it, so it reads
   as the first line of the article rather than as a label pinned under a
   wider picture — and it clears the lit rule the head hangs from */
.article-hero figcaption {
  margin: var(--s2) 0 var(--s3);
  font-family: var(--mono);
  font-size: 0.7rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--rest);
  line-height: 1.8;
}

.article-body {
  max-width: var(--page-max);
  margin: 0 auto;
  padding: 0 40px var(--s6);
}

.article-head { position: relative; padding: var(--s5) 0 var(--s3); }

.article-body h1 {
  font-size: clamp(1.9rem, 4.2vw, 2.9rem);
  font-weight: 300;
  line-height: 1.16;
  letter-spacing: -0.03em;
  max-width: 20ch;
  margin: var(--s2) 0 0;
}
.article-body h1 .subtitle {
  display: block;
  margin-top: var(--s2);
  font-size: clamp(0.98rem, 1.8vw, 1.2rem);
  font-weight: 300;
  letter-spacing: -0.01em;
  color: var(--dim);
  max-width: 34ch;
}

/* the standfirst carries the lit bar, same as the home tagline */
.standfirst {
  position: relative;
  margin-top: var(--s3);
  padding-left: 1rem;
  font-family: var(--mono);
  font-size: 0.88rem;
  line-height: 1.8;
  color: var(--read);
  max-width: 52ch;
}
.standfirst::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 2px;
  background-image: var(--spectrum-v);
  box-shadow: 0 0 10px 0 rgba(214, 133, 70, 0.45);
  background-size: 100% 300%;
  animation: palettesV 40s ease-in-out infinite alternate;
  animation-delay: calc(0s - var(--t0, 0s));
}

.prose { padding-top: var(--s4); }

.prose p {
  font-size: 1.02rem;
  font-weight: 300;
  line-height: 1.9;
  color: var(--read);
  margin-bottom: var(--s3);
}

.prose h2 {
  position: relative;
  font-family: var(--sans);
  font-size: clamp(1.2rem, 2.4vw, 1.5rem);
  font-weight: 400;
  letter-spacing: -0.02em;
  color: var(--ink);
  margin: var(--s5) 0 var(--s2);
  padding-top: var(--s3);
}

.prose strong { font-weight: 500; color: var(--ink); }
.prose em { font-style: italic; color: var(--dim); }

.prose a {
  color: var(--warm);
  border-bottom: 1px solid hsl(31 100% 63% / 0.35);
  transition: 0.2s ease;
}
.prose a:hover {
  border-bottom-color: var(--warm);
  text-shadow: 0 0 12px hsl(31 100% 63% / 0.6);
}

.prose ul { margin: 0 0 var(--s3) 1.1rem; }
.prose li {
  font-size: 1.02rem;
  font-weight: 300;
  line-height: 1.85;
  color: var(--read);
  margin-bottom: var(--s1);
}
.prose li::marker { color: var(--warm); }

/* an in-article picture, on the same honest frame as the feed */
.prose figure { margin: var(--s4) 0; }
.prose figure .frame { margin-bottom: var(--s2); }
.prose figcaption {
  font-family: var(--mono);
  font-size: 0.7rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--rest);
  line-height: 1.7;
}

/* a spec block: mono, tracked, one fact per line */
.specs {
  position: relative;
  margin: var(--s4) 0;
  padding: var(--s3) 0 var(--s3) var(--s3);
  font-family: var(--mono);
  font-size: 0.82rem;
  line-height: 2.1;
  color: var(--read);
}
.specs::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 2px;
  background-image: var(--spectrum-v);
  box-shadow: 0 0 10px 0 rgba(214, 133, 70, 0.42);
  background-size: 100% 300%;
  animation: palettesV 40s ease-in-out infinite alternate;
  animation-delay: calc((var(--i, 0) * -4s) - var(--t0, 0s));
}
.specs b { font-weight: 400; color: var(--warm); }

/* the note the piece is not finished — said plainly, not dressed up */
.draft {
  margin: var(--s3) 0 0;
  padding: var(--s2) var(--s3);
  border: 1px solid var(--rule);
  border-radius: 4px;
  font-family: var(--mono);
  font-size: 0.78rem;
  line-height: 1.8;
  color: var(--dim);
  background: rgba(24, 12, 29, 0.5);
}

/* ============================================================================
   12 · FOOTER
   ========================================================================= */
.foot {
  position: relative;
  z-index: 1;
  max-width: var(--page-max);
  margin: 0 auto;
  padding: var(--s4) 40px var(--s6);
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--rest);
  line-height: 1.9;
  display: flex;
  justify-content: space-between;
  gap: var(--s3);
  flex-wrap: wrap;
}
.foot a { color: var(--dim); transition: color 0.2s ease; }
.foot a:hover { color: var(--warm); }

/* ============================================================================
   13 · CONTACT MODAL
   The Labs multi-step form, re-lit: same three steps, same Web3Forms POST,
   drawn on the Bloom stock with the ramp on its edges.
   ========================================================================= */
.contact-modal {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: none;
  align-items: center;
  justify-content: center;
  padding: var(--s3);
}
.contact-modal.active { display: flex; }

.modal-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(var(--stock-rgb), 0.86);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}

.modal-container {
  position: relative;
  width: 100%;
  max-width: 560px;
  max-height: 90vh;
  overflow-y: auto;
  background: var(--stock);
  border: 1px solid var(--rule);
  border-radius: 6px;
  padding: var(--s5) var(--s4) var(--s4);
  box-shadow: 0 0 80px -20px hsl(11 100% 60% / 0.35);
}
/* the lit rule again, along the top edge of the panel */
.modal-container .tube { border-radius: 6px 6px 0 0; }

.modal-close {
  position: absolute;
  top: 12px;
  right: 16px;
  background: none;
  border: 0;
  color: var(--dim);
  font-size: 1.6rem;
  line-height: 1;
  cursor: pointer;
  transition: color 0.2s ease;
}
.modal-close:hover { color: var(--hot); }

.modal-progress {
  display: flex;
  align-items: center;
  gap: var(--s1);
  margin-bottom: var(--s4);
}
.progress-step {
  width: 26px;
  height: 26px;
  flex: none;
  display: grid;
  place-items: center;
  border-radius: 50%;
  border: 1px solid var(--rule);
  font-family: var(--mono);
  font-size: 0.7rem;
  color: var(--rest);
  transition: 0.2s ease;
}
.progress-step.active {
  border-color: var(--hot);
  color: var(--hot);
  box-shadow: 0 0 14px -2px hsl(11 100% 65% / 0.6);
}
.progress-step.completed { border-color: var(--warm); color: var(--warm); }
.progress-line { flex: 1; height: 1px; background: var(--rule); }

.form-step { display: none; }
.form-step.active { display: block; }

.step-tag {
  font-family: var(--mono);
  font-size: 0.72rem;
  color: var(--hot);
  letter-spacing: 0.02em;
  text-shadow: 0 0 11px hsl(11 100% 65% / 0.5);
}
.step-header h2 {
  font-size: 1.5rem;
  font-weight: 300;
  letter-spacing: -0.02em;
  margin: var(--s1) 0;
}
.step-description { font-size: 0.92rem; font-weight: 300; color: var(--dim); }

.form-fields { margin: var(--s4) 0; }
.form-group { margin-bottom: var(--s3); }
.form-group label {
  display: block;
  margin-bottom: var(--s1);
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--dim);
}
.form-group .optional { color: var(--rest); text-transform: none; letter-spacing: 0.04em; }

.form-group input,
.form-group textarea {
  width: 100%;
  padding: 12px 14px;
  background: var(--stock-2);
  border: 1px solid var(--rule);
  border-radius: 4px;
  color: var(--ink);
  font-family: var(--sans);
  font-size: 0.96rem;
  transition: 0.2s ease;
}
.form-group textarea { resize: vertical; line-height: 1.7; }
.form-group input::placeholder,
.form-group textarea::placeholder { color: var(--rest); }
.form-group input:focus,
.form-group textarea:focus {
  outline: none;
  border-color: var(--warm);
  box-shadow: 0 0 18px -6px hsl(31 100% 63% / 0.7);
}

.form-actions { display: flex; justify-content: space-between; gap: var(--s2); }
.form-actions button {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 11px 20px;
  border-radius: 4px;
  font-family: var(--mono);
  font-size: 0.8rem;
  cursor: pointer;
  transition: 0.2s ease;
}
.btn-next,
.btn-submit {
  margin-left: auto;
  background: var(--hot);
  border: 1px solid var(--hot);
  color: var(--stock);
  font-weight: 500;
}
.btn-next:hover,
.btn-submit:hover { box-shadow: 0 0 24px -6px hsl(11 100% 65% / 0.8); }
.btn-back {
  background: none;
  border: 1px solid var(--rule);
  color: var(--dim);
}
.btn-back:hover { border-color: var(--warm); color: var(--warm); }
.btn-submit:disabled { opacity: 0.55; cursor: default; box-shadow: none; }

.form-success-state,
.form-error-state { display: none; text-align: center; padding: var(--s4) 0; }
.form-success-state.active,
.form-error-state.active { display: block; }

.success-icon,
.error-icon {
  width: 52px;
  height: 52px;
  margin: 0 auto var(--s3);
  display: grid;
  place-items: center;
  border-radius: 50%;
  font-size: 1.5rem;
  border: 1px solid var(--warm);
  color: var(--warm);
  box-shadow: 0 0 26px -6px hsl(31 100% 63% / 0.6);
}
.error-icon { border-color: var(--hot); color: var(--hot); }

.form-success-state h2,
.form-error-state h2 { font-size: 1.4rem; font-weight: 300; margin-bottom: var(--s1); }
.form-success-state p,
.form-error-state p { color: var(--dim); font-weight: 300; margin-bottom: var(--s3); }
.form-error-state a { color: var(--warm); border-bottom: 1px solid hsl(31 100% 63% / 0.4); }

.btn-close-success,
.btn-retry {
  padding: 10px 20px;
  background: none;
  border: 1px solid var(--rule);
  border-radius: 4px;
  color: var(--dim);
  font-family: var(--mono);
  font-size: 0.8rem;
  cursor: pointer;
  transition: 0.2s ease;
}
.btn-close-success:hover,
.btn-retry:hover { border-color: var(--warm); color: var(--warm); }

/* a field the user left empty or malformed — flagged on the border only,
   never by recolouring the text they typed */
.form-group input.error,
.form-group textarea.error {
  border-color: var(--hot);
  box-shadow: 0 0 18px -6px hsl(11 100% 65% / 0.7);
}

/* ============================================================================
   14 · RESPONSIVE
   Narrow: the column collapses; it never changes shape.
   Wide: the entry turns on its side rather than the picture simply growing.
   A full-width 16:9 frame at 1400px is 790px tall — taller than the viewport
   it is being read in, so one entry would fill the screen and the column
   would stop reading as a list. Set side by side, the picture gets most of
   the extra width, stays a comfortable height, and the summary sits against
   it instead of under it.
   ========================================================================= */
@media (min-width: 1100px) {
  .entry {
    display: grid;
    grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr);
    gap: var(--s4);
    align-items: center;
  }
  .entry .frame { margin-bottom: 0; }
  .entry .say { max-width: none; }

  /* a note has no picture to sit beside, so it stays a single block and
     keeps a readable measure rather than running the full width */
  .entry.note { display: block; }
  .entry.note .say { max-width: 60ch; }
}

/* ── the article on a wide screen ────────────────────────────────────────
   The column does the work, not the type. Body text stays at its normal size
   at every width — scaling it up to hold the line length down just made the
   page shout. Only the display type moves. */
@media (min-width: 1600px) {
  h1 { font-size: clamp(3rem, 4.6vw, 4.9rem); }
}

@media (max-width: 820px) {
  .page { padding: 0 22px; }
  /* the wordmark and the annotation stay on one line with their TOPS level,
     reading as two parallel blocks. Stacking them put the annotation under
     the wordmark and left the masthead looking like two separate headers. */
  .top {
    align-items: flex-start;
    gap: var(--s2);
    padding: var(--s3) 0 0;
  }
  .corner { line-height: 1.7; }
  .lede { padding: var(--s3) 0 var(--s4); }
  .entry { padding: var(--s3) 0 var(--s4); }
  .r169, .r32 { aspect-ratio: 16 / 10; }
  .say { max-width: none; }

  .article-hero { padding: 0 22px; margin-top: var(--s3); }
  .article-body { padding: 0 22px var(--s5); }
  .article-head { padding: var(--s4) 0 var(--s2); }
  .foot { padding: var(--s3) 22px var(--s5); }
  .modal-container { padding: var(--s4) var(--s3) var(--s3); }
}

@media (max-width: 430px) {
  .logo-mark { width: 40px; height: 40px; }
  .logo-mark i { font-size: 0.9rem; }
  .logo-text { font-size: 1.12rem; }
  .meta { gap: 0.5rem; }
  .form-actions { flex-wrap: wrap; }
}

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