/* ==========================================================================
   PURCHASE — OWN THE ALBUM
   Aug 20 2026. Owns the three edition cards on purchase.html and nothing else.
   Roughing-in pass: real and working, not final.

   WHY A FILE AND NOT AN INLINE <style>. Same call css/about-feature.css made and
   for the same reason: this block has a JS partner (js/purchase-checkout.js), and
   a feature's stylesheet and its script should be findable in the same two places
   every other feature on this site is. Everything below is `ks-` prefixed and
   used on exactly one page. If a second page ever needs one of these, extract it
   then — do NOT push it into components.css, which six pages load.

   IT LOADS LAST, after components.css. .ks-edition__cta is a .btn and restyles
   it — full width, centred — and that is a same-specificity fight won by source
   order alone. purchase.html's <head> carries the same warning. The height is
   NO LONGER part of that fight: the 44px min-height moved into components.css
   on Aug 21 2026 and is inherited here rather than overridden.

   NO OPAQUE SURFACE ANYWHERE IN THIS FILE. The nebula and two of the four star
   bands are pseudo-elements of <html> and <main> (css/star-bg.css). A solid panel
   behind the cards would delete the sky for the entire height of the page, which
   on this page is the entire page. The cards are translucent scrims — the
   measured numbers are at .ks-edition below.
   ========================================================================== */

/* ---- Section shell -------------------------------------------------------
   .section already gives --space-24 top padding from base.css. The extra 3rem is
   the same one-off merch.html applies inline, for the same reason: the fixed nav
   is 92px expanded (--nav-h-max) and .section's padding alone puts an h1 under
   it on first paint, before js/nav.js has collapsed the bar. */
.ks-own { padding-top: calc(var(--space-24) + 3rem); }

/* ---- The album masthead --------------------------------------------------
   THREE PAGES USE THIS BLOCK: purchase.html and merch.html, which are the two
   shopfronts and get it at full size, and purchase-success.html, which gets the
   --sm variant at the foot of this section. It lives here rather than being
   copied into each because merch.html's stylesheet banner already settled the
   question for the cards below it — two shopfronts sharing one definition is
   the whole point, and a second copy is how they drift. Same argument, same
   file, one more component.

   THE COVER IS SHOWN ONCE, ABOVE THE TIERS, NOT INSIDE THEM. The full argument
   is in the comment above .ks-own__album in purchase.html; the short form is
   that the artwork identifies the ALBUM and the three cards distinguish the
   EDITIONS, so putting it in each card repeats the one thing all three share
   and spends the accumulated emphasis .ks-edition--deluxe is built out of.

   400px IS THE COVER COLUMN AND IT IS A max, NOT A WIDTH. minmax(0, 400px)
   rather than a bare 400px, and the difference is a safety net rather than
   something you can see: a bare fixed track carries a min-content floor it can
   never go under, so if the copy column's own min-content ever grew past what is
   left, the grid would overflow the container instead of rebalancing. With
   minmax(0, …) the cover is the track that yields.

   MEASURED, IT NEVER ACTUALLY YIELDS above the 880px breakpoint, and that is the
   correct outcome, not a sign the minmax is dead weight. Track widths and the
   resulting copy measure, every step from the container width down to the
   breakpoint (viewport → cover / copy):

       1440 → 400 / 784      1000 → 400 / 512
       1232 → 400 / 736       940 → 400 / 454
       1100 → 400 / 608       881 → 400 / 398   <- last width before it stacks

   The copy column takes the whole loss, bottoming at 398px one pixel above the
   breakpoint, and documentElement.scrollWidth === window.innerWidth at every one
   of those widths. The cover holding its size is what keeps the artwork readable
   as artwork rather than as a thumbnail.

   THE COPY'S MEASURE IS UNCHANGED BY THE COVER'S ARRIVAL, which is the one thing
   that had to be true here. .ks-own__head's 72ch cap measures 734px in Plex Sans,
   and 734 < the 784px track at 1440 — so the cap binds first and the head sets to
   exactly the 734px it set to before, when it was a full-width block under the
   same cap. That means the lede's three-line break survives untouched and the
   line-width table recorded at .ks-own__lede below did not have to be re-run.
   Confirmed: 3 lines at 1440, and still 3 at 950 and at 390.

   align-items: start, NOT center. Centred against a 400px square, the label and
   h1 drop ~90px below the top of the artwork and the section loses its top
   edge — the first thing under the nav becomes a picture with nothing beside
   it. Started, the label sits on the cover's top edge and the two columns share
   one baseline to read from. */
.ks-own__album {
  display: grid;
  grid-template-columns: minmax(0, 400px) 1fr;
  gap: clamp(28px, 4vw, 48px);
  align-items: start;
  margin-bottom: clamp(40px, 6vh, 72px);
}

/* 880px IS css/about-feature.css's NUMBER AND IT IS THE RIGHT ONE TO BORROW.
   That file uses 880 for exactly this situation — a figure standing beside
   prose. The 1024px boundary used by .ks-editions below is for the three-card
   row and does not transfer: two columns survive narrower than three do.

   THE COVER IS CAPPED AT 320px WHEN STACKED, not set to 100%. A full-width
   square on a 390px phone is 390px tall and pushes the h1 to the fold — the
   reader meets the artwork and has to scroll to find out what the page is.
   Capped, the cover, the label and the h1 all land above it. */
/* ---- The receipt variant --------------------------------------------------
   purchase-success.html ONLY. That page is a receipt, not a shopfront: the
   reader has already decided, and the cover's job there is to confirm WHAT was
   bought, not to sell it. At the shopfront's 400px it outweighs TRANSMISSION
   RECEIVED, which is Anton at --fs-hero and is meant to be the first thing read
   on that page. 240px keeps the artwork an object and the headline the headline.

   IT IS A COLUMN CAP, NOT AN IMAGE WIDTH — the cover still fills its track, so
   the hairline, the strip and the scrim keep working exactly as they do at
   400px and the contrast measurement does not have to be redone for a size.

   >> THIS RULE MUST STAY ABOVE THE @media BLOCK BELOW, AND ITS SELECTOR MUST
   >> STAY IN THAT BLOCK'S LIST. <<
   Both halves, or the phone layout breaks. .ks-own__album--sm is (0,1,0) —
   exactly the same specificity as the .ks-own__album in the media block, and a
   media query adds nothing to specificity — so the two can only be separated by
   SOURCE ORDER. Written after the media block, as this shipped for one pass,
   the variant wins at every width and the phone never stacks: measured at
   390x844, purchase-success.html held two columns and squeezed them to 85px and
   233px, i.e. an 85px cover, and there is no error and nothing in the console to
   say why. css/star-bg.css records this identical trap at length for its page
   blocks; this is the same one, in a different file.

   The selector is ALSO added to the media block's list rather than relying on
   source order alone. Belt and braces: source order is invisible in devtools
   until you go looking, and naming the variant in the block that overrides it
   makes the relationship greppable. */
.ks-own__album--sm { grid-template-columns: minmax(0, 240px) 1fr; }

@media (max-width: 880px) {
  .ks-own__album,
  .ks-own__album--sm { grid-template-columns: 1fr; gap: var(--space-6); }
  .ks-own__cover { max-width: 320px; }
}

/* THE HAIRLINE IS STRUCTURAL, NOT DECORATIVE. The cover's outer edge is
   near-black on all four sides and this page's backdrop is a star field at
   close to the same value, so with no border the artwork has no boundary at
   all — the graveyard simply dissolves into the sky and the image reads as a
   bleed rather than as a record sleeve. 0.14 is the SAME alpha .ks-edition
   carries, deliberately: the cover and the cards then read as one family of
   filed objects rather than as a photo sitting next to some cards.

   NO RADIUS AND NO SHADOW. --radius-sharp is the site default (tokens.css:
   severe/institutional, not soft) and the cards only take a shadow on hover.
   A rounded, drop-shadowed cover is the storefront product tile this page was
   told not to be.

   line-height: 0 ON THE FIGURE kills the inline-descender gap under the img
   that would otherwise leave a ~5px sliver of page background inside the
   border along the bottom edge only — a frame that looks 1px heavier at the
   foot than at the sides. display:block on the img is the other half of it. */
.ks-own__cover {
  margin: 0;
  line-height: 0;
}
.ks-own__cover img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 1;
  border: 1px solid rgba(214, 213, 208, 0.14);
  border-bottom: 0;
  border-radius: var(--radius-sharp);
}

/* THE CAPTION IS A STRIP ATTACHED TO THE SLEEVE, NOT A LINE FLOATING UNDER IT,
   AND THE REASON IS MEASURED, NOT STYLISTIC. The first version was exactly that
   floating line — --text-muted on bare sky, 12px, sitting eight pixels below the
   frame. Measured 2026-08-21 the way this file's foot block documents (animations
   pinned to their 46% keyframe, the text set to `color: transparent`, contrast of
   #78888F against every backdrop pixel in the caption's own box):

                        worst 1%   worst 5%   median
       1440x900           1.55       1.72      5.06
        950x900           1.54       1.71      4.18
        390x844           2.58       3.29      4.72

   Nowhere near AA, and for the same reason the streaming line in the foot was
   not: this strip sits directly beneath a big block of near-black artwork, which
   is precisely where the eye has been dark-adapted, and the star field
   immediately under the frame is some of the brightest sky on the page. Colour
   cannot fix it — the absolute worst backdrop pixel computes to 1.05:1 against
   --text-muted, i.e. a star at the same luminance as the type.

   So it takes the treatment the foot took: a translucent wash. Attaching it to
   the frame rather than washing a floating line is what stops it reading as a
   grey chip stuck under a picture — the image drops its bottom border, the strip
   carries a full one, and the two share a single hairline. What lands is a
   sleeve with a filing label on it, which is the language of the three cards
   below and of merch.html's catalogue.

   0.94, DEEPER THAN THE FOOT'S 0.88, AND THE MARGIN IS THE WHOLE ARGUMENT.
   Swept across all three pages that mount this strip, at the three viewports,
   with the sky pinned to its 46% keyframe. worst 1% / worst 5% against
   --text-muted; AA needs 4.5:

       alpha        none         0.66        0.80        0.88        0.94
       purchase 1440  1.45/3.17   2.35/4.82   3.69/5.19   4.52/5.33   5.12/5.46
       purchase  950  1.06/1.34   3.03/3.83   4.22/4.68   4.82/5.07   5.24/5.34
       purchase  390  1.43/2.40   4.02/4.62   4.77/5.08   5.12/5.27   5.37/5.43
       merch    1440  1.41/3.19   2.43/4.84   3.74/5.19   4.57/5.33   5.15/5.46
       merch     950  1.06/1.33   3.11/3.84   4.26/4.68   4.82/5.07   5.24/5.34
       merch     390  1.55/2.42   4.12/4.63   4.82/5.08   5.13/5.28   5.37/5.43
       success  1440  3.19/5.37   4.89/5.51   5.22/5.55   5.34/5.53   5.46/5.56
       success   950  2.62/3.33   4.68/4.97   5.12/5.26   5.30/5.36   5.43/5.46
       success   390  1.44/2.40   4.02/4.62   4.77/5.08   5.12/5.27   5.37/5.43

   The foot's 0.88 technically passes — its worst cell is 4.52 — but that is a
   margin of two hundredths of a ratio point, and it is not a margin worth
   shipping: every figure above is ONE PINNED KEYFRAME of a sky whose twinkle and
   cloud layers are not phase-locked (the note at the foot of this file says so),
   and none of it has been seen on a real handset. 0.94 moves the worst cell to
   5.12. Six points of alpha for six tenths of a ratio point is the right trade
   here and it is the only place in this file that departs from the foot's value.

   THE THREE PAGES AGREE WITH EACH OTHER, which is worth stating because a first
   pass reported that they did not — merch.html appeared to fail at 390 with 3.64
   while purchase.html passed. That was a measurement artifact, not a difference
   between the pages; see the settle-gate trap below. Read across the rows above:
   the two shopfronts are within 0.05 of each other at every alpha and every
   width, exactly as two pages sharing one component and one sky should be.

   TWO WAYS TO MIS-MEASURE THIS STRIP, BOTH OF WHICH HAPPENED HERE.

   1. MEASURE THE CONTENT BOX, NOT THE BORDER BOX. The strip is ~400x34 with a
      1px border, so the border is ~5% of its pixels — clip the element's full
      rect and the worst-5% tail IS the border, which sits at 4.36:1 against
      --text-muted no matter what the background alpha is. That is what a first
      pass reported: a flat 4.36 at 0.88, 0.92, 0.94 and 0.96, a curve that does
      not move because it was never measuring the backdrop. No glyph ever
      overlaps the hairline. Inset by the border AND the padding before sampling.

   2. LET THE PAGE SETTLE, AND SCROLL THE CAPTION ITSELF INTO VIEW. The sky
      layers are position:fixed, so what sits behind this strip is a function of
      scroll position. Centring a nearby heading instead pushes the strip
      off-screen on a phone and the clip rect leaves the viewport entirely.
      Worse, on a tall page — merch.html is 4819px — a screenshot taken straight
      after scrollIntoView() catches the page mid-settle and samples a different
      band of sky: that is where the phantom 3.64, and a 2.68 that reproduced
      nowhere, came from. Await document.fonts.ready, scroll the caption, wait,
      THEN pin the animations. Assert the caption's rect is fully inside the
      viewport, and take two samples and require them to agree before believing
      either.

   Both are the same family as the fixed-layer screenshot trap recorded at
   .ks-edition above: on this site, a contrast number you did not fight for is
   probably measuring the wrong pixels.

   MEASURE THE CONTENT BOX, NOT THE BORDER BOX, OR THE NUMBER IS THE HAIRLINE'S
   AND NOT THE TEXT'S. This strip is about 400x34 with a 1px border, so the
   border is ~5% of its pixels — clip the element's full rect and the worst-5%
   tail IS the border, which sits at 4.36:1 against --text-muted no matter what
   the background alpha is. That is what a first pass here reported: a flat 4.36
   at 0.88, 0.92, 0.94 and 0.96, a curve that does not move because it was never
   measuring the backdrop. No glyph ever overlaps the hairline. Inset by the
   border and the padding before sampling. Same family of trap as the
   fixed-layer screenshot one recorded at .ks-edition above.

   .readout already sets Plex Mono / 12px / 0.08em / uppercase in base.css, so
   only the wash, the frame, the colour and the spacing belong here. --text-muted
   rather than secondary: the line restates what the h1 and the card titles
   already say, and it must not compete with the DIRECT PURCHASE label sitting
   directly across from it.
   line-height is restored because the figure zeroed it for the image. */
.ks-own__cover-cap {
  margin: 0;
  padding: var(--space-3) var(--space-4);
  border: 1px solid rgba(214, 213, 208, 0.14);
  border-radius: var(--radius-sharp);
  background: rgba(3, 4, 15, 0.94);
  line-height: 1.5;
  color: var(--text-muted);
}

/* ---- Section head -------------------------------------------------------- */
/* The bottom margin now lives on .ks-own__album, which is what actually sits
   above the card row — leaving it here would have added a second gap inside
   the two-column block and dropped the copy column out of alignment with the
   top of the cover. */
.ks-own__head { max-width: 72ch; }
.ks-own__head .label { display: block; margin-bottom: var(--space-3); }

/* The lede is the line that has to land. Set in the DISPLAY face rather than
   body — this is the one sentence on the page doing the album's argument, and
   at 65ch of Plex Sans it reads as a subtitle instead.

   30ch IS A LINE-BREAK DECISION, NOT A MEASURE. The sentence is a parallel
   pair — "Streaming gives you access. Ownership gives you the transmission." —
   and the break wants to fall between the clauses. Measured at 1440x900, line
   widths in px:
       30ch (474px cap):  420 / 359 / 210   <- clause 1 gets its own line
       38ch (600px cap):  583 / 410         <- breaks after "OWNERSHIP"
       no cap (734px):    same two lines as 38ch
   So the wider settings are FEWER lines and a worse read: they hyphenate the
   second clause's subject onto the first line and the pairing disappears.
   Three lines is the right answer here. If the copy ever changes, re-measure —
   this number means nothing on a different sentence. */
.ks-own__lede {
  margin: var(--space-6) 0 var(--space-4);
  max-width: 30ch;
  font-family: var(--font-display);
  font-weight: var(--display-wght);
  font-stretch: var(--display-stretch);
  font-variation-settings: "wdth" var(--display-wdth), "wght" var(--display-wght);
  text-transform: uppercase;
  font-size: clamp(1.35rem, 2.6vw, 2.1rem);
  line-height: 1.06;
  letter-spacing: var(--tracking-heading);
  color: var(--text-primary);
}
.ks-own__sub { max-width: 68ch; color: var(--text-secondary); }

/* ---- The grid ------------------------------------------------------------
   THREE UP, THEN STRAIGHT TO ONE. There is no two-column step, deliberately:
   three cards in a two-column grid leave an orphan, and the orphan is Artifact —
   the one tier that is not even purchasable yet. It would read as the odd one
   out for a reason that has nothing to do with its state.

   1024px IS THE SITE'S OWN NUMBER, not a new one: base.css collapses .grid--3 at
   exactly 1024px. The 880px boundary css/about-feature.css uses is for floated
   figures beside prose and does not transfer. Measured at 900px wide with the
   3-up forced back on: each card is 258px border-box with a 208px contents
   column, the Deluxe title breaks onto two lines, and "Exclusive bonus material
   not available on streaming platforms" takes two lines where the stacked 640px
   card sets it in one. Not catastrophic — but 208px is a 30-character column,
   and three of them side by side is a worse read than one card at a time.

   THE DELUXE COLUMN IS WIDER BY 12%, the smallest step that is visible without
   reading as a different component. Measured at 1440x900 inside the 1232px
   container: 379 / 425 / 380 border-box, against a flat 395 each if the columns
   were 1fr. All three cards are 706px tall — they stretch, so the CTAs line up
   across a 6-item, an 8-item and a 5-item contents list. */
.ks-editions {
  display: grid;
  grid-template-columns: 1fr 1.12fr 1fr;
  gap: var(--grid-gap);
  align-items: stretch;
}
@media (max-width: 1024px) {
  .ks-editions { grid-template-columns: 1fr; max-width: 640px; }
}

/* ---- An edition card -----------------------------------------------------
   THE SCRIM IS 0.42 → 0.66 TOP TO BOTTOM, AND IT IS EARNING ITS KEEP. Measured
   2026-08-20 with the twinkle and cloud-glow animations pinned to their 46%
   keyframe (the peak), backdrop captured with the contents list set to
   `color: transparent`, contrast computed against --text-secondary #9DB2C0
   across every pixel of the list's box. Deluxe card, 1440x900:

                       worst 1%    worst 5%    median
       scrim on          5.21        7.06       8.90
       scrim removed     1.49        3.11       7.94

   and the same card at 390x844, where the phone crop puts the densest part of
   the nebula band directly behind it: 3.01 → 7.05 at the worst 1%, 5.65 → 8.22
   at the worst 5%. The Artifact card, whose list sits lower, runs 4.48 → 7.19
   (worst 1%) at 1440. So without the scrim the bottom of the range is well
   under AA's 4.5:1 and with it nothing in the card's own box is.

   HOW TO RE-RUN THIS, because the obvious way is wrong. A full-page screenshot
   paints position:fixed layers ONCE, at the top of the capture — clip a region
   below the fold out of one and you sample flat page background and conclude the
   sky is not there. Scroll the element into view and take a VIEWPORT screenshot
   clipped to its rect. That mistake cost two measurement passes here, and it is
   the same family of trap as the one css/about-feature.css records for shadows.

   Going to a flat opaque --bg-surface would also have fixed contrast, and cost
   the whole background — the one thing this page may not do. At 0.42 the star
   field is still plainly visible through the card; the sky is dimmed, not
   deleted.

   The gradient runs DARKER DOWNWARD because that is where the list is longest
   and the CTA sits; the head end stays lighter so the catalogue number and the
   rule above it still sit over visible sky.

   BORDER, NOT SHADOW, AND radius 0. --radius-sharp is the site default and the
   reason is in tokens.css: severe/institutional, not soft. A rounded card with a
   drop shadow is the exact SaaS pricing-table shape this page was told not to
   be. */
.ks-edition {
  display: flex;
  flex-direction: column;
  padding: var(--space-6) var(--space-6) var(--space-8);
  border: 1px solid rgba(214, 213, 208, 0.14);
  border-radius: var(--radius-sharp);
  background: linear-gradient(180deg, rgba(3, 4, 15, 0.42) 0%, rgba(3, 4, 15, 0.66) 100%);
  transition: border-color var(--motion-base) var(--ease-standard),
              transform var(--motion-base) var(--ease-standard),
              box-shadow var(--motion-base) var(--ease-standard);
}

/* The rule above the catalogue number. A ::before on the card rather than a
   border-top on the number, so it can be inset from the padding and read as a
   filing mark rather than as a second frame inside the first. */
.ks-edition::before {
  content: '';
  display: block;
  height: 1px;
  margin-bottom: var(--space-4);
  background: rgba(214, 213, 208, 0.22);
}

/* HOVER IS FOUR SMALL THINGS, NONE OF THEM A ZOOM. The border comes up, the card
   lifts 2px, a very low atmospheric glow appears under it, and the catalogue
   number slides 3px right (see .ks-edition__cat). 2px is deliberate — at 4px the
   three cards visibly disagree about their baseline when the pointer crosses
   between them, because the gap is only 24px.
   :focus-within carries the same treatment so a keyboard reader gets the same
   card response a mouse reader does; the button's own --focus-ring outline comes
   from base.css on top of it. */
.ks-edition:hover,
.ks-edition:focus-within {
  border-color: rgba(214, 213, 208, 0.34);
  transform: translateY(-2px);
  box-shadow: 0 10px 34px rgba(3, 4, 15, 0.55);
}

/* THE CENTREPIECE, BY ACCUMULATION. A wider column (above), a lit hairline, a
   marginally deeper scrim, and the word RECOMMENDED. No fill, no ribbon, no
   glow: four quiet differences read as emphasis, one loud one reads as an ad. */
.ks-edition--deluxe {
  border-color: rgba(214, 213, 208, 0.30);
  background: linear-gradient(180deg, rgba(3, 4, 15, 0.52) 0%, rgba(3, 4, 15, 0.74) 100%);
}
.ks-edition--deluxe::before { background: rgba(214, 213, 208, 0.40); }
.ks-edition--deluxe:hover,
.ks-edition--deluxe:focus-within { border-color: rgba(214, 213, 208, 0.50); }

/* ---- Card head ----------------------------------------------------------- */
/* .readout already sets Plex Mono / 12px / 0.08em / uppercase in base.css. Only
   the colour, the spacing and the hover shift belong here. */
.ks-edition__cat {
  margin: 0 0 var(--space-4);
  color: var(--text-secondary);
  transition: transform var(--motion-base) var(--ease-standard),
              color var(--motion-base) var(--ease-standard);
}
.ks-edition:hover .ks-edition__cat,
.ks-edition:focus-within .ks-edition__cat {
  transform: translateX(3px);
  color: var(--text-primary);
}
.ks-edition--deluxe .ks-edition__cat { color: var(--color-crimson-lit); }

/* RECOMMENDED. One of the page's two crimson uses (tokens.css budgets roughly
   two a page); the other is the 02 catalogue number directly above it. It is
   real text in the flow — nothing here is carried by colour alone, so it
   survives a monochrome display and a screen reader reads it in document order,
   immediately before the tier's own <h2>. */
.ks-edition__flag {
  margin: calc(var(--space-4) * -1) 0 var(--space-4);
  padding-bottom: var(--space-3);
  border-bottom: 1px solid rgba(161, 51, 62, 0.45);
  color: var(--color-crimson-lit);
}

/* The h2 would otherwise inherit --fs-h1 from base.css (up to 4rem), which at
   card width sets "RISE UP — DELUXE TRANSMISSION" over four lines. --fs-h3 with
   the heading tracking is the size the rest of the site uses for card titles. */
.ks-edition__title {
  margin: 0 0 var(--space-4);
  font-size: var(--fs-h3);
  letter-spacing: var(--tracking-heading);
  line-height: var(--lh-heading);
  color: var(--text-primary);
}

.ks-edition__price {
  margin: 0 0 var(--space-6);
  padding-bottom: var(--space-4);
  border-bottom: 1px solid rgba(214, 213, 208, 0.14);
  font-family: var(--font-display);
  font-weight: var(--display-wght);
  font-stretch: var(--display-stretch);
  font-variation-settings: "wdth" var(--display-wdth), "wght" var(--display-wght);
  font-size: 2.25rem;
  line-height: 1;
  letter-spacing: var(--tracking-display);
  color: var(--text-primary);
}
/* "FROM" is a qualifier, not part of the number — mono and small so the eye
   still lands on $45 first. Baseline-aligned by hand: the display face's cap
   height at 2.25rem sits well above the mono's, so vertical-align does the work
   that a flex row would have done less predictably at three different sizes. */
.ks-edition__from {
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: var(--fs-mono);
  font-stretch: normal;
  font-variation-settings: normal;
  letter-spacing: var(--tracking-mono);
  text-transform: uppercase;
  color: var(--text-muted);
  vertical-align: 0.55em;
  margin-right: var(--space-1);
}

/* ---- Contents ------------------------------------------------------------ */
.ks-edition__contents-label { display: block; margin-bottom: var(--space-3); }

/* NO BULLET GLYPHS AND NO CHECK MARKS. A tick per line is the ecommerce tell
   this page was told to avoid; a disc bullet is a blog. Each row gets a 10px
   hairline instead — the same mark the rest of the site uses to separate
   technical rows, at the same weight as the card's own rules. */
.ks-edition__contents {
  list-style: none;
  margin: 0 0 var(--space-8);
  padding: 0;
  color: var(--text-secondary);
  font-size: var(--fs-caption);
  line-height: 1.55;
}
.ks-edition__contents li {
  position: relative;
  padding-left: var(--space-6);
  margin-bottom: var(--space-3);
}
.ks-edition__contents li:last-child { margin-bottom: 0; }
.ks-edition__contents li::before {
  content: '';
  position: absolute;
  left: 0;
  /* 0.775em ≈ the optical centre of a 1.55 line box at this size — a 1px rule
     set on the text baseline reads as an underscore, not as a marker. */
  top: 0.775em;
  width: 10px;
  height: 1px;
  background: rgba(157, 178, 192, 0.55);
}

/* ---- Foot ---------------------------------------------------------------- */
/* margin-top:auto is what keeps the three CTAs on one line across cards of very
   different list lengths (Digital has 6 rows, Deluxe 8, Artifact 5). Without it
   each button sits under its own list and the row reads as three unrelated
   blocks. The card is the flex column that makes this work — see .ks-edition. */
.ks-edition__foot { margin-top: auto; }

/* THE 44px PIN HAS MOVED TO components.css (Aug 21 2026), which is where the
   note that used to sit here said it belonged. .btn now carries min-height:44px
   site-wide, so these three CTAs inherit it and the local copy is gone — one
   source of truth, and a change to that number now reaches this page too.
   Re-measured after the move: all three still 44px at 1440 and at 390.

   The rest of this rule stays local and is not about height at all: full width
   and centred text are what make three buttons of different label lengths read
   as one row of cards. */
.ks-edition__cta {
  display: flex;
  width: 100%;
  justify-content: center;
  text-align: center;
}
.ks-edition__note {
  margin: var(--space-3) 0 0;
  font-family: var(--font-mono);
  font-size: var(--fs-mono);
  letter-spacing: var(--tracking-mono);
  text-transform: uppercase;
  color: var(--text-muted);
}

/* ---- The artifact tier's two states --------------------------------------
   ONE LEVER, BECAUSE THE MODULE ONLY PULLS ONE. js/purchase-checkout.js's
   syncStatus() does precisely this and nothing else:

       el.hidden = (edition.status !== 'coming-soon')   // el = [data-ks-edition-status]

   It never touches the button. So the button hangs off the same attribute:
   while the status block is showing, the CTA is not rendered; the instant the
   module hides the status block the CTA appears. The static default is the
   status block VISIBLE, so the page is honest with JS switched off and no
   inventory is implied — merch.html's STANDBY precedent.

   :has() IS DOING REAL WORK HERE and there is no non-:has() version of this
   that keeps the no-JS default correct. Chromium/Safari/Firefox have all
   shipped it; the site already assumes a modern engine (container-relative
   clamp(), :focus-within, aspect-ratio are all in use).

   THE CTA IS HIDDEN, NOT DISABLED. `disabled` removes the button from the tab
   order, which would leave a keyboard reader on a card with no reachable
   control at all. The JOIN THE RELEASE LIST anchor is a real focusable link and
   is the honest route out of this state.

   LAYOUT DOES NOT JUMP BETWEEN THE STATES. Measured at 1440x900 by toggling
   [data-ks-edition-status].hidden in the console — the numbers are in the
   verification note at the foot of this file. */
.ks-edition__foot:has(> .ks-edition__status:not([hidden])) .ks-edition__cta[data-ks-purchase] { display: none; }
.ks-edition__status:empty { display: none; }

.ks-edition__status-line {
  margin: 0 0 var(--space-3);
  color: var(--text-secondary);
  /* Not crimson. The page's crimson budget is already spent on RECOMMENDED and
     the 02 catalogue number, and --color-crimson-lit tops out at 2.97:1 on flat
     #03040F — a waiting notice is real content and cannot be set in a colour
     that fails AA before the sky is even involved. css/about-feature.css records
     the same finding at length. */
}

/* ---- The refusal message -------------------------------------------------
   purchase.html supplies one of these per card, shipped `hidden`. It exists
   ONLY so js/purchase-checkout.js's message() finds a container: without it
   that function BUILDS one, gives it .empty-state, and inserts it after the
   button — a dashed box appearing inside a card, at a width and a rhythm nobody
   designed. Supplying the node costs four lines of CSS and takes the decision
   back.
   Left-bordered rather than boxed: this is a response to something the reader
   just pressed, and a second framed rectangle inside the card frame reads as a
   nested panel. */
.ks-edition__message {
  margin: var(--space-3) 0 0;
  padding-left: var(--space-3);
  border-left: 1px solid rgba(161, 51, 62, 0.55);
  font-size: var(--fs-caption);
  line-height: 1.5;
  color: var(--text-secondary);
}

/* ---- Section foot --------------------------------------------------------
   THIS BLOCK CARRIES A SCRIM AND THE FIRST DRAFT DID NOT. It sits directly over
   the brightest part of the nebula — at 1440x900 the band's core lands between
   the bottom of the cards and the site footer, which is exactly where these two
   paragraphs are. Measured there with the animations pinned to their 46% peak
   and the text set to `color: transparent`, contrast against the paragraph's own
   colour, no scrim:

       support paragraph, --text-secondary   worst 5%: 2.64   median 7.92
       streaming line,    --text-muted       worst 5%: 1.41   median 4.49

   Both fail, and the two-pass text-shadow does NOT rescue them: with the shadow
   removed as a control the same figures are 2.20 and 1.40 — the shadow is worth
   about 0.4 of a ratio point here, not the four points needed. Nor can colour
   fix it. The brightest backdrop pixels in the streaming line's box sit at the
   same luminance as --text-muted itself (ratio 1.0); --color-bone against those
   same pixels computes to 2.37:1, still under AA. The nebula core is genuinely
   as bright as the type.

   So the foot gets the same treatment the cards get: a translucent wash, bled to
   the section's padding so the text stays aligned to everything above it. The
   sky reads through it. After: 7.40 and 4.58 at the same worst-5% measure. Full
   before/after table is in the verification note at the foot of this file. */
.ks-own__foot {
  margin-top: clamp(40px, 6vh, 72px);
  /* NO HORIZONTAL BLEED. An earlier version pulled this block out to the
     container's padding edge, and the wash then ended 24px outside the card row
     on each side — a translucent band with hard vertical edges that lined up
     with nothing. Left as the plain content box, the wash starts and ends on
     exactly the same x as the cards above it (104 and 1336 at 1440x900), so it
     reads as the last row of the same table rather than as a panel. */
  padding-block: var(--space-8);
  border-top: 1px solid rgba(214, 213, 208, 0.14);
  /* 0.62 → 0.88, and the bottom stop is the one that was tuned. At 0.78 the
     support paragraph was fixed (worst 5% 2.64 → 7.08) but the STREAMING LINE,
     which is the last thing in the block and therefore sits at the deepest stop,
     still measured 4.17 at the worst 5% — just under AA, and only at 1440 where
     the nebula core lands there. 0.88 clears it. The head stop stays at 0.62:
     nothing at the top of this block is small enough to need more, and every
     point of alpha spent is sky removed. */
  background: linear-gradient(180deg, rgba(3, 4, 15, 0.62) 0%, rgba(3, 4, 15, 0.88) 100%);
}
.ks-own__support {
  max-width: 68ch;
  font-size: var(--fs-caption);
  color: var(--text-secondary);
}
/* THE STREAMING LINE IS THE QUIETEST THING ON THE PAGE, on instruction, and it
   stays a plain link rather than a .btn: a second button at the bottom competes
   with the three above it and turns the exit into a fourth offer. --text-muted
   (#78888F) is 5.56:1 on flat #03040F — comfortably AA at 14px, which is what
   lets it be this quiet. The anchor inside it is pulled UP to --text-secondary
   (9.30:1) rather than pushed down, so the one clickable word in the sentence is
   the brightest thing in it. */
.ks-own__stream {
  margin-top: var(--space-4);
  font-size: var(--fs-caption);
  color: var(--text-muted);
}
.ks-own__stream a { color: var(--text-secondary); text-decoration-color: rgba(157, 178, 192, 0.5); }
.ks-own__stream a:hover { color: var(--text-primary); }

/* ---- Legibility over the sky ---------------------------------------------
   The section HEAD sits on bare sky — no scrim under it, unlike the cards and
   (as of the block above) the foot. Same two-pass shadow css/about-feature.css
   and css/spine-bg.css use for small text over the lit nebula: the 1px pass
   gives the glyph a hard edge, the 3px pass kills the halo, and rgba(3,4,15)
   rather than #000 so it can never paint below the page.

   WHAT THE SHADOW IS ACTUALLY WORTH HERE, since this file now has the control
   measurement that most files quoting this pattern do not. Shadow on vs shadow
   off, same region, same pinned frame, worst-5% of the backdrop:
       support paragraph @1440   2.64  vs  2.20
       streaming line    @1440   1.41  vs  1.40
       support paragraph @390    4.86  vs  4.31
   That is roughly half a ratio point. It is worth keeping — it is free, and at
   the head, where the sky is dark, it is what stops a stray star sitting inside
   a letterform. It is NOT a substitute for a scrim where the nebula is bright,
   which is why the foot got one and this list did not grow to cover it.

   The card interiors are NOT in this list — they have the scrim instead, and
   doubling the two would thicken the contents list visibly at 14px. */
.ks-own__head h1, .ks-own__lede, .ks-own__sub, .ks-own__head .label,
.ks-own__support, .ks-own__stream {
  text-shadow: 0 0 3px rgba(3, 4, 15, 0.98), 0 1px 2px rgba(3, 4, 15, 0.92);
}

/* ---- Narrow --------------------------------------------------------------
   600px is the site's phone boundary — css/star-bg.css keys its whole phone
   block to it, so this file uses the same line rather than inventing a third.

   THE TYPE IS LEFT ALONE ON PURPOSE. Every heading and the lede are already on
   clamp()s from tokens.css and they land correctly at 390x844 with nothing added
   here: measured, the h1 sits at --fs-hero's 44px floor and sets "OWN THE ALBUM"
   on ONE line at 342px of card width, and the lede keeps its three-line break
   with the first clause on its own line. The only reason .ks-own__lede drops its
   30ch cap is that 30ch is WIDER than the phone viewport, so the cap is inert
   there and leaving it in would imply it was doing something.

   What does change is space, not size: --space-24 of section padding and
   --space-6 of card padding are desktop numbers, and at 390 they cost a screen
   and a half of scrolling before the first price. */
@media (max-width: 600px) {
  .ks-own { padding-top: calc(var(--space-16) + 2rem); }
  .ks-edition { padding: var(--space-4) var(--space-4) var(--space-6); }
  .ks-editions { gap: var(--space-4); }
  .ks-own__lede { max-width: none; }
}

@media (prefers-reduced-motion: reduce) {
  /* --motion-* already collapse to 0ms in tokens.css under this query, so the
     transitions are gone by the time this block runs. What is left is the
     TRANSFORMS themselves, which are instantaneous jumps rather than motion at
     0ms — a card that teleports 2px on hover is worse than one that does not
     move. The border and the glow are not motion and are kept: they are the
     hover affordance these users still need. */
  .ks-edition:hover, .ks-edition:focus-within { transform: none; }
  .ks-edition:hover .ks-edition__cat,
  .ks-edition:focus-within .ks-edition__cat { transform: none; }
}

/* ==========================================================================
   VERIFICATION NOTE — 2026-08-20, roughing-in pass.
   Chromium via Playwright against `python scripts/serve.py`, three viewports:
   1440x900, 900x900, 390x844. Everything below was observed, not expected.

   CLEAN: zero console errors, zero page errors, zero 4xx.
   NO HORIZONTAL OVERFLOW: documentElement.scrollWidth === window.innerWidth at
   all three widths (1440/1440, 900/900, 390/390).

   TAB ORDER reaches every control and every one shows the site focus ring
   (2px solid rgb(228,232,235), i.e. --focus-ring, from base.css):
     skip link (44px) → nav → OWN THE DIGITAL ALBUM (44px) → UNLOCK THE DELUXE
     EDITION (44px) → JOIN THE RELEASE LIST (47px) → Stream Rise Up → footer.
   CLAIM THE ARTIFACT is absent from that list and that is the design — see the
   artifact-state note above. The 44px figures were this file's own pinned
   min-height doing its job against a bare .btn of 42px; since Aug 21 2026 the
   pin lives in components.css and .btn carries it site-wide, so the same
   numbers now come from there. Re-measured after the move: unchanged.

   THE ARTIFACT STATE SWITCH MOVES NO LAYOUT. Toggling
   [data-ks-edition-status].hidden in the console at 1440x900:
       coming-soon   digital 706  deluxe 706  artifact 706  doc 2309
       available     digital 706  deluxe 706  artifact 706  doc 2309
   digital's top stays at y=673 in both, and getComputedStyle on the CLAIM THE
   ARTIFACT button flips display none/flex with it. The card is stretched by the
   grid row, which Deluxe sets, so swapping a two-element status block for a
   one-element button consumes slack INSIDE the card and nothing outside moves.

   CONTRAST, animations pinned to the 46% twinkle keyframe, backdrop captured
   with the text set to `color: transparent`, every pixel of each box measured,
   worst 5% of the box:
                                        1440x900        390x844
       card contents, scrim on            7.06            8.22
       card contents, scrim removed       3.11            5.65
       support paragraph, foot scrim on   7.40            8.29
       support paragraph, no foot scrim   2.64            4.86
       streaming line,    foot scrim on   4.58            5.08
       streaming line,    no foot scrim   1.41            2.37
   Every shipped figure clears AA's 4.5:1; both "no scrim" rows are the first
   draft and are why both scrims exist.
   Reference values on flat #03040F, computed not guessed: --text-secondary
   #9DB2C0 = 9.30:1, --text-muted #78888F = 5.56:1, --color-bone = 13.90:1,
   --color-crimson-lit #A1333E = 2.97:1.

   THE MEASUREMENT IS DELIBERATELY PESSIMISTIC: it scores every pixel of the
   element's box, including the gaps between lines and after the last word,
   where no glyph will ever sit. A worst-5% figure is therefore a floor on the
   real reading experience, not an estimate of it.

   STILL UNMEASURED, deliberately, and the next session should not assume
   otherwise: nothing here has been seen on the owner's actual phone, only at a
   390x844 emulated viewport. And the 46% keyframe is the twinkle's peak, not
   necessarily the worst frame for any given pixel — the cloud layer breathes on
   its own cycle and the two are not phase-locked.
   ========================================================================== */

/* ==========================================================================
   VERIFICATION NOTE — 2026-08-21, the album masthead.
   Same method and same three viewports as the pass above, plus a DPR-2 run.
   Everything below was observed.

   THIS BLOCK NOW SERVES THREE PAGES and all three were verified, not just the
   one it was designed against: purchase.html and merch.html at the full 400px,
   purchase-success.html at the --sm 240px.

   THE ASSET AND HOW TO REGENERATE IT. Source is the owner's master, which is
   NOT in this repo:
       Desktop/Kundalini-Spines-Track-Art/Kundalini Spines Album Cover Art/
       Kundalini_Spines_Rise_Up_Stripe.jpg     (1254x1254 RGB, 1.18 MB)
   Encoded to the two sizes this repo already uses for cover art — the full
   1254 square beside the per-track covers, the 448 square in thumbs/ — with
   Pillow at quality=82, method=6, the settings the existing set was cut at:
       assets/music/rise-up-cover.webp          1254x1254   219 KB
       assets/music/thumbs/rise-up-cover.webp    448x448      36 KB
   Both sit inside the range of the 28 track covers already there (216-522 KB
   full, 33-53 KB thumb), so nothing about this page's weight is exceptional.

   srcset PICKS CORRECTLY, which is the only reason the 448 is worth shipping:
       DPR 1, 400px box   -> thumbs/rise-up-cover.webp
       DPR 2, 400px box   -> music/rise-up-cover.webp
   The `sizes` attribute in purchase.html declares 400px above the breakpoint
   and min(100vw - 48px, 320px) below it; both match the boxes measured below,
   so the browser is never choosing against a wrong declared width.

   LAYOUT, at DPR 1:
                       cover img        caption strip      overflow
       1440x900        400x400          400x44             0
        950x900        400x400          400x44             0
        390x844        320x320          320x44             0
   documentElement.scrollWidth === window.innerWidth at 1440, 1232, 1100, 1000,
   940, 890, 881, 880, 600 and 390 — the full sweep, not just the three.

   THE SEAM IS EXACTLY 0.00px at all three widths: the caption's top edge sits on
   the image's bottom edge, the image carries no bottom border and the strip
   carries a full one, so what renders is a single shared hairline rather than
   two stacked. If a future edit reintroduces a bottom border on the image, that
   line doubles and the strip detaches visually.

   LAYOUT AND ASSET SELECTION ON THE OTHER TWO PAGES, DPR 1 unless stated:
                                   cover img    caption strip    seam    overflow
       merch.html      1440         400x400       400x44         0.00      0
       merch.html       950         400x400       400x44         0.00      0
       merch.html       390         320x320       320x44         0.00      0
       success.html    1440         240x240       240x62         0.00      0
       success.html     950         240x240       240x62         0.00      0
       success.html     390         320x320       320x44         0.00      0
   At DPR 2 all three pages fetch music/rise-up-cover.webp and all three fetch
   thumbs/rise-up-cover.webp at DPR 1 — the same result on every page, which is
   what one shared `sizes` contract should produce.

   THE 240px CAPTION WRAPS, AND THE COUNT IS PINNED TO ITS UNIT SO THAT IT WRAPS
   WELL. "RISE UP - 2026 - 28 TRACKS" does not fit one line at 240px in Plex Mono
   at 12px with 0.08em tracking, and left alone it broke after the 28 — a filing
   label whose last line is the single word TRACKS. All three pages therefore
   write `28&nbsp;tracks` in the markup, so the number and its unit travel
   together and the break falls after the second separator instead. Two lines,
   62px tall, and the same &nbsp; is in purchase.html and merch.html even though
   neither wraps at 400px — the strip is one component and its text should not
   need per-page fixing if a size ever changes.

   The alternatives were a shorter caption on that page only, which makes the
   three pages disagree about what the strip says, or a wider --sm, which puts
   the sleeve back into competition with the headline. Neither is cheaper.

   THE PHONE GETS ONE SIZE ON ALL THREE PAGES (320px), because the 880px media
   block collapses --sm along with the base class. That is a deliberate pairing
   and it is fragile: see the specificity warning at .ks-own__album--sm.

   CONTRAST of the caption strip, content box only, at the shipped 0.94 — the
   full alpha sweep and both mis-measurement traps are recorded at
   .ks-own__cover-cap and are not repeated here:
                          worst 1%   worst 5%
       purchase.html 1440    5.12       5.46
       purchase.html  950    5.24       5.34
       purchase.html  390    5.37       5.43
       merch.html    1440    5.15       5.46
       merch.html     950    5.24       5.34
       merch.html     390    5.37       5.43
       success.html  1440    5.46       5.56
       success.html   950    5.43       5.46
       success.html   390    5.37       5.43
   Every cell clears AA's 4.5:1, and each was sampled twice with the two samples
   required to agree within 0.1 before being recorded.

   UNCHANGED BY THIS PASS, checked rather than assumed:
     - Tab order. The figure adds no focusable node on ANY of the three pages —
       it is the subject, not a control — so purchase.html's sequence is still
       skip link -> nav -> the three CTAs -> Stream Rise Up -> footer, exactly as
       the note above records it. On purchase-success.html this is a rule and not
       just an outcome: that page may not offer anything that looks like a
       delivery, so the cover there is deliberately not a link.
     - The lede still breaks over three lines at 1440, 950 and 390. The head
       column's 72ch cap (734px) binds before the 784px grid track does, so the
       copy measure is identical to what it was before the cover arrived.
     - Zero console errors, zero page errors, zero failed requests, zero 4xx at
       all three widths and at DPR 2.

   STILL UNMEASURED: the owner's real phone, as above. And the artwork has not
   been checked against a light-mode or forced-colors rendering — the site has
   neither today, and a cover is an image either way, but the caption strip's
   translucent wash would need a real background under forced-colors.

   THE LAST SENTENCE HAS THE FAILURE BACKWARDS, corrected Aug 23 2026 and kept
   as the record. background-color is a FORCED property: under forced colours
   the UA does not fade rgba(3, 4, 15, 0.94) away, it REPLACES it — alpha
   discarded — with an opaque system fill. The strip therefore ends up more
   opaque than it ships, not less, and the caption was never the thing at risk.
   The alpha sweep above is simply dead weight in that mode, which is fine.

   WHAT IS ACTUALLY AT RISK IS THE OTHER HALF OF THE PAGE, and this file's own
   table two notes up is the evidence. Forced colours drops background-image
   ONLY for values that are not url(). Every sky layer in css/star-bg.css is a
   url(), so the photograph keeps painting — while both scrims laid over it are
   linear-gradient()s and are deleted outright. That is exactly the scrim
   removed column: 3.11 on the card contents, 2.64 on the support paragraph,
   1.41 on the streaming line. The two-pass text-shadow that might have softened
   that is dropped in this mode too. The block below closes all of it.
   ========================================================================== */

/* ==========================================================================
   FORCED COLOURS (Windows High Contrast) — added Aug 23 2026, V2HANDOFF 45
   open item 17.
   ==========================================================================
   THIS BLOCK IS LAST IN THE FILE ON PURPOSE, AND MOVING IT UP WILL BREAK IT.
   A media query adds no specificity. Every selector below ties exactly with the
   base rule it is overriding — and with the 600px and 880px blocks, which also
   set background and colour on some of them — so SOURCE ORDER IS THE ONLY THING
   MAKING IT WIN. This is the same trap that shipped .ks-own__album--sm the wrong
   way round and gave the phone an 85px cover with nothing in the console. Keep
   this block at the foot, below every media query in the file.

   NOT SEEN ON A REAL HIGH-CONTRAST THEME. Everything here is reasoned from the
   forced-colors substitution rules, not measured the way the rest of this file
   is measured. It is a floor, not a verified result. The first person to have a
   Windows HC theme in front of them should re-check it and correct this note
   rather than delete it.

   WHAT THE MODE DOES: the UA replaces color, background-color, border-color and
   text-decoration-color from the system palette, and drops box-shadow and
   text-shadow to none. It drops background-image too — BUT ONLY FOR VALUES THAT
   ARE NOT url(). That single exception decides everything below.

   1. THE CAPTION STRIP NEEDS ALMOST NOTHING, which is the opposite of what open
      item 17 assumed; the correction is in the note directly above. Its wash is
      a background-COLOR, so the UA replaces it with an opaque system fill on its
      own. Canvas is restated anyway because this component's whole legibility
      argument is that the strip is opaque, and a rule saying so is greppable in
      a way a UA default is not.

   2. THE HAIRLINE MUST STAY SINGLE. border-WIDTH is not a forced property —
      only border-COLOR is — so `border-bottom: 0` on .ks-own__cover img holds
      and the 0.00px seam recorded above survives this mode by itself. All that
      is done here is giving both halves the SAME colour so the frame does not
      change value across the seam. IF YOU ADD TO THIS BLOCK: do not give the
      image a bottom border and do not put a border on the figure. Either one
      doubles the line and detaches the strip.

      ButtonBorder, not CanvasText, because that frame is a structural edge and
      not type. It matters most on a DARK HC theme, where the cover's near-black
      edges sit on a near-black Canvas and the frame is the only thing saying
      where the sleeve stops. At 0.14 alpha it is a suggestion; forced, it is a
      line.

   3. THE SCRIMS ARE THE REAL REPAIR, and they are not the masthead at all. Both
      are linear-gradient()s, so both are deleted in this mode, while the six sky
      layers in css/star-bg.css are url() and keep painting underneath. The
      contrast table above already measured that exact state as the first draft
      of this page: 3.11 on the card contents, 2.64 on the support paragraph and
      1.41 on the streaming line, against AA's 4.5. Canvas restores an opaque
      floor under all three. Left undone, item 17 would have polished the one
      part of the page that was never in danger.

   4. THE 1px RULES ARE DRAWN AS BACKGROUNDS, not borders — the filing rule above
      the catalogue number and the dash marker in the contents list. Forced to
      Canvas they would be Canvas on Canvas and vanish, so they are named
      explicitly and given the frame colour.

   5. NO forced-color-adjust ANYWHERE, and that is a decision. The only thing
      `forced-color-adjust: none` does is put the author's colours back — here, a
      translucent wash over a star field, for the one reader who asked the OS to
      remove exactly that. The artwork does not need it either: forced colours
      never recolours img content, so the cover paints as itself with no opt-out.
      That property earns its place when a component carries meaning IN colour
      that the palette would flatten — a status swatch, a chart key. Nothing here
      does.

   6. NO LinkText, because there is no link. The cover is deliberately not an
      anchor on any of the three pages. If a shopfront ever makes it clickable,
      its colour goes here.

   background-color rather than the `background` shorthand throughout: the
   shorthand would also reset background-image, and none of these rules has any
   business having an opinion about a property their base rules never set. */
@media (forced-colors: active) {

  /* The masthead — item 17 as written. */
  .ks-own__cover img { border-color: ButtonBorder; }
  .ks-own__cover-cap {
    background-color: Canvas;
    border-color: ButtonBorder;
    color: CanvasText;
  }

  /* The scrims the mode deletes. See 3 above: this is the half that matters. */
  .ks-edition,
  .ks-edition--deluxe,
  .ks-own__foot {
    background-color: Canvas;
  }

  /* Hairlines drawn as backgrounds, which would otherwise be Canvas on Canvas. */
  .ks-edition::before,
  .ks-edition--deluxe::before,
  .ks-edition__contents li::before {
    background-color: ButtonBorder;
  }
}
