/* ==========================================================================
   Patch — one screen

   The product has no interface, so neither does this page. What is on screen:
   a wordmark, two words that choose what Patch is doing, one sentence, one
   address, and a sheet of paper that obeys the sentence. Nothing else earns
   its place.

   Colour comes from tokens.css, unchanged — both blues are lifted from the
   logo and `--brand-hand` stays held for illustration, so it does not appear
   here. Type is set locally in `--site-serif` / `--site-sans` rather than by
   overwriting the token names, because the token file describes the product
   surfaces and this page deliberately reads differently.
   ========================================================================== */

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

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

body {
  margin: 0;
  min-height: 100dvh;
  background: var(--surface-page);
  color: var(--ink-body);
  font-family: var(--site-sans);
  -webkit-font-smoothing: antialiased;
}

/* ---- The world behind the glass ------------------------------------------ */
/* Two exposures of one landscape. They cross-fade rather than cut, so changing
   theme reads as the sun going down over the same place. If either file is
   missing the page simply keeps its own surface colour underneath. */

.scene {
  position: fixed;
  inset: 0;
  z-index: -1;
  background: var(--surface-page);
}

.scene__img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Holds the sun above the fold when a tall phone crops the frame. */
  object-position: 50% 42%;
  opacity: 0;
}

:root[data-theme="light"] .scene__img--light,
:root[data-theme="dark"] .scene__img--dark {
  opacity: 1;
}

@media (prefers-reduced-motion: no-preference) {
  .scene__img {
    transition: opacity var(--motion-beat) var(--ease-out);
  }
}

p,
h1,
h2,
dl,
dd,
blockquote,
figure {
  margin: 0;
}

img,
svg {
  display: block;
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ==========================================================================
   Theme.

   Both palettes live in one declaration per token via `light-dark()`, so there
   is no second block to keep in sync and no way for the two to drift. Older
   engines drop the declaration and keep the light value tokens.css already set,
   which is the correct fallback rather than a broken one.

   `color-scheme` is what actually decides: `light dark` follows the system,
   and the toggle pins it by setting `data-theme` on the root.
   ========================================================================== */

:root {
  color-scheme: light dark;

  --site-serif: "Mackinac", "Mackinac Fallback", Georgia, serif;
  --site-sans: "General Sans", "Helvetica Neue", Arial, sans-serif;

  /* The frame and the pane's vertical padding are pure outer air, and on a
     short viewport they were costing 155px before a word of content had been
     placed. Both now give ground with the height and both are back to their
     old values by the time the viewport is tall enough not to care. */
  --site-frame: clamp(18px, 3.4vh, 34px); /* the margin around everything */

  /* How far off square the page lies. JavaScript reads this back so the drag
     maths and the rendering can never disagree about the angle. */
  --site-tilt: -1.15deg;

  /* How far the page hangs off the right of the pane. One number, so the note
     above it can hold itself back by exactly the same amount. */
  --site-overhang: clamp(30px, 5vw, 78px);
  --site-pad: clamp(24px, 3.4vw, 54px);

  /* One design unit for the fixed-height view.
     Everything on that view used to be sized off width alone, while the pane it
     lives in is sized off height (100dvh minus the frame). Nothing connected the
     two, so on any viewport shorter than about 900px the column was simply too
     tall for its own pane and `overflow: clip` ate the bottom of it silently —
     which is why the page looked right in Safari and needed zooming out in
     Chrome and Brave. Those two give a shorter viewport at the same window size,
     and the layout had no way to notice.
     min() makes the unit follow whichever axis is scarcer. The 1.55 is the
     design ratio: at and above ~1000px of viewport height the width term wins
     and nothing changes from before, and below it the whole column comes down
     together instead of one property at a time. */
  --u: min(1vw, 1.38vh);
  --site-measure: 1260px; /* how wide the composition is allowed to get */

  /* Surfaces. The sheet is a surface like any other and changes with the rest
     of the page — a document you are reading at night should read at night. */
  --surface-page: light-dark(#ffffff, #08090f);
  --surface-canvas: light-dark(#f9f9fa, #12131c);
  --surface-raised: light-dark(#ffffff, #1b1d29);
  --surface-tile: light-dark(#f7f8fb, #23263a);
  --surface-chip: light-dark(#ededed, #23263a);

  --ink-display: light-dark(#1b1c21, #f4f4f8);
  --ink-strong: light-dark(#0e0d40, #e8ebfb);
  --ink-title: light-dark(#1a1f36, #dfe1ee);
  --ink-body: light-dark(#3d3d62, #b9bccd);
  --ink-lede: light-dark(#5c606f, #9ba1b7);
  --ink-status: light-dark(#646b8a, #8f95ad);
  --ink-nav: light-dark(#626060, #9ba1b7);

  --line-soft: light-dark(#f0f0f0, rgba(232, 235, 251, 0.1));
  --line-strong: light-dark(#d5d4e0, rgba(232, 235, 251, 0.22));

  /* The visible side of a raised object, in the neutral of what it sits on. */
  --site-edge: #e1e1e9;
  --site-edge: light-dark(#e1e1e9, #04050b);

  /* Brand, at whatever weight the current surface can carry. On white this is
     the logo's front plate; on the dark canvas that blue disappears, so the
     accent lifts. */
  --site-accent: var(--brand-600);
  --site-accent: light-dark(var(--brand-600), #8ea0ec);

  /* The highlighter is `--brand-hand`, the yellow from the logo, at full
     strength in both modes — a washed-out version of it in the dark reads as
     olive, not as Patch. The token file holds this colour back from UI and this
     is the one deliberate exception: it marks a change for a moment and then
     takes itself off the page, which is the only kind of mark this product is
     allowed to make. */

  /* The lens and the frost, in whatever is "paper white" right now. */
  --site-veil: light-dark(rgba(255, 255, 255, 0.5), rgba(30, 33, 48, 0.62));

  /* Dense enough that the page is a surface you read on, not a window. The
     scene survives as the glow behind the sentence and the colour at the
     corners, which is all it was ever meant to contribute. */
  --site-glass: light-dark(rgba(255, 255, 255, 0.84), rgba(9, 11, 20, 0.8));
  --site-glass-edge: light-dark(rgba(255, 255, 255, 0.75), rgba(255, 255, 255, 0.1));
}

:root[data-theme="light"] {
  color-scheme: light;
}

:root[data-theme="dark"] {
  color-scheme: dark;
}

/* The page is one pane of glass laid on the landscape. The frame around it is
   generous enough that you can see what it is resting on; through it, the scene
   survives as soft colour rather than detail. The panel is allowed to fill any
   display; the composition inside it is not, or the two columns end up shouting
   at each other from opposite edges of a 27-inch screen. */
.screen {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: clamp(20px, 3vh, 40px);
  /* Fixed, not minimum: the sheet is taller than this on purpose, and a pane
     that grew to fit it would push the address off the bottom of the screen. */
  height: calc(100dvh - var(--site-frame) * 2);
  margin: var(--site-frame);
  /* Square with --site-pad whenever there is room; tighter top and bottom when
     there is not. min() keeps it from ever exceeding the horizontal padding, so
     the pane never looks letterboxed. */
  padding: min(var(--site-pad), clamp(22px, 4.4vh, 54px)) var(--site-pad);
  border: 1px solid var(--site-glass-edge);
  border-radius: var(--radius-canvas);
  /* The lit top edge is a background layer, not an overlay. As a pseudo-element
     it painted above the content and washed out everything in the top fifth of
     the pane — the lockup most of all. */
  background-color: var(--site-glass);
  background-image: linear-gradient(
    180deg,
    light-dark(rgba(255, 255, 255, 0.45), rgba(255, 255, 255, 0.06)) 0%,
    transparent 20%
  );
  -webkit-backdrop-filter: blur(30px) saturate(150%);
  backdrop-filter: blur(30px) saturate(150%);
  box-shadow: 0 30px 90px rgba(4, 6, 20, 0.22);
  /* The sheet runs off the bottom of this and is cut by it. You never see the
     foot of the page: it rises out of the base of the pane and keeps going. */
  overflow: clip;
}

/* Where the backdrop cannot be filtered, the pane goes nearly solid instead of
   letting the landscape through at full detail behind the text. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .screen {
    background-color: light-dark(rgba(255, 255, 255, 0.93), rgba(10, 12, 22, 0.93));
  }
}

/* Asked for less transparency, given exactly that: the scene goes, the pane
   becomes the surface it was standing in for. */
@media (prefers-reduced-transparency: reduce) {
  .scene {
    display: none;
  }

  .screen {
    background-color: var(--surface-canvas);
    background-image: none;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    box-shadow: none;
  }
}

.top,
.body {
  width: 100%;
  max-width: var(--site-measure);
  margin-inline: auto;
}

/* ---- Top line ------------------------------------------------------------ */

.top {
  display: flex;
  align-items: center;
  gap: 16px;
}

/* Both lockups ship and CSS picks one, because the wordmark has to follow the
   toggle and `<picture>` can only follow the system. */
.mark {
  display: grid;
}

/* Supplied at 420×108, drawn at 92 wide — roughly 4.5× density, so it stays
   crisp on any screen worth looking at it on. */
.mark img {
  grid-area: 1 / 1;
  width: 92px;
  height: auto;
}

:root[data-theme="light"] .mark__dark,
:root[data-theme="dark"] .mark__light {
  visibility: hidden;
}

.top__end {
  display: flex;
  align-items: center;
  margin-left: auto;
  gap: 14px;
}

.beta {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.06em;
  color: var(--ink-status);
}

/* The toggle only exists once scripting has resolved a theme to pin, because
   without it there is nothing for the button to change. */
.theme {
  display: none;
  place-items: center;
  width: 34px;
  height: 34px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-pill);
  background: none;
  color: var(--ink-nav);
  cursor: pointer;
  transition:
    color var(--motion-chrome) var(--ease-out),
    border-color var(--motion-chrome) var(--ease-out);
}

.js-ready .theme {
  display: grid;
}

.theme:hover {
  color: var(--ink-strong);
  border-color: var(--ink-status);
}

.theme:focus-visible {
  outline: 2px solid var(--site-accent);
  outline-offset: 3px;
}

.theme__icon {
  grid-area: 1 / 1;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Each mode shows the mode you would be switching to. */
:root[data-theme="light"] .theme__icon--sun,
:root[data-theme="dark"] .theme__icon--moon {
  display: none;
}

/* ---- The two halves ------------------------------------------------------ */

/* Both columns start on the same line, because the two labels that begin them
   are one sentence read across the screen: you email this → it comes back like
   this. Losing that alignment loses the argument. */
.body {
  flex: 1;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 2.05fr);
  gap: clamp(26px, 3vw, 50px);
  align-items: stretch;
  min-height: 0;
  /* Pinned to the height of the pane. Left as `auto`, the row would grow to fit
     a page that is taller than the screen on purpose, and take the container
     with it. */
  grid-template-rows: 100%;
}

/* No measure of its own — the grid column is the measure. Capping this in `ch`
   made the sentence wrap far short of its track, which is what left everything
   huddled against the left edge on wide screens. */
.say {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}

/* ---- Two words, and the one that is live --------------------------------- */
/* A segmented switch with real depth: the track is recessed into the pane and
   the live word sits on a key raised out of it. Underlined text was a label
   pretending to be a control. */

.switch {
  display: inline-flex;
  gap: 3px;
  margin-top: clamp(8px, 1.7vh, 16px);
  padding: 4px;
  border-radius: 13px;
  background: light-dark(rgba(16, 16, 40, 0.055), rgba(0, 0, 0, 0.3));
  box-shadow:
    inset 0 1px 2px light-dark(rgba(16, 16, 40, 0.13), rgba(0, 0, 0, 0.55)),
    inset 0 -1px 0 light-dark(rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0.05));
}

.tab {
  position: relative;
  border: 0;
  border-radius: 10px;
  padding: 8px 12px;
  background: none;
  font-family: var(--site-sans);
  font-size: 12.5px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--ink-status);
  cursor: pointer;
  transition:
    color var(--motion-info) var(--ease-out),
    transform var(--motion-chrome) var(--ease-out),
    box-shadow var(--motion-chrome) var(--ease-out);
}

.tab:hover {
  color: var(--ink-title);
}

.tab[aria-selected="true"] {
  color: var(--ink-display);
  background: light-dark(#ffffff, #2b2f42);
  transform: translateY(-1px);
  box-shadow:
    0 var(--depth-control-sm) 0 light-dark(#dcdce6, #10121c),
    0 6px 12px light-dark(rgba(16, 16, 40, 0.14), rgba(0, 0, 0, 0.45)),
    inset 0 1px 0 light-dark(rgba(255, 255, 255, 0.9), rgba(255, 255, 255, 0.08));
}

.tab[aria-selected="true"]:active {
  transform: translateY(var(--depth-control-sm));
  box-shadow: 0 0 0 light-dark(#dcdce6, #10121c);
}

.tab:focus-visible {
  outline: 2px solid var(--site-accent);
  outline-offset: 3px;
}

/* ---- The header of an email nobody has to open --------------------------- */

.micro {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--ink-status);
}

.compose {
  align-self: stretch;
  margin-top: clamp(20px, 3vh, 34px);
}

.compose__row {
  display: flex;
  align-items: baseline;
  gap: 18px;
  padding: clamp(5px, 1vh, 10px) 0;
  border-bottom: 1px solid var(--line-soft);
  font-size: 13.5px;
  font-weight: 600;
}

.compose__row dt {
  flex: 0 0 68px;
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-status);
}

.intro .formats span + span::before {
  content: "";
  display: inline-block;
  width: 3px;
  height: 3px;
  margin: 0 12px 0.28em;
  border-radius: 50%;
  background: currentColor;
  opacity: 0.55;
}

.compose__row dd {
  color: var(--ink-title);
}

/* ---- Attachments --------------------------------------------------------- */
/* Sitting under the message the way they do in a mail client: a thumbnail of
   the file with a bar across its foot, and the dog-eared corner that has meant
   "document" since long before either of us. */

.attached {
  margin-top: clamp(18px, 2.6vh, 28px);
}

.attached__count {
  font-size: 12px;
  font-weight: 600;
  color: var(--ink-status);
}

.attached__list {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin: clamp(6px, 1.1vh, 10px) 0 0;
  padding: 0;
  list-style: none;
}

/* Attachments land after the sentence is finished, in the order you would have
   added them — you write the email, then you attach the files. */
.js-ready .attached .clip,
.js-ready .attached .attached__count {
  opacity: 0;
}

.attached.is-in .clip,
.attached.is-in .attached__count {
  opacity: 1;
}

@media (prefers-reduced-motion: no-preference) {
  .attached.is-in .attached__count {
    animation: clip-in var(--motion-move) var(--ease-out) both;
  }

  .attached.is-in .clip {
    animation: clip-in var(--motion-move) var(--ease-settle) both;
    animation-delay: calc(90ms + var(--clip-index, 0) * 90ms);
  }

  @keyframes clip-in {
    from {
      opacity: 0;
      transform: translateY(10px) scale(0.97);
    }
  }
}

.clip {
  position: relative;
  overflow: hidden;
  width: 148px;
  border: 1px solid var(--line-strong);
  border-radius: 4px;
  background: var(--surface-raised);
}

.clip__preview {
  display: flex;
  flex-direction: column;
  gap: 5px;
  height: clamp(40px, 7.6vh, 74px);
  padding: 12px 12px 0;
}

.clip__rule {
  height: 3px;
  border-radius: 1px;
  background: var(--line-soft);
}

.clip__rule--head {
  width: 52%;
  height: 5px;
  background: var(--line-strong);
}

.clip__rule--short {
  width: 64%;
}

.clip__bar {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 7px 8px;
  border-top: 1px solid var(--line-soft);
  background: var(--surface-tile);
}

.clip__kind {
  flex: none;
  padding: 2px 4px;
  border-radius: 2px;
  background: var(--danger);
  font-size: 8px;
  font-weight: 600;
  letter-spacing: 0.06em;
  color: #fff;
}

.clip__name {
  overflow: hidden;
  font-size: 11.5px;
  font-weight: 600;
  color: var(--ink-body);
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The dog-ear. The card's corner is genuinely cut away — you see through it to
   whatever is behind — and the flap lies over the gap. The previous version was
   a coloured triangle pasted on top, which read as a sticker rather than as a
   corner of paper that had turned. */
.clip {
  clip-path: polygon(0 0, 100% 0, 100% calc(100% - 18px), calc(100% - 18px) 100%, 0 100%);
}

.clip__fold {
  position: absolute;
  right: 0;
  bottom: 0;
  width: 18px;
  height: 18px;
  background: light-dark(#e8eaf1, rgba(255, 255, 255, 0.14));
  clip-path: polygon(0 0, 100% 0, 0 100%);
}

/* The send. Neutral rather than brand, because it belongs to the email being
   written rather than to the page: the blue key below is the one real action
   here, and two blue buttons stacked would argue about which. It presses itself
   when the demo runs, so it is obvious what caused what. */
.send-key {
  margin: -8px 0 -8px auto;
  border: 0;
  border-radius: var(--radius-button);
  padding: 9px 18px;
  font-family: var(--site-sans);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: -0.01em;
  background: light-dark(#ffffff, #2b2f42);
  color: var(--site-accent);
  cursor: pointer;
  box-shadow:
    0 var(--depth-control) 0 light-dark(#dcdce6, #10121c),
    0 8px 16px light-dark(rgba(16, 16, 40, 0.14), rgba(0, 0, 0, 0.45)),
    inset 0 1px 0 light-dark(rgba(255, 255, 255, 0.9), rgba(255, 255, 255, 0.08));
  transition:
    transform var(--motion-chrome) var(--ease-out),
    box-shadow var(--motion-chrome) var(--ease-out);
}

/* It is only there for the moment it is needed: it arrives once the email is
   finished, presses itself, and is gone before the answer comes back. */
.js-ready .send-key {
  opacity: 0;
  pointer-events: none;
}

.send-key.is-in {
  opacity: 1;
  pointer-events: auto;
}

.send-key.is-gone {
  opacity: 0;
  pointer-events: none;
}

@media (prefers-reduced-motion: no-preference) {
  .js-ready .send-key {
    transform: scale(0.94);
    transition:
      opacity var(--motion-info) var(--ease-out),
      transform var(--motion-info) var(--ease-settle),
      box-shadow var(--motion-chrome) var(--ease-out);
  }

  .send-key.is-in {
    transform: none;
  }

  .send-key.is-gone {
    transform: scale(0.94);
  }
}

.send-key:active,
.send-key.is-down {
  transform: translateY(var(--depth-control));
  box-shadow:
    0 0 0 light-dark(#dcdce6, #10121c),
    0 2px 5px light-dark(rgba(16, 16, 40, 0.12), rgba(0, 0, 0, 0.4));
}

.send-key:focus-visible {
  outline: 2px solid var(--site-accent);
  outline-offset: 3px;
}

/* ---- The sentence -------------------------------------------------------- */

/* No `text-wrap: balance` on purpose: it re-balances on every character while
   the line is being typed, which reads as the text twitching. */
.instruction {
  position: relative;
  margin-top: clamp(20px, 3.4vh, 36px);
  font-family: var(--site-serif);
  font-size: clamp(24px, calc(2.7 * var(--u)), 39px);
  font-weight: 400;
  line-height: 1.26;
  letter-spacing: -0.015em;
  color: var(--ink-display);
}

.caret {
  display: inline-block;
  width: 2px;
  height: 0.78em;
  margin-left: 0.04em;
  vertical-align: -0.04em;
  background: var(--site-accent);
}

@media (prefers-reduced-motion: no-preference) {
  .caret {
    animation: blink 1.06s steps(1, end) infinite;
  }

  @keyframes blink {
    0%,
    50% {
      opacity: 1;
    }
    50.01%,
    100% {
      opacity: 0;
    }
  }
}

/* Hung quotes: the sentence should start on the margin, not next to it. */
.instruction p::before {
  content: "“";
  margin-left: -0.42em;
}

.instruction p::after {
  content: "”";
}

/* Nothing is quoted until it has finished being said. Left on, the closing mark
   trails the caret — `"Completion moved to|”` — and reads as a typo for the
   whole of the sentence. */
.instruction.is-typing p::after {
  content: "";
}

/* ---- The address --------------------------------------------------------- */

.send {
  margin-top: auto;
  padding-top: clamp(14px, 3vh, 52px);
}

/* The only pressable object on the page, so it is the only one with a side —
   and the depth token is also how far it travels, which is the whole reason
   the system distinguishes things you press from things you read. */
.key {
  position: relative;
  display: grid;
  overflow: hidden;
  border: 0;
  border-radius: var(--radius-bar);
  padding: clamp(12px, 1.9vh, 18px) clamp(22px, 2vw, 30px);
  /* The key wears the mark: the logo's front plate for its face, its back
     plate for the side you press it onto. Both blues come straight off the
     icon, and it keeps them in either mode — this is the one control on the
     page, and it should look like it belongs to Patch and nothing else. */
  background: var(--brand-600);
  --site-edge: var(--brand-900);
  cursor: pointer;
  box-shadow:
    0 var(--depth-bar) 0 var(--site-edge),
    0 14px 26px rgba(16, 16, 40, 0.07);
  transition:
    transform var(--motion-chrome) var(--ease-out),
    box-shadow var(--motion-chrome) var(--ease-out);
}

.key:hover {
  box-shadow:
    0 var(--depth-bar) 0 var(--site-edge),
    0 20px 34px rgba(16, 16, 40, 0.1);
}

.key:active,
.key.is-down {
  transform: translateY(var(--depth-bar));
  box-shadow:
    0 0 0 var(--site-edge),
    0 3px 8px rgba(16, 16, 40, 0.08);
}

.key:focus-visible {
  outline: 2px solid var(--site-accent);
  outline-offset: 4px;
}

/* Both faces occupy the same cell, so the key never changes size when it
   swaps — it is a physical object, and physical objects keep their dimensions. */
.key__face {
  grid-area: 1 / 1;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  font-family: var(--site-sans);
  font-size: clamp(17px, calc(1.9 * var(--u)), 28px);
  font-weight: 600;
  letter-spacing: -0.025em;
  line-height: 1.2;
  white-space: nowrap;
  color: var(--ink-on-brand);
}

/* The copy affordance is a thing of its own inside the key: a hairline to
   divide it from the address, and a chip so it reads as something you press
   rather than punctuation after the text. */
.key__rule {
  flex: none;
  align-self: stretch;
  width: 1px;
  margin: -4px 0;
  background: rgba(255, 255, 255, 0.26);
}

.key__chip {
  display: grid;
  place-items: center;
  flex: none;
  width: 34px;
  height: 34px;
  margin: -6px -6px -6px 0;
  border-radius: var(--radius-button);
  background: rgba(255, 255, 255, 0.15);
  color: rgba(255, 255, 255, 0.82);
  transition:
    background var(--motion-chrome) var(--ease-out),
    color var(--motion-chrome) var(--ease-out);
}

.key:hover .key__chip {
  color: #ffffff;
}

.key__glyph,
.key__tick {
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.key__face--done {
  color: var(--ink-on-brand);
  opacity: 0;
  transform: translateY(0.5em);
}

.key__tick {
  stroke-width: 2;
  stroke-dasharray: 100;
  stroke-dashoffset: 100;
}

.key.is-copied .key__face--idle {
  opacity: 0;
  transform: translateY(-0.5em);
}

.key.is-copied .key__face--done {
  opacity: 1;
  transform: none;
}

.key.is-copied .key__tick {
  stroke-dashoffset: 0;
}

/* One pass of light across the face, as the confirmation rather than a
   decoration — it fires on copy and never otherwise. */
.key::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(100deg, transparent 34%, var(--brand-050) 50%, transparent 66%);
  opacity: 0;
  pointer-events: none;
}

.fine__link {
  display: inline-block;
  color: var(--ink-nav);
  text-underline-offset: 3px;
}

.fine__link:hover {
  color: var(--ink-strong);
}

.fine {
  max-width: 42ch;
  margin-top: clamp(10px, 1.8vh, 18px);
  font-size: 13.5px;
  font-weight: 600;
  line-height: 1.55;
  color: var(--ink-status);
}

/* ---- The sheet ----------------------------------------------------------- */

.stack {
  display: flex;
  align-items: stretch;
  /* Held to its own side. Centred, an oversized page runs left across the
     sentence; from here anything it overruns goes out through the edge. */
  justify-content: flex-end;
  align-items: flex-start;
  /* Slid toward the edge of the pane, and past it. The page keeps its size; it
     just sits further over, and what runs off the edge is cut the same way the
     foot of it is. */
  margin-right: calc(-1 * var(--site-overhang));
  gap: clamp(8px, 0.9vw, 14px);
  min-height: 0;
  height: 100%;
}

/* Shrinks to the sheet's own width, so the label above it and the line below
   it hang off the same edge the paper does — and the whole assembly lies at a
   slight angle, captions and all. Tilting the sheet alone would leave the
   labels pointing at a seam that had moved out from under them. */
.stack__inner {
  transform: rotate(var(--site-tilt));
  transform-origin: center;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 16px;
  min-height: 0;

  /* Where the pane sits. It lives here because the labels above the sheet have
     to follow the seam too, and they are not inside it. */
  --split: 50%;

  /* Hung from the top, sized by width, A4 from there down. At this width the
     page is simply taller than the room it has, so it runs out through the base
     of the pane on its own — no negative margins, and nothing that could push
     the container around. */
  flex: 0 1 clamp(300px, 48vw, 672px);
}

/* A window onto the top 85% of the page rather than the whole of it. Two things
   come from that: the sheet can be 18% wider for the same height, which is what
   makes it legible; and a page with its foot cut off reads as a real document
   that carries on below rather than a thumbnail of one.

   1.202 is 1.414 × 0.85 — A4, less the part that is not shown. */
.paper {
  position: relative;
  width: 100%;
  aspect-ratio: 1 / 1.414;
  container-type: inline-size;
  border-radius: 3px;
  background: var(--surface-raised);
  /* Three layers: the contact edge where the sheet meets the glass, the close
     shadow that gives it thickness, and the long one that lifts it off. */
  box-shadow:
    0 2px 3px light-dark(rgba(16, 16, 40, 0.1), rgba(0, 0, 0, 0.45)),
    0 22px 40px light-dark(rgba(16, 16, 40, 0.1), rgba(0, 0, 0, 0.42)),
    0 64px 120px light-dark(rgba(16, 16, 40, 0.18), rgba(0, 0, 0, 0.6));
  overflow: hidden;
}

/* The turned corner. The gap where the paper has lifted is darker than the
   sheet and shades away from the fold; the flap itself catches the light along
   its edge. The earlier version was one flat triangle, which read as a shape
   sitting on the page rather than a corner of it that had moved. */
.paper::before {
  content: "";
  position: absolute;
  top: 0;
  right: 0;
  z-index: 3;
  width: 30px;
  height: 30px;
  background:
    linear-gradient(
      225deg,
      light-dark(#f2f3f8, #23263a) 0 46%,
      light-dark(rgba(255, 255, 255, 0.95), rgba(255, 255, 255, 0.2)) 46% 48%,
      transparent 48% 100%
    ),
    linear-gradient(
      225deg,
      light-dark(rgba(16, 16, 40, 0.16), rgba(0, 0, 0, 0.5)) 0%,
      transparent 60%
    );
  filter: drop-shadow(-1px 1px 1px light-dark(rgba(16, 16, 40, 0.16), rgba(0, 0, 0, 0.5)));
}

/* Type inside the sheet is sized against the sheet, not the viewport, so the
   page keeps its proportions at every screen size. */
/* Each document is issued by a different business, so each one gets its own
   identity: a colour, a mark and a layout that suits what it is. Invented
   brands, designed properly — which is also the only honest way to do it, since
   these are fabricated invoices and contracts and cannot carry a real
   company's mark. */
/* Four tokens, because a brand colour behaves differently depending on whether
   it is being read or being filled. On paper, ink is dark and a solid band is
   the same colour as the ink. On a dark sheet that inverts: type has to come up
   to meet the surface, but a solid area has to go *down* — printing a whole
   letterhead in the light tint puts a lamp in the middle of the page. So
   --doc-accent rises in the dark and --doc-solid sinks. */
.sheet--caboo {
  --doc-accent: light-dark(#2f3f9e, #8496e0);
  --doc-solid: light-dark(#2f3f9e, #242e69);
  --doc-on-solid: light-dark(#ffffff, #ccd4f7);
  --doc-band: light-dark(#eef0fb, rgba(142, 160, 236, 0.12));
}

.sheet--rowan {
  --doc-accent: light-dark(#2f6b4f, #6fbb98);
  --doc-solid: light-dark(#2f6b4f, #1d4234);
  --doc-on-solid: light-dark(#ffffff, #b4e2cb);
  --doc-band: light-dark(#eaf3ee, rgba(121, 196, 162, 0.11));
}

/* The engineering firm. A colder blue than Caboo's, because these two sit next
   to a blueprint and a plotted line drawing and should read as drawing-office
   paper rather than as company stationery. */
.sheet--halden {
  --doc-accent: light-dark(#1f4d63, #74b3ce);
  --doc-solid: light-dark(#1f4d63, #123646);
  --doc-on-solid: light-dark(#ffffff, #b4dbeb);
  --doc-band: light-dark(#eaf2f6, rgba(116, 179, 206, 0.11));
}

.sheet--lettings {
  --doc-accent: light-dark(#a4472e, #dd8770);
  --doc-solid: light-dark(#a4472e, #6b3020);
  --doc-on-solid: light-dark(#ffffff, #f5c4b3);
  --doc-band: light-dark(#fbeee9, rgba(229, 144, 121, 0.12));
}

.sheet {
  display: flex;
  flex-direction: column;
  height: 100%;
  padding: 8%;
  background: var(--surface-raised);
  font-family: var(--site-serif);
  font-size: clamp(8px, 2.45cqw, 18px);
  line-height: 1.6;
  color: var(--ink-title);
}

/* The masthead: a full-bleed band of the issuer's colour with its mark and name
   reversed out of it. This is the thing that stops a document reading as a word
   processor window — before a word of content, something has claimed the page. */
.masthead-doc {
  display: flex;
  align-items: center;
  gap: 0.8em;
  margin: -8% -8% 1.7em;
  padding: 0.95em 8%;
  background: var(--doc-solid, var(--doc-accent));
  color: var(--doc-on-solid, #fff);
  font-family: var(--site-sans);
}

/* The mark is a mask rather than an image, so it takes the letterhead's own ink
   in both modes. A coloured logo on a coloured band is invisible half the time,
   and these three are one flat shape each, which is exactly what a mask wants. */
.masthead-doc__mark {
  flex: none;
  width: 1.55em;
  height: 1.55em;
  background: currentColor;
  -webkit-mask: var(--doc-mark) center / contain no-repeat;
  mask: var(--doc-mark) center / contain no-repeat;
}

.masthead-doc--lettings {
  --doc-mark: url("/brand/doc/mark-lettings.svg");
}

.masthead-doc--rowanfell {
  --doc-mark: url("/brand/doc/mark-rowanfell.svg");
}

.masthead-doc--restaurant {
  --doc-mark: url("/brand/doc/mark-restaurant.svg");
}

.masthead-doc--halden {
  --doc-mark: url("/brand/doc/mark-halden.svg");
}

.masthead-doc__co {
  font-size: 1.02em;
  font-weight: 600;
  letter-spacing: 0.01em;
}

.masthead-doc__ref {
  margin-left: auto;
  font-size: 0.8em;
  font-weight: 600;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  opacity: 0.78;
}

.sheet__kicker {
  color: var(--doc-accent);
}

.sheet__band-amount {
  font-family: var(--site-sans);
  font-size: 1.35em;
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--doc-accent);
}

.sheet__kicker {
  font-family: var(--site-sans);
  font-size: 1.05em;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-strong);
}

.sheet__sub {
  margin-top: 0.3em;
  padding-bottom: 1.5em;
  border-bottom: 1px solid var(--line-soft);
  font-size: 0.95em;
  color: var(--ink-status);
}

.sheet__h {
  margin-top: 1.9em;
  font-family: var(--site-serif);
  font-size: 1em;
  font-weight: 400;
  letter-spacing: 0;
  color: var(--ink-strong);
}

.sheet__p {
  margin-top: 0.45em;
  text-align: justify;
  hyphens: auto;
}

.sheet__rows {
  margin-top: 0.7em;
}

.sheet__row {
  display: flex;
  justify-content: space-between;
  gap: 1.5em;
  padding: 0.5em 0;
  border-bottom: 1px solid var(--line-soft);
}

.sheet__row dt {
  color: var(--ink-status);
}

/* Tables inside the sheet: an invoice's lines, a price list's grid. Both keep
   the document's own type and hairlines rather than looking like UI. */
.sheet__table {
  width: 100%;
  margin-top: 0.8em;
  border-collapse: collapse;
  font-variant-numeric: tabular-nums;
}

.sheet__table th,
.sheet__table td {
  padding: 0.42em 0;
  border-bottom: 1px solid var(--line-soft);
  text-align: left;
  font-weight: inherit;
}

.sheet__table thead th {
  padding: 0.4em 0.5em;
  border-bottom: 1px solid var(--line-strong);
  background: var(--doc-solid, var(--surface-tile));
  border-bottom: 0;
  font-family: var(--site-sans);
  font-size: 0.82em;
  font-weight: 600;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--doc-on-solid, var(--ink-strong));
}

.sheet__table--grid td {
  padding-inline: 0.5em;
}

.sheet__table--grid tbody tr:nth-child(even) td {
  background: light-dark(#fafbfd, rgba(255, 255, 255, 0.022));
}

.sheet__table td:last-child,
.sheet__table th:last-child,
.sheet__table--grid td:nth-child(4),
.sheet__table--grid th:nth-child(4) {
  text-align: right;
}

.sheet__table--grid td:nth-child(3),
.sheet__table--grid th:nth-child(3) {
  text-align: right;
}

.sheet__table:not(.sheet__table--grid) td:nth-child(2) {
  text-align: right;
  color: var(--ink-status);
}

.sheet__rows--panel {
  margin-top: 1.1em;
  border-left: 2px solid var(--doc-accent);
  padding: 0.2em 0.9em 0.1em;
  border-radius: 2px;
  background: var(--surface-tile);
}

.sheet__rows--panel .sheet__row:last-child {
  border-bottom: 0;
}

.sheet__rows--totals {
  margin-top: 1.4em;
  margin-left: auto;
  width: 58%;
}

.sheet__row--strong {
  border-bottom: 0;
  border-top: 2px solid var(--doc-accent);
}

.sheet__row--strong dt,
.sheet__row--strong dd {
  padding-top: 0.15em;
  font-family: var(--site-sans);
  font-weight: 600;
  color: var(--doc-accent);
}

.sheet__fields {
  margin-top: 0.8em;
}

/* A form is boxes, not underlines. This is the difference between something
   printed to be filled in and something typed in a word processor. */
.sheet__field {
  display: grid;
  grid-template-columns: 8.5em 1fr;
  align-items: stretch;
  margin-bottom: 0.45em;
  border: 1px solid var(--line-strong);
  border-radius: 2px;
  overflow: hidden;
}

.sheet__field dt {
  padding: 0.5em 0.7em;
  background: var(--surface-tile);
  border-right: 1px solid var(--line-strong);
  font-size: 0.92em;
  color: var(--ink-status);
}

.sheet__field dd {
  min-height: 1.3em;
  padding: 0.5em 0.7em;
}

.sheet__h {
  margin-top: 1.9em;
  font-family: var(--site-serif);
  font-size: 1em;
  font-weight: 400;
  letter-spacing: 0;
  color: var(--ink-strong);
}

/* Forms label their sections; contracts and invoices simply number them. */
.sheet--form .sheet__h {
  display: inline-block;
  margin-top: 1.7em;
  padding: 0.3em 0.7em;
  border-radius: 2px;
  background: var(--doc-band);
  color: var(--doc-accent);
  font-family: var(--site-sans);
  font-size: 0.88em;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.sheet__sign {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 2em;
  margin-top: 2.4em;
}

.sheet__signline span {
  display: block;
  height: 2.4em;
  border-bottom: 1px solid var(--line-strong);
}

.sheet__signline p {
  margin-top: 0.5em;
  font-size: 0.92em;
  color: var(--ink-status);
}

.sheet__foot {
  display: flex;
  justify-content: space-between;
  gap: 1.5em;
  margin-top: auto;
  padding-top: 0.7em;
  border-top: 1px solid var(--line-strong);
  font-family: var(--site-sans);
  font-size: 0.82em;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-status);
}

.micro--paper {
  margin-left: 2px;
}

/* ---- The bits that change ------------------------------------------------ */
/* The highlighter: a block of the logo's yellow lays itself over each changed
   value, holds long enough to read, then wipes off the page. The mark is
   temporary on purpose — it points at the change without ever becoming part of
   the document. Only the finished side flags; the original has nothing to
   point at. */

/* The box is identical on both layers and only the paint differs. This matters
   more than it looks: the flag's padding and negative margins used to sit on
   the finished side alone, which made that layer take up fractionally less
   width per value, wrap a line earlier, and tear the sentence apart across the
   seam. Anything that changes geometry belongs here; anything that changes
   colour belongs below. */
.slot {
  position: relative;
  display: inline-block;
  white-space: nowrap;
  padding: 0.1em 0.16em;
  margin: -0.1em -0.16em;
  border-radius: 1px;
}

/* The flag is a second copy of the value, drawn over the first in the logo's
   yellow with dark ink on it, and wiped away afterwards. Two copies rather than
   a background, because a background forces a choice: dark ink that disappears
   the moment the block starts leaving, or light ink that cannot be read on
   yellow. With the plain value sitting underneath at full strength, whatever
   the wipe uncovers is already legible. */
.side--after .slot::after {
  content: attr(data-to);
  position: absolute;
  inset: 0;
  padding: inherit;
  border-radius: inherit;
  background: var(--brand-hand);
  color: #17181f;
  clip-path: inset(0 100% 0 0);
}

@media (prefers-reduced-motion: no-preference) {
  .side--after .slot::after {
    animation: flag 5.6s var(--ease-move) infinite;
    animation-delay: var(--flag-delay, 0s);
  }

  @keyframes flag {
    0%,
    3% {
      clip-path: inset(0 100% 0 0);
    }
    13%,
    40% {
      clip-path: inset(0 0 0 0);
    }
    52%,
    100% {
      clip-path: inset(0 0 0 100%);
    }
  }
}

/* Nothing animates, so the mark has to stay put to do its job. */
@media (prefers-reduced-motion: reduce) {
  .side--after .slot::after {
    clip-path: inset(0 0 0 0);
  }
}

/* ---- Before and after, on the same sheet --------------------------------- */

/* The tracks are declared on the stack, not here: a local `--split` on this
   element shadowed the one being set above it, so the labels followed the lens
   while the sheet stayed clipped at half. */
.compare {
  position: relative;
  height: 100%;
}

.side {
  position: absolute;
  inset: 0;
}

/* The finished document is clipped to the right of the lens; the original sits
   underneath it, uncovered as the lens travels left. */
.side--after {
  clip-path: inset(0 0 0 var(--split));
}

/* The reveal and the comparison want opposite things from glass. During the
   sweep the frost covers everything the lens has not reached yet, so the
   finished document appears out of it — that is what makes the reveal read.
   The moment it settles the frost lifts, leaving the thin lens below, because
   you cannot compare two documents through frosted glass. */
/* The frost belongs to the movement, not to the resting state. It comes on the
   moment the pane starts travelling and goes as soon as it stops, so what you
   are left looking at is always one whole document with nothing over it. The
   filter is scoped to the same class rather than left on the element, because
   Chrome keeps a backdrop-filter working at `opacity: 0`. */
.frost {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: calc(100% - var(--split));
  opacity: 0;
  pointer-events: none;
  background: var(--site-veil);
}

.compare.is-sliding .frost {
  opacity: 1;
  -webkit-backdrop-filter: blur(7px) saturate(108%);
  backdrop-filter: blur(7px) saturate(108%);
}

@media (prefers-reduced-motion: no-preference) {
  .frost {
    transition: opacity var(--motion-info) var(--ease-out);
  }

  /* The page arrives the way a sheet actually lands: up out of the base of the
     pane, over-rotated, then settling onto its own angle as it comes to rest.
     One beat, slow enough to watch, and no tricks it would not do on a desk. */
  /* Not on the page until the email that causes it has been sent. */
  .js-ready .stack__inner:not(.is-here) {
    opacity: 0;
  }

  .stack__inner[data-arriving] {
    animation: page-arrive 760ms var(--ease-settle) both;
  }

  /* Half the rotation it had. At −5.2° the sheet read as falling over rather
     than being set down, and the middle of the animation looked like a fault. */
  @keyframes page-arrive {
    from {
      transform: translateY(58px) rotate(-2.8deg);
      opacity: 0;
    }
    40% {
      opacity: 1;
    }
  }
}

/* The resting lens does not blur anything. It used to, and it read as a column
   of the contract having been wiped out — which is the exact opposite of what
   this product promises. All it is now is the seam, a grip, and enough of a
   gradient to feel like an edge. Blur belongs to the frost, and the frost only
   exists while the reveal is running. */
.glass {
  position: absolute;
  top: 0;
  bottom: 0;
  /* Clamped so that parked at either end the grip is still on the page and
     still has something to take hold of. */
  left: clamp(18px, var(--split), calc(100% - 18px));
  width: 32px;
  margin-left: -16px;
  cursor: ew-resize;
  touch-action: none;
}

/* Two registration ticks in the sheet's own margins, and nothing across the
   text. A full-height rule marked where the join was, but it also drew a line
   down the middle of the customer's contract — which is precisely the thing
   this product promises never to do. The join has to be findable without being
   inflicted on the document, so it shows only where the page is already blank.

   There is a second, better reason to take it out: with no rule there, the two
   halves are visibly one continuous page except where a value differs. Not
   being able to see the join is the argument. */
.glass__seam {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 50%;
  width: 1px;
  margin-left: -0.5px;
  background: var(--site-accent);
  opacity: 0.55;
  -webkit-mask-image: linear-gradient(
    #000 0 20px,
    transparent 20px,
    transparent calc(100% - 20px),
    #000 calc(100% - 20px)
  );
  mask-image: linear-gradient(
    #000 0 20px,
    transparent 20px,
    transparent calc(100% - 20px),
    #000 calc(100% - 20px)
  );
}

.glass__grip {
  position: absolute;
  top: 50%;
  left: 50%;
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  margin: -17px 0 0 -17px;
  border: 0;
  border-radius: var(--radius-pill);
  background: var(--surface-raised);
  color: var(--brand-900);
  cursor: ew-resize;
  box-shadow:
    0 1px 2px rgba(16, 16, 40, 0.12),
    0 6px 16px rgba(16, 16, 40, 0.14);
}

.glass__grip svg {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.glass__grip:focus-visible {
  outline: 2px solid var(--site-accent);
  outline-offset: 3px;
}

.compare.is-dragging {
  user-select: none;
}

/* The two ends, named outside the paper where nothing can be mistaken for part
   of the document — and pinned to the seam rather than to the corners. Parked
   at the far edges they were decoration: you could not tell which half of the
   sheet either one was talking about. Held either side of the lens and moving
   with it, each label is always touching the thing it names. */
.ends {
  position: relative;
  align-self: stretch;
  height: 15px;
}

.ends span {
  position: absolute;
  top: 0;
  white-space: nowrap;
  transition:
    opacity var(--motion-info) var(--ease-out),
    color var(--motion-info) var(--ease-out);
}

[data-end-before] {
  right: calc(100% - var(--split, 50%));
  padding-right: 13px;
}

[data-end-after] {
  left: var(--split, 50%);
  padding-left: 13px;
  color: var(--ink-title);
}

/* Pushed to an edge there is no room, and no side left to describe. */
.stack__inner[data-edge="start"] [data-end-before],
.stack__inner[data-edge="end"] [data-end-after] {
  opacity: 0;
}

/* ---- The note beside the page -------------------------------------------- */
/* Three short lines standing next to the sheet rather than one long one running
   underneath it. Under the page there is nowhere to put it now — the page goes
   off the bottom of the pane and does not stop. */

.stamp {
  flex: 0 0 15ch;
  align-self: flex-start;
  margin-top: 34px;
  /* Held back by exactly what the page hangs off the edge by, so moving the
     page right does not take its note off the pane with it. */
  margin-right: var(--site-overhang);
  font-family: var(--site-sans);
  font-size: 13px;
  font-weight: 600;
  line-height: 1.5;
  color: var(--ink-status);
  opacity: 0;
}

.stamp.is-on {
  opacity: 1;
}

.stamp svg {
  margin-bottom: 9px;
  overflow: visible;
}

.stamp__tick {
  fill: none;
  stroke: var(--site-accent);
  stroke-width: 1.9;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-dasharray: 100;
  stroke-dashoffset: 100;
}

.stamp.is-on .stamp__tick {
  stroke-dashoffset: 0;
}

/* ==========================================================================
   Motion. Everything that moves here carries information: the marker passing
   over a value, the value changing under it, the tick at the end. Travel lives
   inside `no-preference`; the finished state is identical either way.
   ========================================================================== */

@media (prefers-reduced-motion: no-preference) {
  .frost {
    transition: opacity var(--motion-beat) var(--ease-out);
  }

  .stamp {
    transition: opacity var(--motion-info) var(--ease-out);
  }

  .stamp.is-on .stamp__tick {
    transition: stroke-dashoffset var(--motion-move) var(--ease-out) 80ms;
  }

  .key__face {
    transition:
      opacity var(--motion-info) var(--ease-out),
      transform var(--motion-info) var(--ease-out);
  }

  .key__tick {
    transition: stroke-dashoffset var(--motion-move) var(--ease-out) 60ms;
  }

  /* Released, not merely un-pressed: it comes back up past its resting height
     and settles, which is what makes a key feel like a key. */
  .key.is-up {
    animation: key-up var(--motion-move) var(--ease-settle);
  }

  @keyframes key-up {
    from {
      transform: translateY(var(--depth-bar));
    }
  }

  /* One pass of light across the face as the confirmation. It fires on copy
     and never otherwise. */
  .key.is-copied::after {
    animation: key-sweep 620ms var(--ease-move);
  }

  @keyframes key-sweep {
    from {
      opacity: 0.9;
      transform: translateX(-70%);
    }
    to {
      opacity: 0;
      transform: translateX(70%);
    }
  }
}

/* ==========================================================================
   Smaller screens. The two halves stack, the sheet keeps its proportions, and
   the page is allowed to scroll rather than crush itself into one view.
   ========================================================================== */

/* Large displays: the panel keeps filling the screen, the composition grows a
   little and then stops. */
@media (min-width: 1600px) {
  :root {
    --site-measure: 1380px;
    --site-frame: 44px;
  }
}

/* The awkward middle — wide enough for two columns, not wide enough for them to
   be generous with each other. */
@media (max-width: 1200px) {
  :root {
    --site-pad: 36px;
  }

  .body {
    grid-template-columns: minmax(0, 1fr) minmax(0, 0.94fr);
    gap: 40px;
  }

  .instruction {
    font-size: clamp(26px, 3.1vw, 36px);
  }
}

@media (max-width: 900px), (max-height: 680px) {
  :root {
    --site-frame: 10px;
  }

  .screen {
    height: auto;
    min-height: calc(100dvh - 20px);
  }

  .body {
    grid-template-columns: 1fr;
    gap: 34px;
    align-items: start;
  }

  .say {
    max-width: none;
  }

  .stack {
    height: auto;
    justify-content: flex-start;
  }

  .stack__inner {
    width: 100%;
  }

  .paper {
    height: auto;
    width: 100%;
    max-width: 440px;
  }

  .sheet {
    font-size: clamp(9.5px, 2.9cqw, 11.5px);
  }
}

@media (max-width: 520px) {
  .switch {
    gap: 18px;
  }

  .tab {
    font-size: 13px;
  }

  .instruction {
    font-size: clamp(23px, 6.4vw, 30px);
  }

  .address {
    width: 100%;
    text-align: center;
  }

  .stamp {
    font-size: 11.5px;
  }
}

/* ==========================================================================
   Document furniture

   Appended rather than threaded into the sheet section above, because an
   earlier edit removed part of that block by accident and silently — these are
   the pieces that make each document look like the kind of document it is.
   ========================================================================== */

/* The title band an invoice or a form announces itself with. */
.sheet__band {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1.2em;
  margin: 0 -0.6em 1.3em;
  padding: 0.7em 0.9em;
  border-radius: 2px;
  background: var(--doc-band);
}

.sheet__band .sheet__kicker {
  color: var(--doc-accent);
}

/* The image well. Flat vector rather than a photograph: it has to survive being
   90px tall on a miniature page, and it is drawn here rather than fetched, so
   there is no asset to license, host or keep provenance for. */
/* The plate used to be a coded SVG of a street: honest, but an estate agent's
   brochure with a drawing where the photograph goes reads as a mock-up, which
   is the one thing these sample documents must not do. */
.plate {
  display: block;
  width: calc(100% + 16%);
  margin: 0 -8% 1.5em;
  height: 24%;
  object-fit: cover;
  /* The photograph is the document's, not the page's, so it does not follow the
     theme — the sheet stays paper in both modes and so does everything on it. */
  background: var(--doc-band);
}

.plate--band {
  height: 15%;
  margin-bottom: 1.3em;
}

/* Key facts as three cells, the way any particulars sheet sets them. */
.facts {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1px;
  margin: 1.3em 0 1.6em;
  border: 1px solid var(--line-strong);
  border-radius: 2px;
  background: var(--line-strong);
  overflow: hidden;
}

.facts__item {
  padding: 0.6em 0.8em;
  background: var(--surface-raised);
}

.facts__item dt {
  font-family: var(--site-sans);
  font-size: 0.78em;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-status);
}

.facts__item dd {
  margin-top: 0.15em;
  font-family: var(--site-sans);
  font-size: 1.15em;
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--doc-accent);
}

/* ---- A CSV, looking like a CSV ------------------------------------------- */
/* No letterhead, because a comma-separated file has never had one. What you
   actually see when you open one is a grid: lettered columns, numbered rows,
   ruled cells and figures that line up. */

.sheet--csv {
  font-size: clamp(7px, 2.05cqw, 12px);
}

.filebar {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1.2em;
  margin: -8% -8% 1.5em;
  padding: 0.9em 8%;
  background: var(--surface-tile);
  border-bottom: 1px solid var(--line-strong);
  font-family: var(--site-sans);
}

.filebar__name {
  font-size: 1.02em;
  font-weight: 600;
  color: var(--ink-strong);
}

.filebar__meta {
  font-size: 0.82em;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-status);
}

.csv {
  width: 100%;
  border-collapse: collapse;
  font-variant-numeric: tabular-nums;
}

.csv th,
.csv td {
  padding: 0.42em 0.5em;
  border: 1px solid var(--line-soft);
  text-align: left;
  font-weight: inherit;
  white-space: nowrap;
}

/* The lettered strip along the top and the numbered one down the side: the
   frame a spreadsheet puts around a file it did not design. */
.csv thead th,
.csv tbody th {
  background: var(--surface-tile);
  border-color: var(--line-strong);
  font-family: var(--site-sans);
  font-size: 0.82em;
  font-weight: 600;
  text-align: center;
  color: var(--ink-status);
}

.csv tbody th {
  width: 2.2em;
}

.csv__corner {
  width: 2.2em;
}

.csv__head-row td {
  font-weight: 600;
  color: var(--ink-strong);
}

.csv td.csv__num {
  text-align: right;
}

/* ---- Stepping through the samples ---------------------------------------- */
/* Out in the margin, off the text's own alignment: it steers the demo rather
   than being part of the email, and it should not read as one of its fields. */

.stepper {
  position: absolute;
  top: 0.28em;
  left: calc(-1 * clamp(30px, 3.4vw, 46px));
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.stepper button {
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  border: 0;
  border-radius: 7px;
  background: none;
  color: var(--ink-status);
  cursor: pointer;
  transition:
    color var(--motion-chrome) var(--ease-out),
    background var(--motion-chrome) var(--ease-out);
}

.stepper button:hover {
  color: var(--ink-display);
  background: light-dark(rgba(16, 16, 40, 0.06), rgba(255, 255, 255, 0.08));
}

.stepper button:focus-visible {
  outline: 2px solid var(--site-accent);
  outline-offset: 2px;
}

.stepper svg {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* ==========================================================================
   Inner pages

   The house style, stated once so three more pages can be built to it:

     · One pane of glass on the landscape. Everything lives inside it, and the
       page never breaks out of it.
     · Mackinac for anything that is a statement or a document. General Sans for
       anything that is interface — labels, addresses, small print. Nothing else,
       no italics, no third weight.
     · Ink on glass, near-monochrome. The logo's two blues for anything you
       press; the logo's yellow only ever marks a change and then leaves.
     · Things you press have a visible side and travel when pressed. Things you
       read are flat. Never both.
     · Hairlines separate, uppercase micro-labels announce, and the measure stays
       around 62 characters so prose is prose.
   ========================================================================== */

.screen--page {
  --page-max: 1160px;

  height: auto;
  min-height: calc(100dvh - var(--site-frame) * 2);
  overflow: visible;
}

/* On the full-width pages the lockup and the nav sit on the same column as the
   content below them. Left on the pane's own gutter they read as furniture
   belonging to the window rather than to the page. */
body:has(.page--full) .top {
  width: 100%;
  max-width: var(--page-max);
  margin-inline: auto;
}

.nav {
  display: flex;
  gap: clamp(14px, 1.6vw, 24px);
  /* Tight to the lockup. Floated out into the middle they read as a second,
     unrelated group rather than as this site's own pages. */
  margin-left: clamp(14px, 1.6vw, 22px);
}

.nav a {
  font-family: var(--site-sans);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--ink-status);
  text-decoration: none;
  transition: color var(--motion-chrome) var(--ease-out);
}

.nav a:hover {
  color: var(--ink-display);
}

.nav a[aria-current="page"] {
  color: var(--ink-display);
}

.nav a:focus-visible {
  outline: 2px solid var(--site-accent);
  outline-offset: 4px;
  border-radius: 3px;
}

.page {
  width: 100%;
  max-width: 62ch;
  margin: clamp(36px, 7vh, 76px) auto 0;
  padding-bottom: clamp(48px, 9vh, 96px);
}

.page__title {
  margin-top: 16px;
  font-family: var(--site-serif);
  font-size: clamp(30px, 4.2vw, 46px);
  font-weight: 400;
  line-height: 1.12;
  letter-spacing: -0.02em;
  color: var(--ink-display);
}

.page__lede {
  margin-top: 22px;
  font-family: var(--site-serif);
  font-size: clamp(18px, 1.6vw, 22px);
  line-height: 1.5;
  color: var(--ink-lede);
}

.page--prose h2 {
  margin-top: clamp(38px, 6vh, 62px);
  padding-top: 22px;
  border-top: 1px solid var(--line-soft);
  font-family: var(--site-sans);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--ink-status);
}

.page--prose p,
.page--prose li {
  margin-top: 16px;
  font-family: var(--site-serif);
  font-size: 17px;
  line-height: 1.62;
  color: var(--ink-body);
}

.page--prose h2 + p {
  margin-top: 18px;
  font-size: 19px;
  color: var(--ink-title);
}

.page--prose ul {
  margin: 0;
  padding: 0;
  list-style: none;
}

.page--prose li {
  padding-left: 20px;
  position: relative;
}

.page--prose li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.72em;
  width: 5px;
  height: 5px;
  border-radius: 1px;
  background: var(--site-accent);
  transform: rotate(45deg);
}

.page--prose a:not(.key) {
  color: var(--ink-display);
  text-decoration-color: var(--line-strong);
  text-underline-offset: 3px;
}

/* Prices, as a ruled list rather than three competing cards. */
.tier {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 4px 24px;
  align-items: baseline;
  padding: 18px 0;
  border-bottom: 1px solid var(--line-soft);
}

.tier__name {
  font-family: var(--site-sans);
  font-size: 15px;
  font-weight: 600;
  color: var(--ink-display);
}

.tier__price {
  font-family: var(--site-sans);
  font-size: 15px;
  font-weight: 600;
  color: var(--doc-accent, var(--site-accent));
}

.tier__note {
  grid-column: 1 / -1;
  margin: 0;
  font-size: 15.5px;
  line-height: 1.55;
  color: var(--ink-lede);
}

.page .key {
  margin-top: 30px;
}

/* A plain statement of where something stands, set apart from the prose. */
.aside-note {
  margin-top: 30px;
  padding: 18px 20px;
  border-left: 2px solid var(--site-accent);
  background: light-dark(rgba(16, 16, 40, 0.035), rgba(255, 255, 255, 0.04));
  border-radius: 0 3px 3px 0;
}

.aside-note p {
  margin-top: 0;
  font-size: 15.5px;
  color: var(--ink-body);
}

.page__foot {
  width: 100%;
  max-width: 62ch;
  margin: auto auto 0;
  padding-top: 26px;
  border-top: 1px solid var(--line-soft);
  display: flex;
  flex-wrap: wrap;
  gap: 8px 22px;
  font-family: var(--site-sans);
  font-size: 12.5px;
  font-weight: 600;
  color: var(--ink-status);
}

.page__foot a {
  color: var(--ink-status);
  text-decoration: none;
}

.page__foot a:hover {
  color: var(--ink-display);
}

/* ==========================================================================
   Small screens, properly

   The desktop composition depends on three things a phone cannot give it: a
   pane taller than the sheet, room to the right to hang off, and a second
   column for the note. Below 900px all three are withdrawn rather than
   squeezed — the page scrolls, the sheet sits square in the column, and the
   note follows it instead of standing beside it.
   ========================================================================== */

@media (max-width: 900px) {
  .top {
    flex-wrap: wrap;
    row-gap: 14px;
  }

  .nav {
    order: 3;
    width: 100%;
    margin-left: 0;
    padding-top: 12px;
    border-top: 1px solid var(--line-soft);
  }

  /* The pane stops being the viewport and becomes a document that scrolls. */
  .screen {
    height: auto;
    min-height: calc(100dvh - var(--site-frame) * 2);
    overflow: visible;
  }

  .body {
    grid-template-rows: auto;
  }

  .stack {
    flex-direction: column;
    align-items: stretch;
    margin-right: 0;
    height: auto;
  }

  /* Square on, at full width. The tilt is a desk-top affectation and it costs
     real width on a phone. */
  .stack__inner {
    width: 100%;
    transform: none;
  }

  .paper {
    width: 100%;
    max-width: none;
    height: auto;
    /* Shorter than A4 on purpose: the foot of the page is still cut off, but by
       the sheet's own frame rather than by a pane that is no longer taller than
       it. */
    aspect-ratio: 1 / 1.15;
  }

  .paper::before {
    display: none;
  }

  .stamp {
    flex: none;
    width: 100%;
    margin: 16px 0 0;
    padding-right: 0;
  }

  /* Beside the sentence there is no margin left to sit in, so it goes under it
     as a pair of buttons you can actually hit. */
  .stepper {
    position: static;
    flex-direction: row;
    gap: 8px;
    margin-top: 18px;
  }

  .stepper button {
    width: 40px;
    height: 40px;
    border: 1px solid var(--line-strong);
  }

  .instruction {
    display: flex;
    flex-direction: column-reverse;
    align-items: flex-start;
  }
}

@media (max-width: 520px) {
  .beta {
    display: none;
  }

  .page,
  .page__foot {
    max-width: none;
  }
}

/* ==========================================================================
   Microinteractions

   The rule the rest of the page follows applies here too: motion that carries
   no information gets deleted. Each of these says something — this is a file
   you could pick up, this is a control that presses, this is the one thing left
   to do.
   ========================================================================== */

@media (prefers-reduced-motion: no-preference) {
  /* Attachments are files. They should feel liftable rather than printed on. */
  .clip {
    transition:
      transform var(--motion-info) var(--ease-out),
      box-shadow var(--motion-info) var(--ease-out);
  }

  .attached.is-in .clip:hover {
    transform: translateY(-3px);
    box-shadow: 0 10px 20px light-dark(rgba(16, 16, 40, 0.14), rgba(0, 0, 0, 0.45));
  }

  /* The key floats once everything else has finished — the page has said its
     piece, and this is the only thing left to do. It stops the moment you reach
     for it, so the float never fights the press. */
  .key.is-idle {
    animation: key-float 3.8s var(--ease-move) infinite;
  }

  .key.is-idle:hover,
  .key.is-idle:active,
  .key.is-idle:focus-visible {
    animation: none;
  }

  @keyframes key-float {
    0%,
    100% {
      transform: none;
    }
    50% {
      transform: translateY(-3.5px);
    }
  }
}

/* The stepper gets the same physics as everything else you press. It was the
   one control on the page with no side and no travel. */
.stepper button {
  box-shadow: 0 var(--depth-control-sm) 0 light-dark(#e3e3ec, rgba(0, 0, 0, 0.45));
  transition:
    color var(--motion-chrome) var(--ease-out),
    background var(--motion-chrome) var(--ease-out),
    transform var(--motion-chrome) var(--ease-out),
    box-shadow var(--motion-chrome) var(--ease-out);
}

.stepper button:active {
  transform: translateY(var(--depth-control-sm));
  box-shadow: 0 0 0 light-dark(#e3e3ec, rgba(0, 0, 0, 0.45));
}

/* Nothing to step through means nothing to press. */
.stepper button[disabled] {
  opacity: 0.32;
  cursor: default;
  box-shadow: none;
}

.stepper button[disabled]:hover {
  background: none;
  color: var(--ink-status);
}

/* The grip says it can be taken hold of before you take hold of it. */
.glass__grip {
  cursor: grab;
  transition: transform var(--motion-chrome) var(--ease-out);
}

.glass__grip:hover {
  /* Centred by margins, not a transform, so this only has to do the scale. */
  transform: scale(1.12);
}

.compare.is-dragging .glass__grip {
  cursor: grabbing;
}

/* ==========================================================================
   Two views, one pane

   The intro sets the stage and offers the only two things worth offering: take
   the address now, or watch it work. The demo is proof, not the pitch, so it
   waits behind a button. Neither view scrolls — they are the same screen in two
   states, and the pane never changes size between them.
   ========================================================================== */

.screen[data-view="intro"] .body {
  display: none;
}

.screen[data-view="demo"] .intro {
  display: none;
}

.intro {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0;
  width: 100%;
  /* In pixels, not `ch`: this element is set in the sans, so a `ch` measure
     here would size the column by the wrong typeface entirely. */
  max-width: min(100%, 840px);
  margin-inline: auto;
  padding-bottom: clamp(10px, 3vh, 34px);
  text-align: center;
}

.intro__title {
  max-width: 19ch;
  margin-top: 20px;
  font-family: var(--site-serif);
  font-size: clamp(32px, calc(4.4 * var(--u)), 58px);
  font-weight: 400;
  line-height: 1.06;
  letter-spacing: -0.025em;
  color: var(--ink-display);
  text-wrap: balance;
}

.intro__lede {
  max-width: 46ch;
  margin-top: clamp(18px, 2.6vh, 28px);
  font-family: var(--site-serif);
  font-size: clamp(17px, calc(1.45 * var(--u)), 21px);
  line-height: 1.5;
  color: var(--ink-lede);
  text-wrap: pretty;
}

/* Stacked, not side by side. Two controls on one line read as a choice between
   equals; one under the other reads as an offer and an alternative, which is
   what they actually are. */
.intro__actions {
  display: flex;
  flex-direction: column;
  /* Stretched, so the second control is exactly as wide as the first. Stacked
     buttons of different widths read as an accident. */
  align-items: stretch;
  gap: 14px;
  margin-top: clamp(26px, 4.2vh, 48px);
}

.intro__fine {
  max-width: 42ch;
  margin-top: clamp(20px, 3vh, 30px);
  font-family: var(--site-sans);
  font-size: 13.5px;
  font-weight: 600;
  line-height: 1.55;
  color: var(--ink-status);
}

/* The second way out. Flat on purpose — the raised key is the primary, and two
   objects with depth would be two primaries. */
.ghost {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-bar);
  /* The key's own padding, so the two stack to the same height as well as the
     same width. */
  padding: 20px clamp(24px, 2.2vw, 32px);
  background: none;
  font-family: var(--site-sans);
  font-size: clamp(17px, 1.5vw, 22px);
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--ink-title);
  cursor: pointer;
  transition:
    color var(--motion-chrome) var(--ease-out),
    border-color var(--motion-chrome) var(--ease-out),
    background var(--motion-chrome) var(--ease-out);
}

.ghost svg {
  width: 1em;
  height: 1em;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: transform var(--motion-info) var(--ease-out);
}

.ghost:hover {
  color: var(--ink-display);
  border-color: var(--ink-status);
  background: light-dark(rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.05));
}

.ghost:hover svg {
  transform: translateY(3px);
}

.ghost:focus-visible {
  outline: 2px solid var(--site-accent);
  outline-offset: 3px;
}

/* The key on the intro is the hero, so it is bigger there. */
.key--intro {
  padding: 20px clamp(24px, 2.2vw, 32px);
}

.key--intro .key__face {
  font-size: clamp(20px, 2vw, 30px);
}

/* ---- The shimmer --------------------------------------------------------- */
/* A specular sweep, the width of a highlight passing over something polished.
   It is the only thing on the intro that moves, which is the point: of the two
   ways out of this screen, this is the one we are pointing at. */

.key--intro::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  background: linear-gradient(
    100deg,
    transparent 38%,
    rgba(255, 255, 255, 0.42) 50%,
    transparent 62%
  );
  opacity: 0;
}

@media (prefers-reduced-motion: no-preference) {
  .key--intro::before {
    animation: key-shimmer 5.2s var(--ease-move) infinite;
  }

  @keyframes key-shimmer {
    0% {
      opacity: 0;
      transform: translateX(-65%);
    }
    6% {
      opacity: 1;
    }
    22% {
      opacity: 0;
      transform: translateX(65%);
    }
    100% {
      opacity: 0;
      transform: translateX(65%);
    }
  }

  /* Where the browser can do it, the key is the one thing that survives the
     change of view: it travels from the middle of the stage to its place in the
     email rather than being replaced by another of itself. Only one of the two
     is ever rendered, so the name is never duplicated at capture time. */
  .key {
    view-transition-name: address-key;
  }

  /* The stage leaves upward and the demo comes up under it, rather than the two
     dissolving into each other on the spot. A crossfade tells you the screen
     changed; this tells you it moved. */
  ::view-transition-old(root) {
    animation: stage-leave 460ms var(--ease-move) both;
  }

  ::view-transition-new(root) {
    animation: demo-enter 720ms var(--ease-settle) 120ms both;
  }

  @keyframes stage-leave {
    to {
      opacity: 0;
      transform: translateY(-26px) scale(0.985);
    }
  }

  @keyframes demo-enter {
    from {
      opacity: 0;
      transform: translateY(34px);
    }
  }

  /* The key takes its own time to travel, and settles rather than arriving. It
     is the one thing on screen the eye can follow across the change. */
  ::view-transition-group(address-key) {
    animation-duration: 780ms;
    animation-timing-function: var(--ease-settle);
  }

  /* And once it lands, the email assembles itself in reading order. The sheet
     has its own arrival, so it is left out of this. */
  .screen[data-view="demo"] .say > *:not(.send) {
    animation: stage-rise 560ms var(--ease-out) both;
  }

  .screen[data-view="demo"] .say > *:nth-child(2) {
    animation-delay: 90ms;
  }

  .screen[data-view="demo"] .say > *:nth-child(3) {
    animation-delay: 180ms;
  }

  .screen[data-view="demo"] .say > *:nth-child(4) {
    animation-delay: 270ms;
  }
}

@media (max-width: 620px) {
  .intro {
    max-width: none;
  }

  .intro__actions {
    align-items: stretch;
    width: 100%;
  }

  .ghost {
    justify-content: center;
  }
}

/* ---- The stage, arriving -------------------------------------------------- */
/* Pure CSS, and deliberately so. This used to hide everything and wait for
   JavaScript to put it back; one script error and the whole page was a blank
   pane. An animation with `both` fill hides an element only for as long as it
   is going to animate it, and an engine that cannot run it shows the content
   instead of hiding it forever. */

@media (prefers-reduced-motion: no-preference) {
  .intro > * {
    animation: stage-rise var(--motion-beat) var(--ease-settle) both;
  }

  .intro > *:nth-child(2) {
    animation-delay: 90ms;
  }

  .intro > *:nth-child(3) {
    animation-delay: 180ms;
  }

  .intro > *:nth-child(4) {
    animation-delay: 270ms;
  }

  .intro > *:nth-child(5) {
    animation-delay: 360ms;
  }

  @keyframes stage-rise {
    from {
      opacity: 0;
      transform: translateY(16px);
    }
  }
}

/* The formats line, doing the job the eyebrow was wasting: the first thing
   anyone needs to know is whether their file is on the list. */
/* The separators were lost with the old hero. Set as flex gaps rather than
   punctuation glyphs, so the run reads as a list at any width. */
.intro .formats {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 6px 0;
  margin-top: 0;
  font-family: var(--site-sans);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-status);
}

/* The second way out is raised too — it is a control, and on this page controls
   have a side. It is the neutral of the two, which is the hierarchy: colour
   separates them, not one of them pretending to be flat. */
.ghost {
  background: light-dark(#ffffff, #2b2f42);
  border: 0;
  color: var(--ink-display);
  box-shadow:
    0 var(--depth-control) 0 light-dark(#dcdce6, #10121c),
    0 10px 20px light-dark(rgba(16, 16, 40, 0.12), rgba(0, 0, 0, 0.4)),
    inset 0 1px 0 light-dark(rgba(255, 255, 255, 0.9), rgba(255, 255, 255, 0.07));
  transition:
    transform var(--motion-chrome) var(--ease-out),
    box-shadow var(--motion-chrome) var(--ease-out);
}

.ghost:hover {
  background: light-dark(#ffffff, #333853);
  border: 0;
}

.ghost:active {
  transform: translateY(var(--depth-control));
  box-shadow:
    0 0 0 light-dark(#dcdce6, #10121c),
    0 2px 6px light-dark(rgba(16, 16, 40, 0.12), rgba(0, 0, 0, 0.4));
}

/* ==========================================================================
   If light-dark() is not understood

   Safari only learned `light-dark()` in 17.5. Without it every one of those
   declarations is dropped at parse time and the tokens fall back to the light
   values in tokens.css — which, on a dark pane, is near-black text on near-black
   glass. The page looks empty.

   This is the one place duplicating the dark palette is right: it is a fallback,
   not a second source of truth, and it only ever runs where the good version
   cannot.
   ========================================================================== */

@supports not (color: light-dark(#fff, #000)) {
  @media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
      --surface-page: #08090f;
      --surface-canvas: #12131c;
      --surface-raised: #1b1d29;
      --surface-tile: #23263a;
      --surface-chip: #23263a;
      --ink-display: #f4f4f8;
      --ink-strong: #e8ebfb;
      --ink-title: #dfe1ee;
      --ink-body: #b9bccd;
      --ink-lede: #9ba1b7;
      --ink-status: #8f95ad;
      --ink-nav: #9ba1b7;
      --line-soft: rgba(232, 235, 251, 0.1);
      --line-strong: rgba(232, 235, 251, 0.22);
      --site-edge: #04050b;
      --site-accent: #8ea0ec;
      --site-veil: rgba(30, 33, 48, 0.62);
      --site-glass: rgba(9, 11, 20, 0.8);
      --site-glass-edge: rgba(255, 255, 255, 0.1);
      --doc-band: rgba(142, 160, 236, 0.14);
    }
  }

  :root[data-theme="dark"] {
    --surface-page: #08090f;
    --surface-canvas: #12131c;
    --surface-raised: #1b1d29;
    --surface-tile: #23263a;
    --surface-chip: #23263a;
    --ink-display: #f4f4f8;
    --ink-strong: #e8ebfb;
    --ink-title: #dfe1ee;
    --ink-body: #b9bccd;
    --ink-lede: #9ba1b7;
    --ink-status: #8f95ad;
    --ink-nav: #9ba1b7;
    --line-soft: rgba(232, 235, 251, 0.1);
    --line-strong: rgba(232, 235, 251, 0.22);
    --site-edge: #04050b;
    --site-accent: #8ea0ec;
    --site-veil: rgba(30, 33, 48, 0.62);
    --site-glass: rgba(9, 11, 20, 0.8);
    --site-glass-edge: rgba(255, 255, 255, 0.1);
    --doc-band: rgba(142, 160, 236, 0.14);
  }

  /* The document identities are per-sheet, so they cannot ride on the root
     palette above — each one has to state its dark values itself. Named the
     long way, twice, for the same reason as everything else in this block. */
  @media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .sheet--caboo {
      --doc-accent: #8496e0;
      --doc-solid: #242e69;
      --doc-on-solid: #ccd4f7;
      --doc-band: rgba(142, 160, 236, 0.12);
    }

    :root:not([data-theme="light"]) .sheet--rowan {
      --doc-accent: #6fbb98;
      --doc-solid: #1d4234;
      --doc-on-solid: #b4e2cb;
      --doc-band: rgba(121, 196, 162, 0.11);
    }

    :root:not([data-theme="light"]) .sheet--lettings {
      --doc-accent: #dd8770;
      --doc-solid: #6b3020;
      --doc-on-solid: #f5c4b3;
      --doc-band: rgba(229, 144, 121, 0.12);
    }
  }

  :root[data-theme="dark"] .sheet--caboo {
    --doc-accent: #8496e0;
    --doc-solid: #242e69;
    --doc-on-solid: #ccd4f7;
    --doc-band: rgba(142, 160, 236, 0.12);
  }

  :root[data-theme="dark"] .sheet--rowan {
    --doc-accent: #6fbb98;
    --doc-solid: #1d4234;
    --doc-on-solid: #b4e2cb;
    --doc-band: rgba(121, 196, 162, 0.11);
  }

  :root[data-theme="dark"] .sheet--lettings {
    --doc-accent: #dd8770;
    --doc-solid: #6b3020;
    --doc-on-solid: #f5c4b3;
    --doc-band: rgba(229, 144, 121, 0.12);
  }

  /* The sheet is the exception in both directions: it stays paper-coloured. */
  :root[data-theme="dark"] .paper .sheet,
  :root[data-theme="dark"] .paper {
    --surface-raised: #1b1d29;
  }
}

/* A menu sets its prices against the dish with the space between doing the
   joining — no rules, no columns, just the two ends of a line. */
.menu {
  margin-top: 0.7em;
}

.menu__row {
  display: flex;
  align-items: baseline;
  gap: 0.6em;
  padding: 0.32em 0;
}

.menu__row dt {
  flex: none;
}

.menu__row::after {
  content: "";
  order: 1;
  flex: 1;
  height: 1px;
  margin-bottom: 0.28em;
  background: var(--line-soft);
}

.menu__row dd {
  order: 2;
  flex: none;
  font-variant-numeric: tabular-nums;
  color: var(--ink-strong);
}

/* Its sections are named, not numbered, and centred is a restaurant cliché — so
   they keep the form treatment's band without the uppercase shout. */
.sheet--rowan .menu + .sheet__h,
.sheet--rowan .sheet__h {
  text-transform: none;
  letter-spacing: 0.02em;
}

/* ==========================================================================
   Pricing and Why Patch

   These two are the only pages that are not prose, and the 62ch measure that
   serves Policy was strangling them — a five-column comparison squeezed into a
   reading column wraps every answer onto three lines. So they drop the measure
   entirely and work in full-width bands that bleed to the edge of the glass,
   with each block deciding its own width from the inside.
   ========================================================================== */

.page--full {
  max-width: none;
  margin-top: 0;
  padding-bottom: 0;
}

/* The band bleeds; the content inside it does not. A three-track grid gives a
   centred column of at most --page-max with the leftovers as gutters, so a
   rule or a tint can run the full width of the glass while the words stay in a
   column you can read across. Children keep their own alignment inside it,
   which a `margin-inline: auto` container would have taken away. */
.page--full,
.band {
  display: grid;
  grid-template-columns:
    [full-start] minmax(var(--site-pad), 1fr)
    [in-start] minmax(0, var(--page-max)) [in-end]
    minmax(var(--site-pad), 1fr) [full-end];
}

.page--full > *,
.band > * {
  grid-column: in;
  /* Grid items are min-width:auto by default, so the comparison — 860px wide
     before it agrees to scroll — was widening its own track and pushing the
     whole pane off a phone screen. */
  min-width: 0;
}

/* Out through the pane's own padding, so the band reaches the edge of the
   glass rather than stopping on the page gutter. */
.band {
  grid-column: full;
  margin-inline: calc(var(--site-pad) * -1);
  padding-block: clamp(34px, 5.4vh, 64px);
}

.band--ruled {
  border-top: 1px solid var(--line-soft);
}

.band--close {
  border-top: 1px solid var(--line-soft);
  padding-bottom: clamp(48px, 8vh, 90px);
  text-align: center;
}

.band--close .close__note {
  margin-inline: auto;
}

/* ---- The opening statement ----------------------------------------------- */

.lede-block {
  padding: clamp(40px, 8vh, 96px) 0 clamp(30px, 5vh, 56px);
}

/* The statement is the one thing allowed to run past the reading column — it
   is three words to a line, not a paragraph. */
.lede-block .huge {
  max-width: 21ch;
}

.huge {
  margin-top: 18px;
  font-family: var(--site-serif);
  font-size: clamp(38px, 6.2vw, 88px);
  font-weight: 400;
  line-height: 1.03;
  letter-spacing: -0.035em;
  color: var(--ink-display);
}

.lede-block__sub {
  max-width: 52ch;
  margin-top: clamp(20px, 2.6vh, 30px);
  font-family: var(--site-serif);
  font-size: clamp(17px, 1.5vw, 21px);
  line-height: 1.55;
  color: var(--ink-lede);
}

/* ---- Plans --------------------------------------------------------------- */

.plans {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 16px;
  align-items: start;
}

.plan {
  display: flex;
  flex-direction: column;
  height: 100%;
  padding: clamp(24px, 2.4vw, 34px);
  border: 1px solid var(--line-soft);
  border-radius: 14px;
  background: light-dark(rgba(255, 255, 255, 0.44), rgba(255, 255, 255, 0.03));
}

/* One card is the recommendation, so it is the only one carrying the brand
   colour and the only one standing off the surface. */
.plan--lead {
  border-color: light-dark(rgba(50, 69, 169, 0.32), rgba(142, 160, 236, 0.32));
  background: light-dark(rgba(50, 69, 169, 0.055), rgba(142, 160, 236, 0.075));
  box-shadow:
    0 1px 0 light-dark(rgba(255, 255, 255, 0.7), rgba(255, 255, 255, 0.05)) inset,
    0 14px 34px light-dark(rgba(28, 34, 82, 0.09), rgba(0, 0, 0, 0.3));
}

.plan__flag {
  margin-bottom: 10px;
  font-family: var(--site-sans);
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--site-accent);
}

.plan__name {
  font-family: var(--site-sans);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--ink-status);
}

.plan__fig {
  display: flex;
  align-items: baseline;
  gap: 2px;
  margin-top: 14px;
  font-family: var(--site-serif);
  line-height: 1;
  color: var(--ink-display);
}

.plan__cur {
  font-size: clamp(20px, 1.7vw, 26px);
  transform: translateY(-0.45em);
}

.plan__amt {
  font-size: clamp(46px, 4.6vw, 66px);
  letter-spacing: -0.035em;
}

.plan__per {
  margin-left: 7px;
  font-family: var(--site-sans);
  font-size: 13px;
  font-weight: 600;
  color: var(--ink-status);
}

.plan__sub {
  margin-top: 16px;
  font-family: var(--site-sans);
  font-size: 14.5px;
  font-weight: 600;
  line-height: 1.45;
  letter-spacing: -0.01em;
  color: var(--ink-display);
}

.plan__list {
  margin: 18px 0 0;
  padding: 18px 0 0;
  border-top: 1px solid var(--line-soft);
  list-style: none;
}

.plan__list li {
  position: relative;
  margin-bottom: 9px;
  padding-left: 16px;
  font-family: var(--site-sans);
  font-size: 13.5px;
  line-height: 1.45;
  color: var(--ink-lede);
}

.plan__list li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.52em;
  width: 4px;
  height: 4px;
  border-radius: 1px;
  background: var(--ink-status);
  transform: rotate(45deg);
}

.plan--lead .plan__list li::before {
  background: var(--site-accent);
}

.plan__foot {
  margin-top: auto;
  padding-top: 16px;
  font-family: var(--site-sans);
  font-size: 13px;
  line-height: 1.45;
  color: var(--ink-lede);
}

.plan__foot b {
  color: var(--ink-display);
}

/* ---- Quoted and not-yet -------------------------------------------------- */

/* Not offers, so not cards — three notes on one rule. */
.later {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(24px, 3vw, 52px);
}

.later__name {
  font-family: var(--site-sans);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-status);
}

.later__fig {
  margin-top: 10px;
  font-family: var(--site-serif);
  font-size: clamp(24px, 2.2vw, 32px);
  line-height: 1.1;
  letter-spacing: -0.02em;
  color: var(--ink-display);
}

.later__note {
  max-width: 40ch;
  margin-top: 12px;
  font-family: var(--site-sans);
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--ink-lede);
}

/* ---- The counting rules -------------------------------------------------- */

.rules__head,
.vs-head__title {
  font-family: var(--site-serif);
  font-size: clamp(26px, 2.8vw, 38px);
  font-weight: 400;
  line-height: 1.1;
  letter-spacing: -0.025em;
  color: var(--ink-display);
}

.rules__grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(22px, 2.6vw, 44px);
  margin-top: clamp(24px, 3vh, 38px);
}

.rules__q {
  font-family: var(--site-sans);
  font-size: 14px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--ink-display);
}

.rules__a {
  max-width: 54ch;
  margin-top: 9px;
  font-family: var(--site-serif);
  font-size: 16.5px;
  line-height: 1.6;
  color: var(--ink-body);
}

/* ---- What Patch does differently ----------------------------------------- */

.diffs {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 2px;
  border-radius: 14px;
  overflow: hidden;
  /* The gap is the rule: one background behind a two-pixel grid draws every
     divider at once, without six borders doubling up at the seams. */
  background: var(--line-soft);
}

.diff {
  padding: clamp(22px, 2.2vw, 32px);
  background: light-dark(rgba(255, 255, 255, 0.44), rgba(255, 255, 255, 0.03));
}

/* Solar Bold Duotone, drawn inline so the mark is one paint layer with the
   card and takes the accent by inheritance rather than by a second colour. */
.diff__icon {
  display: block;
  color: var(--site-accent);
}

.diff__icon svg {
  display: block;
  width: 30px;
  height: 30px;
  fill: currentColor;
}

.diff__name {
  margin-top: 14px;
  font-family: var(--site-serif);
  font-size: clamp(19px, 1.7vw, 24px);
  line-height: 1.2;
  letter-spacing: -0.02em;
  color: var(--ink-display);
}

.diff__note {
  margin-top: 11px;
  font-family: var(--site-sans);
  font-size: 13.5px;
  line-height: 1.6;
  color: var(--ink-lede);
}

/* ---- The comparison ------------------------------------------------------ */

.vs-head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: 16px 40px;
  margin-bottom: clamp(20px, 2.6vh, 30px);
}

.vs-head__note {
  max-width: 46ch;
  font-family: var(--site-sans);
  font-size: 12.5px;
  line-height: 1.55;
  color: var(--ink-status);
}

.vs-wrap {
  overflow-x: auto;
  border: 1px solid var(--line-soft);
  border-radius: 14px;
  /* Wide content scrolls inside its own box; the page never does. */
  scrollbar-width: thin;
}

.vs {
  width: 100%;
  min-width: 860px;
  border-collapse: collapse;
  /* Fixed, so the four products get equal room and no column collapses to one
     word per line because its answer happened to be short. */
  table-layout: fixed;
  font-family: var(--site-sans);
}

.vs__axis {
  width: 24%;
}

.vs col:not(.vs__axis) {
  width: 19%;
}

.vs th,
.vs td {
  padding: 14px clamp(14px, 1.4vw, 20px);
  border-bottom: 1px solid var(--line-soft);
  text-align: left;
  font-size: 13.5px;
  font-weight: 600;
  line-height: 1.4;
  vertical-align: top;
  color: var(--ink-lede);
}

.vs tbody tr:last-child th,
.vs tbody tr:last-child td {
  border-bottom: 0;
}

.vs thead th {
  padding-top: 17px;
  padding-bottom: 15px;
  border-bottom: 1px solid var(--line-strong);
  font-size: 13px;
  color: var(--ink-status);
}

.vs tbody th {
  color: var(--ink-status);
}

/* Our column is the one being argued for, so it is the one that is lit — the
   same treatment as the recommended plan, for the same reason. */
.vs .vs__us {
  background: light-dark(rgba(50, 69, 169, 0.05), rgba(142, 160, 236, 0.06));
  color: var(--ink-display);
}

.vs thead .vs__us {
  color: var(--site-accent);
  border-bottom-color: var(--site-accent);
}

.vs__row--price td {
  padding-top: 18px;
  padding-bottom: 18px;
}

.vs__row--price b {
  display: block;
  font-family: var(--site-serif);
  font-size: clamp(24px, 2vw, 30px);
  font-weight: 400;
  letter-spacing: -0.025em;
  color: var(--ink-display);
}

.vs__fine {
  display: block;
  margin-top: 4px;
  font-size: 11.5px;
  font-weight: 600;
  color: var(--ink-status);
}

/* The mark and the answer, not the mark instead of it. A tick on its own says
   a product has something; it never says what it is like to use. Three states,
   because two would force every "down to how you edit" into a cross it does
   not deserve. */
.mk {
  display: inline-flex;
  align-items: flex-start;
  gap: 8px;
}

.mk svg {
  flex: none;
  width: 15px;
  height: 15px;
  margin-top: 0.08em;
}

.mk--yes svg {
  color: light-dark(#2f6b4f, #6fbb98);
}

.mk--no svg {
  color: light-dark(#a24334, #d98a7c);
}

.mk--mid svg {
  color: var(--ink-status);
}

.vs__us .mk--yes svg {
  color: var(--site-accent);
}

.vs-foot {
  max-width: 62ch;
  margin-top: clamp(18px, 2.4vh, 26px);
  font-family: var(--site-serif);
  font-size: clamp(17px, 1.5vw, 20px);
  line-height: 1.55;
  color: var(--ink-title);
}

/* ---- The close ----------------------------------------------------------- */

.close__eyebrow {
  font-family: var(--site-sans);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--ink-status);
}

/* The key is display:grid, so on its own in a full-width band it stretches
   the width of the page. It is a button, not a bar. */
.band--close .key {
  width: fit-content;
  margin: 24px auto 0;
}

.close__note {
  max-width: 62ch;
  margin: 26px auto 0;
  font-family: var(--site-sans);
  font-size: 12.5px;
  line-height: 1.65;
  color: var(--ink-status);
}

.close__note a {
  color: var(--ink-body);
  text-decoration-color: var(--line-strong);
  text-underline-offset: 3px;
}

/* ---- Narrower ------------------------------------------------------------ */

@media (max-width: 1020px) {
  .plans,
  .diffs,
  .later {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  /* The recommendation leads on a narrow screen rather than sitting in the
     middle of a row that no longer exists. */
  .plan--lead {
    order: -1;
  }

}

@media (max-width: 680px) {
  .plans,
  .diffs,
  .later,
  .rules__grid {
    grid-template-columns: 1fr;
  }

  .vs th,
  .vs td {
    padding: 12px 13px;
    font-size: 12.5px;
  }

  /* The statement is broken by hand for a wide screen. On a phone those breaks
     fight the natural wrap and leave a two-word line stranded, so the column
     takes over and the hand-set breaks stand down. */
  .lede-block .huge {
    max-width: none;
  }

  .lede-block .huge br {
    display: none;
  }
}


/* ==========================================================================
   Technical documents

   A drawing office sets a page differently from an office: monospaced figures
   so a column of dimensions lines up on the decimal, ruled cells rather than
   white space, and a title block along the foot. These two exist because the
   demo was five business documents in a row, and "change it everywhere it
   appears" is at its most convincing on a page where a number genuinely does
   appear in more than one place.
   ========================================================================== */

/* Denser than the business documents on purpose. A datasheet fits four spec
   rows, six parts and three notes on one page precisely because it is set small
   — at the size a contract uses, the labels wrap and it stops looking like a
   drawing office produced it. */
.sheet--tech {
  font-size: clamp(6px, 2.1cqw, 13px);
}

/* The drawing is the document's own artwork and does not follow the theme, for
   the same reason the sheet does not: this is a file that came back unchanged. */
.drawing {
  display: block;
  width: calc(100% + 16%);
  /* Capped, because the drawing is evidence that the page was not touched, not
     the subject of the page — left uncapped it ate half the sheet and pushed
     the figures that actually change below the fold. */
  height: 26%;
  margin: 0.9em -8% 1.3em;
  /* contain, not cover: cropping a general arrangement drawing cuts the top off
     the vessel, which is the one thing a GA drawing exists to show. The plate
     is painted the drawing's own blue so the letterboxing does not read as a
     border. */
  object-fit: contain;
  background: #0456a6;
}

/* Black line-work on white. Left alone in the dark it is a lightbox in the
   middle of the sheet, which is exactly the fault the letterheads had; inverted,
   it becomes white line-work on black and reads as a drawing either way. */
/* `filter` takes no light-dark(), so the dark case is stated the long way —
   twice, matching how every other theme-dependent rule in this file works. */
.drawing--tall {
  width: 100%;
  height: 30%;
  margin-inline: 0;
  object-fit: contain;
  background: #ffffff;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .drawing--tall {
    filter: invert(1) hue-rotate(180deg);
  }
}

:root[data-theme="dark"] .drawing--tall {
  filter: invert(1) hue-rotate(180deg);
}

/* Two label/value pairs a row, ruled like a datasheet rather than spaced like
   prose. The label column is fixed so the values line up down the page. */
.spec {
  width: 100%;
  margin-top: 1.1em;
  border-collapse: collapse;
  border-top: 1px solid var(--line-strong);
}

.spec th,
.spec td {
  padding: 0.5em 0.6em;
  border-bottom: 1px solid var(--line-soft);
  text-align: left;
  font-family: var(--site-sans);
  font-size: 0.82em;
  font-weight: 500;
  vertical-align: baseline;
}

.spec th {
  width: 22%;
  color: var(--ink-status);
  font-weight: 600;
}

.spec td {
  width: 28%;
  font-variant-numeric: tabular-nums;
  color: var(--ink-strong);
}

.spec th + th {
  border-left: 1px solid var(--line-soft);
  padding-left: 1em;
}

/* The parts list. Item numbers and quantities are narrow and centred; the part
   number carries the change, so it sits in the same tabular figures as the
   quantity and cannot shuffle the row when it changes. */
.bom {
  width: 100%;
  margin-top: 1.1em;
  border-collapse: collapse;
}

.bom th,
.bom td {
  padding: 0.44em 0.6em;
  border-bottom: 1px solid var(--line-soft);
  text-align: left;
  font-family: var(--site-sans);
  font-size: 0.8em;
  font-variant-numeric: tabular-nums;
}

.bom thead th {
  border-bottom: 1px solid var(--doc-accent);
  color: var(--doc-accent);
  font-size: 0.72em;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.bom td {
  color: var(--ink-strong);
}

.bom td:first-child {
  width: 12%;
  color: var(--ink-status);
}

.bom td:last-child,
.bom th:last-child {
  width: 10%;
  text-align: right;
}

.notes {
  margin: 1.2em 0 0;
  padding-left: 1.5em;
  font-family: var(--site-sans);
  font-size: 0.78em;
  line-height: 1.55;
  color: var(--ink-body);
}

.notes li {
  margin-bottom: 0.45em;
  padding-left: 0.2em;
}

.notes li::marker {
  color: var(--doc-accent);
  font-weight: 600;
}

/* The revision history. Every drawing carries one, and it is the one table on
   the page where a change is supposed to add a row rather than overwrite a
   value — so it is where the new revision becomes visible. */
.revs {
  width: 100%;
  margin-top: 1.2em;
  border-collapse: collapse;
}

.revs th,
.revs td {
  padding: 0.4em 0.6em;
  border-bottom: 1px solid var(--line-soft);
  text-align: left;
  font-family: var(--site-sans);
  font-size: 0.76em;
  font-variant-numeric: tabular-nums;
  color: var(--ink-body);
}

.revs thead th {
  border-bottom: 1px solid var(--doc-accent);
  color: var(--doc-accent);
  font-size: 0.7em;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.revs td:first-child,
.revs th:first-child {
  width: 10%;
  font-weight: 600;
}

.revs td:nth-child(3),
.revs td:last-child {
  width: 16%;
  color: var(--ink-status);
}

.revs td:last-child {
  width: 10%;
}

/* The title block runs along the foot of a drawing sheet, ruled off from the
   content above it. It is the last thing a drawing has and the first thing a
   reader checks, which is why the revision letter lives here. */
.titleblock {
  display: flex;
  justify-content: space-between;
  gap: 1em;
  margin-top: auto;
  padding-top: 0.6em;
  border-top: 1px solid var(--doc-accent);
  font-family: var(--site-sans);
  font-size: 0.72em;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--ink-status);
}

.titleblock span:last-child {
  color: var(--doc-accent);
}
