/* Changelog page. Loaded only by web/patch.html after the app's full cascade.
   Whimsy tokens do most of the work — the two tier blocks at the bottom add
   only what tokens cannot say (tape, marker swipe, tilt, tag column).
   No ids — tests/element-ids.test.js checks every id in this directory.
   IS in SHELL in sw.js. */

/* --- Undo base.css viewport lock so the page scrolls normally --- */

html, body {
  height: auto;
  /* clip-x prevents horizontal scroll from Softish plate tilt */
  overflow-x: clip;
  overflow-y: visible;
}

body {
  position: relative;
  min-height: 100vh;
  /* Size blooms to one screen so they do not stretch over a tall document */
  background-attachment: scroll;
  background-repeat: no-repeat;
  background-size: 100% 100vh;
}

.pn {
  position: relative;
  z-index: 1;
}

/* Paper grain — same tile/blend as #grain in base.css, scrolls with the page */
body.is-patch::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image: url("../img/paper-grain.webp");
  background-repeat: repeat;
  background-size: 512px 512px;
  mix-blend-mode: multiply;
  opacity: calc(0.8 * var(--grain));
}

.pn-inner {
  max-width: 880px;
  margin: 0 auto;
  padding: clamp(76px, 11vw, 132px) clamp(18px, 5vw, 40px) 96px;
}

/* --- The column — prose with a commit-count margin --- */

/* Back link — sticky, opaque, z-index 3 (above .pn-mark at 2) */
.pn-back {
  position: sticky;
  top: 14px;
  z-index: 3;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  width: fit-content;
  margin: 0 0 clamp(26px, 4vw, 40px);
  padding: 9px 16px 9px 12px;
  border: var(--hairline) solid var(--rule-2);
  border-radius: var(--radius-pill);
  background: var(--paper-card);
  box-shadow: var(--shadow-1);
  color: var(--ink-2);
  font-family: var(--font-display);
  font-size: var(--t-small);
  font-style: var(--display-italic);
  letter-spacing: calc(0.04em * var(--ls-scale));
  text-decoration: none;
  transition: color var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease),
              transform var(--dur-fast) var(--ease);
}
.pn-back:hover {
  color: var(--accent-deep);
  border-color: var(--accent-text);
  transform: translateX(-2px);
}
.pn-back .ico { width: 14px; height: 14px; }

.pn-head {
  margin-bottom: clamp(38px, 7vw, 70px);
}

.pn-title {
  margin: 0;
  font-family: var(--font-display);
  font-size: clamp(var(--t-display), 8vw, calc(var(--t-display) * 2.1));
  font-weight: var(--display-weight);
  /* Tracking tightens as size grows — loose at phone, tight at desktop */
  letter-spacing: calc(clamp(-0.02em, 0.03em - 0.09vw, 0.01em) * var(--ls-scale));
  line-height: 1.04;
  color: var(--ink);
}

.pn-lede {
  margin: 14px 0 0;
  max-width: 48ch;
  font-size: calc(var(--t-body) * 1.06);
  color: var(--ink-2);
}

/* Three counters — computed from source at build time */
.pn-tally {
  display: flex;
  flex-wrap: wrap;
  gap: 0 12px;
  margin: 22px 0 0;
  font-family: var(--font-display);
  font-size: var(--t-tiny);
  font-style: var(--display-italic);
  letter-spacing: calc(0.2em * var(--ls-scale));
  text-transform: uppercase;
  color: var(--ink-3);
}
.pn-tally span + span {
  padding-left: 14px;
  border-left: var(--hairline) solid var(--rule);
}

/* --- The spine — a continuous border drawn by adjacent margin cells --- */

.pn-list {
  margin: 0;
  padding: 0;
  list-style: none;
}

.pn-rel {
  display: grid;
  grid-template-columns: 74px minmax(0, 1fr);
  column-gap: clamp(18px, 3vw, 34px);
}

.pn-margin {
  position: relative;
  padding: 0 14px 0 0;
  border-right: var(--hairline) solid var(--rule);
  text-align: right;
}

/* Sits on the spine line via negative offset */
.pn-mark {
  position: absolute;
  top: 0.72em;
  right: -4px;
  width: 7px;
  height: 7px;
  border-radius: var(--radius-pill);
  background: var(--accent);
}

.pn-count {
  display: block;
  padding-top: 0.34em;
  font-family: var(--font-display);
  font-size: var(--t-tiny);
  font-style: var(--display-italic);
  letter-spacing: calc(0.08em * var(--ls-scale));
  color: var(--ink-3);
}

/* ---------------------------------------------------------
   One release
   --------------------------------------------------------- */

.pn-plate {
  margin: 0 0 clamp(20px, 3.4vw, 34px);
  padding: clamp(22px, 3.6vw, 34px) clamp(22px, 3.6vw, 36px);
  border: var(--hairline) solid var(--rule);
  border-radius: var(--radius);
  background: var(--paper-card);
  box-shadow: var(--shadow-1);
  transition: box-shadow var(--dur-base) var(--ease),
              translate var(--dur-base) var(--ease-back),
              rotate var(--dur-base) var(--ease-back);
}

/* The lift every control on the board answers a pointer with, through the same
   token - so it is a real hover at Softish, a small one in the middle, and
   nothing at all at Harsh, where --lift-hover is 0 and this rule quietly stops
   meaning anything. */
@media (hover: hover) {
  .pn-plate:hover {
    translate: 0 calc(-1 * var(--lift-hover));
    box-shadow: var(--shadow-2);
  }
}

.pn-plate-head { margin-bottom: 18px; }

/* Version, then name, then date - quiet, loud, quiet. The number is what the
   app agrees with and the name is what a person remembers, so the name is the
   one set large; the sidebar wordmark makes the same trade with its .sub. */
.pn-ver {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--t-tiny);
  font-style: var(--display-italic);
  letter-spacing: calc(0.22em * var(--ls-scale));
  text-transform: uppercase;
  color: var(--accent-text);
}

.pn-name {
  margin: 8px 0 0;
  font-family: var(--font-display);
  font-size: calc(var(--t-display) * 1.15);
  font-weight: var(--display-weight);
  /* A flat value here and a clamp in .pn-title, and the difference is the
     font-size two lines up: this one is a fixed multiple of a token rather than
     something that grows with the window, so there is nothing for a vw term to
     follow. It is a release heading rather than the page's title, so it sits at
     the shallow end of the same correction. */
  letter-spacing: calc(-0.005em * var(--ls-scale));
  line-height: 1.12;
  color: var(--ink);
}

.pn-date {
  margin: 6px 0 0;
  font-family: var(--font-display);
  font-size: var(--t-small);
  font-style: var(--display-italic);
  color: var(--ink-3);
}

/* A heading inside a release. The section head from sidebar.css - tracked,
   uppercase, faint, in the display face - because that is what a heading below
   the top rank looks like in this app, and it arrives over a rule for the same
   reason the panel's sections do. */
.pn-part {
  margin: 28px 0 12px;
  padding-top: 20px;
  border-top: var(--hairline) solid var(--rule);
  font-family: var(--font-display);
  font-size: var(--t-tiny);
  font-weight: 400;
  font-style: var(--display-italic);
  letter-spacing: calc(0.24em * var(--ls-scale));
  text-transform: uppercase;
  color: var(--ink-3);
}

/* ---------------------------------------------------------
   The bullets
   --------------------------------------------------------- */

.pn-notes {
  margin: 0;
  padding: 0;
  list-style: none;
  color: var(--ink-2);
}

.pn-notes li {
  margin: 0 0 14px;
  line-height: 1.62;
}
.pn-notes li:last-child { margin-bottom: 0; }

/* A bullet with no tag is prose about the release as a whole, so it gets the
   drawn marker the tagged ones do not need - they have a word in front of
   them already. Squared rather than round, like every small mark in this
   interface: --radius-xs follows the axis and a circle would not. */
.pn-notes li.is-plain {
  position: relative;
  padding-left: 20px;
}
.pn-notes li.is-plain::before {
  content: "";
  position: absolute;
  left: 2px;
  top: 0.62em;
  width: 5px;
  height: 5px;
  border-radius: var(--radius-xs);
  background: var(--rule-2);
}

/* The thing a bullet is about, so a column of grey prose can be skimmed. Full
   ink against the secondary the list is set in - the size and the face do not
   change. */
.pn-notes strong {
  font-weight: 600;
  color: var(--ink);
}

/* One accent-coloured word, used sparingly. The same bargain .hint em makes in
   sidebar.css: not italic, just the pigment. */
.pn-notes em {
  font-style: normal;
  color: var(--accent-text);
}

.pn-notes code {
  padding: 1px 5px;
  border-radius: var(--radius-xs);
  background: var(--paper-2);
  font-family: var(--font-mono);
  font-size: 0.9em;
  color: var(--ink);
}

/* ---------------------------------------------------------
   The three tags

   New, Fix and Faster, and only three on purpose: a set that grows past what
   can be told apart at a glance is a legend, and nobody reads a legend on a
   changelog. Outlines rather than fills at the two rounded levels, because a
   filled chip at the head of every bullet would out-shout the sentence it
   labels - Harsh takes them out of their boxes entirely, see below.
   --------------------------------------------------------- */

.pn-tag {
  display: inline-block;
  margin-right: 9px;
  padding: 0 6px;
  border: var(--hairline) solid var(--accent-text);
  border-radius: var(--radius-xs);
  font-family: var(--font-display);
  font-size: var(--t-tiny);
  font-style: var(--display-italic);
  letter-spacing: calc(0.12em * var(--ls-scale));
  text-transform: uppercase;
  /* Held off the baseline of the sentence it heads: the tag labels the bullet,
     it is not the first word of it. */
  vertical-align: 0.09em;
  white-space: nowrap;
  color: var(--accent-text);
}
.pn-tag.is-fix {
  border-color: var(--rule-2);
  color: var(--ink-3);
}
.pn-tag.is-faster {
  border-color: var(--ink-2);
  color: var(--ink-2);
}

/* ---------------------------------------------------------
   Links and the foot

   `.side-foot .by` used to ride this rule, which was the app's own sidebar
   credit line sitting in the panel on this page. There is no panel here now.
   --------------------------------------------------------- */

.pn a {
  color: var(--accent-text);
  text-decoration-color: color-mix(in srgb, var(--accent-text) 45%, transparent);
  text-underline-offset: 2px;
}
.pn a:hover { color: var(--accent-deep); }
.pn a:focus-visible {
  outline: 2px solid var(--accent-text);
  outline-offset: 3px;
  border-radius: var(--radius-xs);
}

.pn-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  margin-top: clamp(34px, 6vw, 60px);
  padding-top: 20px;
  border-top: var(--hairline) solid var(--rule);
  font-family: var(--font-display);
  font-size: var(--t-tiny);
  font-style: var(--display-italic);
  letter-spacing: calc(0.1em * var(--ls-scale));
  color: var(--ink-3);
}

/* An anchored release lands clear of the top of the window rather than flush
   against it, which on a page whose first line is a version number reads as
   the release above having been cut off. */
.pn-rel { scroll-margin-top: 28px; }

/* The spine is drawn by the margin cells, which are as tall as their rows - so
   the gap under the last plate would hang thirty pixels of line below the end
   of the changelog, pointing at nothing. */
.pn-rel:last-child .pn-plate { margin-bottom: 0; }

/* =========================================================
   0: Softish - the scrapbook

   What a token cannot say. The plates are pinned up rather than laid out: a
   fraction of --tilt-max off square, alternating, with a strip of tape at the
   corner and the codename marked up in the palette's own highlighter. The
   angle is small on purpose - this is a changelog somebody has to read, and a
   page of prose set at three degrees is a joke that stops being funny on the
   second release.
   ========================================================= */

/* The sheet itself, and this is the app's own recipe rather than an idea of
   what notepaper looks like: the same two gradients and the same mask that
   .ghost-card wears at this tier in ghosts.css - blue rules every 22px, the red
   margin at 26px, and a punched edge cut out of the left side. The numbers are
   that file's; the release plate is the same stock as the hint cards on an
   empty board, which is the point.

   Square where it was torn off the pad and rounded where it was not, so the
   left edge reads as the perforation rather than as a corner somebody eased. */
:root[data-whimsy="0"] .pn-plate {
  position: relative;
  border: 0;
  box-shadow: var(--shadow-2);
  background:
    repeating-linear-gradient(180deg,
      transparent 0 21px,
      color-mix(in srgb, #6f86a8 17%, transparent) 21px 22px),
    linear-gradient(90deg,
      transparent 0 26px,
      color-mix(in srgb, #b4544a 42%, transparent) 26px 27px,
      transparent 27px),
    var(--paper-card);
  border-radius: 0 var(--radius) var(--radius) 0;
  /* Clear of the red margin, so the version number does not sit on the rule. */
  padding-left: 44px;
  rotate: calc(var(--tilt-max) * 0.18);
}
:root[data-whimsy="0"] .pn-rel:nth-child(even) .pn-plate {
  rotate: calc(var(--tilt-max) * -0.18);
}
/* Straight under the pointer, which is what every card on the board does when
   you pick it up. */
@media (hover: hover) {
  :root[data-whimsy="0"] .pn-plate:hover { rotate: 0deg; }
}

/* The punch holes. A mask applies to an element *and its descendants* - which
   is the trap ghosts.css names outright - so it goes on the sheet and the tape
   has to be somewhere else. Hence the tape below hangs off .pn-rel, the grid
   row, rather than off the plate it is holding down. */
:root[data-whimsy="0"] .pn-plate {
  -webkit-mask-image: radial-gradient(circle 6px at 0 11px, transparent 6px, #000 6.5px);
  mask-image: radial-gradient(circle 6px at 0 11px, transparent 6px, #000 6.5px);
  -webkit-mask-size: 100% 22px;
  mask-size: 100% 22px;
  -webkit-mask-repeat: repeat-y;
  mask-repeat: repeat-y;
}

/* The tape, built the way .ghost-tape is: kraft over a woven stripe, torn
   across and straight along, because tape tears against the roll and not with
   it. One strip, straddling the top edge at an angle - and it is a child of the
   row so the plate's mask cannot perforate it. */
:root[data-whimsy="0"] .pn-rel { position: relative; }
:root[data-whimsy="0"] .pn-rel::after {
  content: "";
  position: absolute;
  z-index: 2;
  top: -10px;
  right: 46px;
  width: 92px;
  height: 26px;
  rotate: -3.5deg;
  pointer-events: none;
  background:
    repeating-linear-gradient(90deg,
      color-mix(in srgb, #a8814a 7%, transparent) 0 2px, transparent 2px 6px),
    linear-gradient(180deg,
      color-mix(in srgb, #e8cf9c 78%, transparent),
      color-mix(in srgb, #dcc08a 72%, transparent));
  clip-path: polygon(
    3% 0, 97% 4%, 100% 22%, 96% 41%, 100% 62%, 97% 82%, 99% 100%,
    2% 96%, 0 78%, 4% 58%, 0 38%, 3% 19%
  );
  box-shadow: 0 1px 2px color-mix(in srgb, var(--ink) 18%, transparent);
}
/* Pressed down at the other end on alternate rows, since the plate under it
   leans the other way. */
:root[data-whimsy="0"] .pn-rel:nth-child(even)::after {
  right: auto;
  left: 118px;
  rotate: 3deg;
}

/* The codename, marked up in amber - which is what --highlight is at this end
   of the axis, and what tokens.css says a scrapbook is marked up in. The
   gradient is a swipe rather than a fill: a highlighter lands under the words
   and not around them. */
:root[data-whimsy="0"] .pn-name {
  display: inline;
  background-image: linear-gradient(to bottom,
    transparent 58%, var(--highlight) 58%, var(--highlight) 94%, transparent 94%);
  box-decoration-break: clone;
}

/* The mark on the spine is a pin at this end: bigger, and with the ink ring a
   real one would cast. */
:root[data-whimsy="0"] .pn-mark {
  width: 9px;
  height: 9px;
  right: -5px;
  box-shadow: 0 1px 3px color-mix(in srgb, var(--ink) 34%, transparent);
}

/* =========================================================
   2: Harsh - the spec sheet

   The plates stop being objects. No sheet, no border but the rule above, no
   shadow, no corner - all four of those arrive from the tokens - and the tags
   come out of their boxes into a fixed column, so every bullet on the page
   starts at the same x and the whole changelog reads as one table. That column
   is the only thing here a token could not have done, and it is the one move
   this level is really made of.
   ========================================================= */

:root[data-whimsy="2"] .pn-plate {
  padding: 22px 0 0;
  border: 0;
  border-top: var(--hairline) solid var(--rule-2);
  background: none;
}

/* Data, so it is set as data. The one place on the page the mono face is used,
   and Harsh is the level that ships one worth using. */
:root[data-whimsy="2"] .pn-ver,
:root[data-whimsy="2"] .pn-count {
  font-family: var(--font-mono);
  font-style: normal;
  letter-spacing: 0;
}

:root[data-whimsy="2"] .pn-tag {
  width: 4.6em;
  margin-right: 12px;
  padding: 0;
  border: 0;
  font-style: normal;
  font-weight: 600;
  letter-spacing: calc(0.16em * var(--ls-scale));
  color: var(--ink-3);
}
:root[data-whimsy="2"] .pn-tag.is-new { color: var(--accent-text); }
:root[data-whimsy="2"] .pn-tag.is-faster { color: var(--ink); }

/* The bullets hang, so the second line of a note lines up with the first word
   of it rather than with the label. This is what makes the column read as a
   column and not as an indent that gives up after one line. */
:root[data-whimsy="2"] .pn-notes li {
  padding-left: 5.8em;
  text-indent: -5.8em;
}
:root[data-whimsy="2"] .pn-notes li.is-plain {
  padding-left: 5.8em;
  text-indent: 0;
}
:root[data-whimsy="2"] .pn-notes li.is-plain::before { content: none; }

/* The spine is a scale here rather than a thread: full ink, and the mark is
   a tick sitting across it instead of a bead on it. */
:root[data-whimsy="2"] .pn-margin { border-right-color: var(--ink); }
:root[data-whimsy="2"] .pn-mark {
  top: 0.85em;
  right: -5px;
  width: 11px;
  height: 2px;
}

/* =========================================================
   Narrow

   The margin goes rather than shrinks. Seventy-four pixels of gutter on a
   phone is a quarter of the measure spent on a number, and the number is the
   least of what is on this page.
   ========================================================= */

@media (max-width: 640px) {
  .pn-rel { grid-template-columns: minmax(0, 1fr); }
  .pn-margin { display: none; }
  .pn-plate { padding: 20px; }
  :root[data-whimsy="0"] .pn-plate { rotate: 0deg; }
  :root[data-whimsy="0"] .pn-plate::before { display: none; }
  /* The hanging column needs room the phone does not have. */
  :root[data-whimsy="2"] .pn-notes li,
  :root[data-whimsy="2"] .pn-notes li.is-plain {
    padding-left: 0;
    text-indent: 0;
  }
  :root[data-whimsy="2"] .pn-tag { display: block; width: auto; margin-bottom: 2px; }
}

@media (prefers-reduced-motion: reduce) {
  .pn-plate { transition: none; }
  @media (hover: hover) {
    .pn-plate:hover { translate: none; }
    :root[data-whimsy="0"] .pn-plate:hover { rotate: calc(var(--tilt-max) * 0.18); }
  }
}

/* Without color-mix(), for WebKit below 16.2 - section 7 of tokens.css carries
   the argument and declares the channel-form tokens.

   This page is index.html's head and nothing else, so it wears the app's whole
   cascade with none of its script - which means it gets the same fallback for
   the same reason, and gets it from the same tokens. The plate and the tape are
   ghosts.css's, rebuilt here for a document rather than a card; the two blocks
   should be read together and changed together. */
@supports not (color: color-mix(in srgb, red, blue)) {
  .pn a { text-decoration-color: rgb(var(--accent-c) / 45%); }

  :root[data-whimsy="0"] .pn-plate {
    background:
      repeating-linear-gradient(180deg,
        transparent 0 21px,
        rgb(111 134 168 / 17%) 21px 22px),
      linear-gradient(90deg,
        transparent 0 26px,
        rgb(180 84 74 / 42%) 26px 27px,
        transparent 27px),
      var(--paper-card);
  }

  :root[data-whimsy="0"] .pn-rel::after {
    background:
      repeating-linear-gradient(90deg,
        rgb(168 129 74 / 7%) 0 2px, transparent 2px 6px),
      linear-gradient(180deg,
        rgb(232 207 156 / 78%),
        rgb(220 192 138 / 72%));
    box-shadow: 0 1px 2px rgb(var(--ink-c) / 18%);
  }

  :root[data-whimsy="0"] .pn-mark {
    box-shadow: 0 1px 3px rgb(var(--ink-c) / 34%);
  }
}
