# Background and ribbon

**The ground behind every page is code, not a picture. It costs no request and never animates.**

```ribbon-strip
A live strip, drawn by the same component the page uses, from the same data in src/lib/ribbons.ts. Its gradient and filter ids are its own.
```

## In plain words

This page describes the background behind every page: a colour gradient with ribbons on it, drawn in code (an inline SVG) with no image file to fetch. It stands still on purpose, because moving it cost the front page 6.9 s of main-thread work in every 20 s against 0.6 s when still.

What that is worth: a developer changes a colour by editing one named value (a design token) while tests guard contrast and stillness, and the organization gets a background that costs one paint and then nothing.

## The ground

A gradient from green-grey on the left to white on the right, so the side rail sits on the darker end and the reading side stays bright. Where no ribbon is drawn, the page ground is the light grey. Every value is a design token: a named CSS custom property (a CSS variable) holding one design decision.

```swatches
--gradient-ground | The ground, left to right
--color-ground-left | Ground, left
--color-ground-mid | Ground, middle
--color-ground-right | Ground, right
--color-ribbon-gold | Ribbon gold
--color-ribbon-gold-soft | Ribbon gold, soft
--color-ribbon-teal | Ribbon teal
--color-ribbon-teal-light | Ribbon teal, light
--color-ribbon-green | Ribbon dark green
--color-ribbon-shine | Highlight gold
--color-ribbon-shine-pale | Highlight, pale end
--color-ribbon-shine-white | Highlight, white end
--color-ribbon-flare | Flare and spark centre
--color-ribbon-spark | Spark gold
--color-ribbon-star | A flare's arms
```

## The ribbons

13 ribbons, 3 highlight strands, 2 flares and 70 sparks in one inline SVG, counted from [`src/lib/ribbons.ts`](https://github.com/SteveStout/TheYard/blob/main/src/lib/ribbons.ts) when the page opens. They start at the rail's edge and put the gold highlights up front.

```ts
['M49,-40 C439,218 570,331 346,476 S123,786 657,960', 'gold', 1.2, 0.95],
['M-37,-40 C357,85 667,365 248,588 S222,788 586,960', 'gold', 3, 0.7],
['M-22,-40 C265,155 457,274 284,454 S112,763 677,960', 'teal', 2, 0.7],
['M65,-40 C465,150 625,322 290,590 S229,811 623,960', 'gold', 10, 0.25],
['M16,-40 C333,116 460,378 312,569 S95,825 679,960', 'mixed', 1, 0.9],
['M-60,-40 C327,216 572,416 312,460 S148,803 448,960', 'teal', 18, 0.1],
```
*Live from [`src/lib/ribbons.ts`](https://github.com/SteveStout/TheYard/blob/1d6b50b/src/lib/ribbons.ts#L16-L21), region `first-ribbons`, read from this build at 1d6b50b.*

Each line is one ribbon: its path, its colour, its width and its strength.

## The watermark

Dotted rows only, in the chart teal at a tenth of its strength. The lightning mark left the watermark in 1.0.3.28 because it read as a pale box on a desk. The bolt stays in the header.

## Why it stands still

Moving, the front page spent 6.9 s of main-thread work in every 20 s; still, 0.6 s. Measured in Chrome with its GPU over a minute of the front page ([ADR-081](https://theyard.stevenstout.biz/?doc=adr-glass-look), the addendum "the ribbons stand still"). The drawing costs one paint and then nothing. Nothing in it animates, and a test says so:

```ts
it('does not move: no animation, no transition, no keyframes, painted once', () => {
  for (const [selector, body] of rules(sheet)) {
    expect(body, selector).not.toMatch(/animation|transition|will-change/);
  }
  expect(sheet).not.toMatch(/@keyframes/);
  expect(component).not.toMatch(/<animate|requestAnimationFrame|setInterval/);
});
```
*Live from [`src/lib/ribbons.test.ts`](https://github.com/SteveStout/TheYard/blob/1d6b50b/src/lib/ribbons.test.ts#L36-L42), region `does-not-move`, read from this build at 1d6b50b.*

The ground is part of the page, not the window. It scrolls with the words in front of it, like a printed sheet, so the page never slides over a still picture. The ribbons sit in the first screen of every page and the gradient ground carries on below them. [`Ribbons.module.css`](https://github.com/SteveStout/TheYard/blob/main/src/components/layout/Background/Ribbons.module.css) holds the layer, and [`ribbons.test.ts`](https://github.com/SteveStout/TheYard/blob/main/src/lib/ribbons.test.ts) and the browser suite hold that it is pinned to the page:

```css
.layer {
  position: absolute;
  top: 0;
  right: 0;
  left: 0;
  /* The large viewport, not the screen as it is this moment. On a phone the address
     bar comes and goes as the reader scrolls; with inset: 0 the layer's height followed
     it (664 px to 750 px, measured at 390 px wide), and a drawing that fills
     its box by slicing was scaled by 13% every time: the ribbons danced with nothing
     animated. 100vh first for a browser without lvh, which on a phone is the same
     large height. */
  height: 100vh;
  height: 100lvh;
  z-index: var(--layer-ground);
  overflow: hidden;
  pointer-events: none;
  background: var(--ribbon-tints);
  /* Its own layer, painted once. */
  contain: strict;
  transform: translateZ(0);
}

/* The page column starts at the rail; the layer, at the window's edge. */
:global([data-rail='open']) .layer {
  left: calc(-1 * var(--rail-width));
}

:global([data-rail='collapsed']) .layer {
  left: calc(-1 * var(--rail-width-collapsed));
}
```
*Live from [`src/components/layout/Background/Ribbons.module.css`](https://github.com/SteveStout/TheYard/blob/1d6b50b/src/components/layout/Background/Ribbons.module.css#L10-L39), region `ground-layer`, read from this build at 1d6b50b.*

```ts
it('is part of the page, not the window: it scrolls away with the words (ADR-081, the ground is part of the page)', () => {
  const layer = rules(sheet).find(([selector]) => selector === '.layer')?.[1] ?? '';
  expect(layer).toMatch(/position:\s*absolute/);
  expect(layer).not.toMatch(/position:\s*fixed/);
  expect(sheet).not.toMatch(/position:\s*(fixed|sticky)/);
});
```
*Live from [`src/lib/ribbons.test.ts`](https://github.com/SteveStout/TheYard/blob/1d6b50b/src/lib/ribbons.test.ts#L58-L63), region `part-of-the-page`, read from this build at 1d6b50b.*

## Glass over the ribbons

The worst case for a word is the thinnest glass over the darkest thing behind it. Through a panel over the watermark at its worst, the one grey every secondary word uses reads 6.53, and [`StyleRulesTests`](https://github.com/SteveStout/TheYard/blob/main/api/TheYard.Tests/StyleRulesTests.cs) computes that figure from the design tokens and holds the faintest text to 4.5 on it. When a reader's system asks for reduced transparency the glass turns solid; under forced colours the site steps aside and the watermark is not drawn.

## The Chrome bug

Two copies of the drawing shared gradient names, and Chrome painted the page's copy blank. Each copy now names its own ids, and [`coverage.spec`](https://github.com/SteveStout/TheYard/blob/main/tests/e2e/coverage.spec.ts) holds every id to one use per page. The strip at the top of this page is a second copy, and it is the proof.

```ts
it("names its gradients and filters per copy, so the page's copy and a dialog's never share a name", () => {
  // Two copies once both said "ribbon-gold": a reference found the closed dialog's, and
  // Chrome painted the page's ribbons blank (1.0.3.20).
  expect(component).not.toMatch(/\sid="/);
  expect(sheet).not.toMatch(/url\(#/);
});
```
*Live from [`src/lib/ribbons.test.ts`](https://github.com/SteveStout/TheYard/blob/1d6b50b/src/lib/ribbons.test.ts#L75-L80), region `own-ids`, read from this build at 1d6b50b.*

## Files

- [`src/components/layout/Background/Ribbons.tsx`](https://github.com/SteveStout/TheYard/blob/main/src/components/layout/Background/Ribbons.tsx)
- [`src/lib/ribbons.ts`](https://github.com/SteveStout/TheYard/blob/main/src/lib/ribbons.ts)
- [`src/components/layout/Background/Ribbons.module.css`](https://github.com/SteveStout/TheYard/blob/main/src/components/layout/Background/Ribbons.module.css)
- [`src/components/layout/Background/Watermark.tsx`](https://github.com/SteveStout/TheYard/blob/main/src/components/layout/Background/Watermark.tsx)
- [`src/components/layout/Background/Watermark.module.css`](https://github.com/SteveStout/TheYard/blob/main/src/components/layout/Background/Watermark.module.css)
- [`src/lib/ribbons.test.ts`](https://github.com/SteveStout/TheYard/blob/main/src/lib/ribbons.test.ts)
- [`tests/e2e/glass.spec.ts`](https://github.com/SteveStout/TheYard/blob/main/tests/e2e/glass.spec.ts)
