/*
 * Kymatra
 *
 * One visual world, deliberately. This is a wallpaper that glows on a dark
 * desktop, so the site commits to the app's own palette rather than following
 * the reader's theme: near black ground, the wave gray, and the amber the app
 * icon is drawn in. Every color is painted explicitly so the page holds
 * whatever ground it lands on.
 *
 * One stylesheet for every page, so the look changes in one place.
 */

/*
 * Typefaces
 *
 * Served from this domain rather than from Google, so the site makes no third
 * party requests at all. That is worth the two files: an app sold on never
 * connecting to anything should not have a marketing page that quietly tells
 * Google who is reading it.
 *
 * One variable file per family covers every weight used here. Google serves the
 * same file whatever weight is asked for, so five requests were five copies of
 * two fonts.
 *
 * Chivo and Chivo Mono are licensed under the SIL Open Font License 1.1. The
 * license travels with them, in assets/fonts/OFL.txt.
 */

@font-face {
  font-family: "Chivo";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("/assets/fonts/chivo.woff2") format("woff2");
}

@font-face {
  font-family: "Chivo Mono";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("/assets/fonts/chivo-mono.woff2") format("woff2");
}

:root {
  /*
   * Palette
   *
   * Taken from the app icon rather than chosen beside it. The mark has three
   * colours and the site had nine, drawn to look like the mark without being
   * it, which is why the icon read as a visitor on its own page.
   *
   * Ground and amber are the icon's own two, to the byte. Rule is the grey the
   * icon's wave used to be drawn in, kept here after the wave itself went. The
   * four neutrals between them are points on the line from ground to that grey,
   * so every neutral on this page is a mix of two colours the icon contains or
   * once did. Those points were chosen to land on
   * the elevation the site already had: 1.10, 1.20 and 1.35 against ground,
   * where the hand mixed set sat at 1.08, 1.17 and 1.36.
   *
   * Text ratios against ground, all above AA:
   *
   *     bright    15.55:1
   *     amber     10.71:1
   *     gray       7.46:1
   *     amber-dim  6.56:1
   *
   * Amber is carried as components as well, since five rgba() glows quoted it
   * by hand and would have kept the old colour when this one moved.
   */

  --ground: #121417;
  --surface: #1B1E23;
  --raised: #21252B;
  --line: #292E35;
  --rule: #454D59;
  --amber: #FFB842;
  --amber-rgb: 255, 184, 66;
  --amber-dim: #C79033;
  --gray: #9BA6B2;
  --bright: #E8ECF1;
  --ink: #121417;

  /* text runs the width of its box. it was capped at 62ch, which read as
     four different right edges on one page: the headline and the stat
     boxes ran to the wrap, the lede stopped short of them, and the prose
     stopped short of that. the cap is the more usual typographic answer
     and it is not the one that looks deliberate here */
  --measure: none;
  --gutter: clamp(1.25rem, 5vw, 3rem);

  --step-0: clamp(1rem, 0.96rem + 0.2vw, 1.125rem);
  --step-1: clamp(1.25rem, 1.16rem + 0.45vw, 1.5rem);
  --step-2: clamp(1.6rem, 1.4rem + 1vw, 2.25rem);
  --step-3: clamp(2.2rem, 1.7rem + 2.5vw, 3.5rem);
  --step-4: clamp(2.8rem, 1.8rem + 5vw, 5.5rem);
}

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

html {
  -webkit-text-size-adjust: 100%;
  /* clears the sticky bar, which measures 101 points at its tallest */
  scroll-padding-top: 6.5rem;
}

body {
  margin: 0;
  background: var(--ground);
  color: var(--gray);
  font-family: Chivo, "Helvetica Neue", Arial, sans-serif;
  font-size: var(--step-0);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3 {
  color: var(--bright);
  font-weight: 900;
  line-height: 1.05;
  letter-spacing: -0.02em;
  text-wrap: balance;
  margin: 0;
}

p { margin: 0; }

a { color: var(--amber); }
a:hover { color: var(--bright); }

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

.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--amber);
  color: var(--ink);
  padding: 0.75rem 1rem;
  font-weight: 600;
  /* above the sticky bar, which the focused link would otherwise land behind */
  z-index: 60;
}
.skip:focus { left: 0; }

/* Read but not seen. Clipped rather than display:none, which would take it out
   of the accessibility tree along with everything else. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.wrap {
  width: 100%;
  max-width: 68rem;
  margin-inline: auto;
  padding-inline: var(--gutter);
}

/* .wrap-narrow is gone from the markup, every page being one width now */

/*
 * Updates
 *
 * A rule between releases rather than after each one, so the list does not end
 * on a line under the oldest version with nothing beneath it.
 */

.update + .update {
  border-top: 1px solid var(--rule);
  margin-top: clamp(2.5rem, 6vw, 3.5rem);
  padding-top: clamp(2.5rem, 6vw, 3.5rem);
}

/*
 * Words set in the mark's own alphabet
 *
 * Images, so each carries real alt text: a screen reader gets nothing from a
 * drawing of a letter and neither does a search engine.
 *
 * Sized by height with the width left to follow, the alphabet's own
 * proportions deciding it. Setting a width instead would squash the letters.
 *
 * The masthead is not one of them. It carried the word too, directly above the
 * hero's, and two of the same word a hundred points apart is one too many.
 */

.hero-wordmark {
  height: clamp(2rem, 5vw, 3.25rem);
  width: auto;
  margin-bottom: clamp(1.25rem, 3vw, 2rem);
}

.eyebrow {
  font-family: "Chivo Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.75rem;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--amber-dim);
  margin: 0;
}

/* Header */

/*
 * Sticky bar
 *
 * Bar is its own full width element rather than the wrap or the header itself.
 * A sticky element travels only within its parent, and the wrap holds nothing
 * but the header, so sticking either one gives it no distance to travel and it
 * scrolls away like static markup. Full width is also what gives the fill
 * something to cover: the wrap is a centered column, and a background on it
 * would let the page slide past in the margins either side.
 *
 * Fill is flat rather than a translucent blur. A blur costs a compositor pass
 * on every frame of every scroll, and the thing it buys is texture the rest of
 * this site does not use anywhere.
 */

.masthead-bar {
  position: sticky;
  top: 0;
  z-index: 50;
  /* surface rather than ground, and a wave coloured edge under it. Filled in
     the page's own colour the bar had nothing to separate it from the page,
     so a header that stayed put read as content going missing underneath it */
  background: var(--surface);
  border-bottom: 1px solid var(--rule);
}

/*
 * The height of a control in this bar
 *
 * A button is its padding, its border and a line of text, so this is those
 * three added up rather than the number they come to. The icon is set to it,
 * which is what makes the mark and the buttons read as one row of things
 * rather than a small picture beside two big ones, and it goes on matching
 * when either half is retuned.
 */

.masthead {
  --control: calc(1.4rem + 2px + 1.6 * var(--step-0));

  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding-block: 1.5rem;
}

/* the icon alone. the name was set beside it in text and the hero says it in
   the alphabet the app is drawn in, which is the better place for it */
.wordmark {
  display: flex;
  align-items: center;
  text-decoration: none;
}
.wordmark img {
  display: block;
  height: var(--control);
  width: auto;
}

/* Buttons */

.actions { display: flex; flex-wrap: wrap; gap: 0.75rem; }
.actions-spaced { margin-top: 1.5rem; }

.button {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.7rem 1.15rem;
  border-radius: 6px;
  border: 1px solid transparent;
  font: inherit;
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
  transition: background-color 140ms ease, border-color 140ms ease, color 140ms ease, box-shadow 140ms ease;
}

/*
 * Hover lives here rather than in the script: it should be instant, cost no
 * listener, and survive a blocked script. The press does not, and lives in
 * motion.js, which also means transform belongs entirely to the script and
 * nothing here fights it for that property.
 *
 * The glow is the trace's glow. A generic lift would have suited any button on
 * any site; this one is the product.
 */

.button-primary { background: var(--amber); color: var(--ink); }
.button-primary:hover {
  background: #FFC766;
  color: var(--ink);
  box-shadow:
    0 0 0 1px rgba(var(--amber-rgb), 0.35),
    0 8px 24px -8px rgba(var(--amber-rgb), 0.55);
}

.button-quiet {
  background: transparent;
  border-color: var(--line);
  color: var(--bright);
}
.button-quiet:hover {
  border-color: var(--gray);
  color: var(--bright);
  background: rgba(232, 236, 241, 0.04);
}

/*
 * Two header buttons on a phone
 *
 * Masthead is one row of wordmark and actions, and actions wraps rather than
 * overflowing. At 375 points two buttons come to 228 against 209 available, so
 * they stack and header grows from 90 points to 160, which is a quarter of
 * first screen spent on navigation. Trimming padding and type in header alone
 * brings them back onto one row. Body buttons are untouched, having room.
 */

@media (max-width: 480px) {
  .masthead {
    --control: calc(1.2rem + 2px + 1.44rem);
  }

  .masthead .button {
    padding: 0.6rem 0.85rem;
    font-size: 0.9rem;
  }
}

/* Choosing what the scope listens to */

.scope-audio {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem 0.75rem;
  padding: 0.9rem 1rem 0;
}

.audio-button,
.audio-file span {
  font-family: inherit;
  font-size: 0.82rem;
  font-weight: 600;
  padding: 0.5rem 0.9rem;
  border-radius: 7px;
  border: 1px solid rgba(var(--amber-rgb), 0.35);
  background: rgba(var(--amber-rgb), 0.1);
  color: var(--amber);
  cursor: pointer;
  display: inline-block;
  line-height: 1.2;
}

.audio-button:hover,
.audio-file:hover span {
  background: rgba(var(--amber-rgb), 0.18);
  border-color: rgba(var(--amber-rgb), 0.55);
}

/* the input is the control, so it takes focus rather than the label around it */
.audio-file input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
}

.audio-file {
  position: relative;
  display: inline-block;
}

.audio-file input:focus-visible + span {
  outline: 2px solid var(--amber);
  outline-offset: 2px;
}

/* which source is playing, since two buttons that both look available give no
   clue which one you are listening to */
.audio-button.is-active,
.audio-file.is-active span {
  background: var(--amber);
  border-color: var(--amber);
  color: var(--ink);
}

.audio-note {
  flex: 1 1 16rem;
  font-size: 0.78rem;
  line-height: 1.5;
  color: var(--gray);
  opacity: 0.8;
  margin: 0;
}

/* Hero */

.hero { padding-block: clamp(2rem, 6vw, 4.5rem) clamp(1.5rem, 4vw, 3rem); }

.hero h1 {
  font-size: var(--step-4);
  margin-block: 0.6rem 1.1rem;
}

/*
 * The retyping headline
 *
 * text-wrap is returned to normal here alone. Balancing re-decides where the
 * lines break, and re-deciding that on every character makes finished words
 * jump between lines while later ones are still being typed.
 *
 * Height is reserved by the script rather than here, since which phrase is
 * tallest depends on how wide the window is. Without it everything below the
 * headline lifts and drops each time a phrase changes line count.
 */
.headline { text-wrap: normal; }

.headline-caret {
  display: none;
  width: 0.055em;
  height: 0.72em;
  margin-left: 0.07em;
  vertical-align: -0.02em;
  background: var(--amber);
  animation: caret-blink 1.06s steps(1, end) infinite;
}

/* Solid while characters are moving, blinking while the phrase sits there to
   be read. A caret that blinks through its own typing reads as a fault. */
.headline.is-typing .headline-caret {
  animation: none;
  opacity: 1;
}

@keyframes caret-blink {
  0%, 49% { opacity: 1; }
  50%, 100% { opacity: 0; }
}

/* The caret appears only once the script that drives it is running, so a
   blocked script leaves a finished sentence rather than a finished sentence
   with a cursor waiting after it forever. Reduced motion never gets here
   either, the script having stopped before it reached the headline. */
.headline.is-live .headline-caret { display: inline-block; }

.hero-lede {
  font-size: var(--step-1);
  max-width: var(--measure);
  color: var(--gray);
  margin-bottom: 1.75rem;
}

.hero-note {
  font-family: "Chivo Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.8rem;
  color: var(--gray);
  opacity: 0.75;
  margin-top: 0.9rem;
}

/* Scope */

/* The real thing, and the pair that proves the color reading */

.showcase {
  margin: clamp(2rem, 5vw, 3.5rem) 0 0;
}

.showcase-media {
  display: block;
  width: 100%;
  height: auto;
  border: 1px solid var(--line);
  border-radius: 10px;
  /* the source is a screen, so the corners have nothing to hide behind */
  background: var(--surface);
}

.showcase figcaption,
.pair figcaption {
  margin-top: 0.7rem;
  font-size: 0.82rem;
  line-height: 1.5;
  color: var(--gray);
}

.pair {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: clamp(1rem, 2.5vw, 1.75rem);
  margin-bottom: clamp(2rem, 5vw, 3rem);
}

.pair figure { margin: 0; }

.pair img {
  display: block;
  width: 100%;
  height: auto;
  border: 1px solid var(--line);
  border-radius: 8px;
}

/* side by side is the whole argument, so they only stack when there is genuinely
   no room, and a reader who has to scroll between them is comparing memory */
@media (max-width: 720px) {
  .pair { grid-template-columns: 1fr; }
}

/* Saying what the preview is before anyone judges the product by it */
.scope-intro {
  margin-top: clamp(2.5rem, 6vw, 4.5rem);
  max-width: var(--measure);
}

.scope-intro h2 {
  font-size: var(--step-2);
  margin-block: 0.5rem 0.75rem;
}

.scope-intro p:last-child {
  color: var(--gray);
  font-size: 0.95rem;
}

.scope {
  margin-top: clamp(1rem, 2.5vw, 1.5rem);
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--surface);
  overflow: hidden;
}

.scope-canvas {
  display: block;
  width: 100%;
  height: clamp(220px, 34vw, 380px);
  /* the old page ground, kept as the well the trace sits in: a shade below
     the new one, so the scope reads as recessed rather than as a panel */
  background: #0E1013;
}

.scope-bar {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  padding: 0.7rem;
  border-top: 1px solid var(--line);
  background: var(--raised);
}

.mode {
  flex: 1 1 auto;
  padding: 0.5rem 0.7rem;
  border-radius: 5px;
  border: 1px solid transparent;
  background: transparent;
  color: var(--gray);
  font: inherit;
  font-size: 0.85rem;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
  transition: background-color 120ms ease, color 120ms ease;
}
.mode:hover { color: var(--bright); background: var(--raised); }
.mode[aria-pressed="true"] { background: var(--amber); color: var(--ink); }

.scope-caption {
  padding: 0 0.85rem 0.85rem;
  font-size: 0.9rem;
  color: var(--gray);
  background: var(--raised);
  margin: 0;
}

/* Sections */

section { padding-block: clamp(3rem, 8vw, 5.5rem); }

.section-head { max-width: var(--measure); margin-bottom: 2.5rem; }
.section-head h2 { font-size: var(--step-3); margin-block: 0.5rem 1rem; }
.section-head p { font-size: var(--step-1); }

/*
 * Rules
 *
 * A line. They were sawtooths generated from the mark, drawn at a wider sweep
 * with one of them carrying the K, and they went when the wave did: the icon
 * is the letter alone now, and a page ruled with waveforms was pointing at a
 * drawing that no longer exists.
 */

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

/*
 * The rule under the hero
 *
 * Every other rule sits between two sections and takes their padding on both
 * sides, so it lands centred in the gap. This one has the hero above it, which
 * is not a section and ends flush at the bottom of the scope, and so it had
 * nothing above it and a section's worth of room below. It read as a border
 * welded to the demo panel rather than as a divider between two things.
 *
 * Matching the section padding is what makes it symmetric, so the same clamp.
 */

.rule-hero { margin-top: clamp(3rem, 8vw, 5.5rem); }

/* Specs */

.specs {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  gap: 1px;
  background: var(--line);
  border: 1px solid var(--line);
  border-radius: 10px;
  overflow: hidden;
}

.spec { background: var(--surface); padding: 1.5rem 1.35rem; }

.spec dt {
  font-family: "Chivo Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.7rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--amber-dim);
  margin-bottom: 0.7rem;
}

.spec dd {
  margin: 0;
  color: var(--bright);
  font-size: var(--step-2);
  font-weight: 900;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
  line-height: 1.1;
}

.spec .qualifier {
  display: block;
  margin-top: 0.5rem;
  font-size: 0.85rem;
  font-weight: 400;
  color: var(--gray);
  letter-spacing: 0;
}

.footnote {
  margin-top: 1.25rem;
  font-size: 0.85rem;
  color: var(--gray);
  opacity: 0.8;
  max-width: var(--measure);
}

/* Prose columns */

.columns {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
  gap: clamp(1.5rem, 4vw, 3rem);
}

.column h3 { font-size: var(--step-1); margin-bottom: 0.6rem; }

/* Privacy call-out */

.privacy {
  border-left: 2px solid var(--amber);
  padding-left: clamp(1.25rem, 3vw, 2rem);
  max-width: var(--measure);
}
.privacy h2 { font-size: var(--step-3); margin-bottom: 1rem; }
.privacy p + p { margin-top: 1rem; }

/* Price */

/* anchors land with room above them, since a heading flush against the top
   of the window reads as the top of the page rather than as a jump. sticky bar
   is now part of that top, and an anchor that ignores it lands underneath the
   header instead of below it */
.scope-intro, #pricing { scroll-margin-top: 2rem; }

.price-card {
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--surface);
  padding: clamp(1.75rem, 4vw, 2.75rem);
  display: grid;
  gap: 1.5rem;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  align-items: center;
}

/* the word this page is about, set in the alphabet the app is drawn in */
.price-figure {
  margin: 0;
  line-height: 0;
}

.price-figure img {
  height: clamp(2.75rem, 7vw, 4.5rem);
  width: auto;
  max-width: 100%;
}

.price-terms { margin-top: 0.6rem; }

.checklist { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.5rem; }
.checklist li { padding-left: 1.4rem; position: relative; }
.checklist li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.62em;
  width: 0.6rem;
  height: 1px;
  background: var(--amber);
}

/*
 * Document pages
 *
 * Terms, refunds, privacy, and the download page. These are read rather than
 * scanned, so they run at a single measure with generous leading, and they use
 * the same tokens as everything else.
 */

.page-head { padding-block: clamp(2rem, 6vw, 3.5rem) 1.5rem; }
.page-head h1 { font-size: var(--step-3); margin-block: 0.6rem 0.9rem; }
.page-head p { font-size: var(--step-1); max-width: var(--measure); }

.prose { padding-bottom: clamp(3rem, 8vw, 5rem); max-width: var(--measure); }
.prose h2 { font-size: var(--step-1); margin-block: 2.5rem 0.75rem; }
.prose h2:first-child { margin-top: 0; }
.prose p { margin-bottom: 1rem; }
.prose ul, .prose ol { margin: 0 0 1rem; padding-left: 1.25rem; }
.prose li { margin-bottom: 0.5rem; }
.prose strong { color: var(--bright); font-weight: 600; }

.updated {
  font-family: "Chivo Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.75rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--gray);
  opacity: 0.7;
}

/* Requirements block on the download page */

.requirements {
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--surface);
  padding: 1.5rem;
  margin-bottom: 2rem;
}

.requirements dl {
  margin: 0;
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.6rem 1.5rem;
}

.requirements dt {
  font-family: "Chivo Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.72rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--amber-dim);
  padding-top: 0.25rem;
}

.requirements dd { margin: 0; color: var(--bright); }

/* Notice, for anything the reader needs before they click */

.notice {
  border: 1px solid var(--amber-dim);
  border-radius: 8px;
  background: rgba(var(--amber-rgb), 0.06);
  padding: 1.1rem 1.25rem;
  margin-bottom: 2rem;
}
.notice p + p { margin-top: 0.75rem; }

/* Steps, where order genuinely matters */

.steps { counter-reset: step; list-style: none; margin: 0; padding: 0; }
.steps li {
  counter-increment: step;
  position: relative;
  padding-left: 2.5rem;
  margin-bottom: 1.25rem;
}
.steps li::before {
  content: counter(step);
  position: absolute;
  left: 0;
  top: 0.1em;
  width: 1.6rem;
  height: 1.6rem;
  display: grid;
  place-items: center;
  border-radius: 50%;
  border: 1px solid var(--amber-dim);
  color: var(--amber);
  font-family: "Chivo Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.75rem;
}

/* Footer */

footer {
  border-top: 1px solid var(--line);
  padding-block: 2.5rem 3.5rem;
  font-size: 0.9rem;
}

.footer-inner {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem 2rem;
  justify-content: space-between;
  align-items: baseline;
}

.footer-links { display: flex; flex-wrap: wrap; gap: 1.25rem; }
.footer-links a { color: var(--gray); text-decoration: none; }
.footer-links a:hover { color: var(--bright); text-decoration: underline; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

/*
 * Margin overlay
 *
 * Off unless the url asks for it: add #grid to any page.
 *
 * No JavaScript, which is not a preference. This site sends
 * script-src 'self' with no unsafe-inline, so the sixty byte inline script that
 * did this before was blocked from the moment it deployed, silently, and the
 * check that it had shipped only ever proved the tag was in the html. Loosening
 * the policy for a debug tool would be the wrong way round on a site that sends
 * default-src 'none'.
 *
 * So the fragment does it. #grid targets an empty element that is never drawn,
 * and :has lets the page react to its own url. Costs a reader nothing at all:
 * no request, no script, and one element that renders nothing.
 */

#grid { display: none; }

html:has(#grid:target) {
  --grid-gutter: rgb(255 179 64 / 0.22);
  --grid-edge: rgb(255 179 64 / 0.65);
}

html:has(#grid:target) .wrap {
  background-image: linear-gradient(
    to right,
    var(--grid-gutter) 0 var(--gutter),
    transparent var(--gutter) calc(100% - var(--gutter)),
    var(--grid-gutter) calc(100% - var(--gutter)) 100%
  );

  /* the inner edges, which is where every box should agree */
  box-shadow:
    inset var(--gutter) 0 0 -1px var(--grid-edge),
    inset calc(-1 * var(--gutter)) 0 0 -1px var(--grid-edge);
}
