@property --fan {
  syntax: "<angle>";
  inherits: false;
  initial-value: 180deg;
}

:root {
  --paper: #f3ede1;
  --ink: #2b2721;
  --ink-soft: #6b6258;
  --seal: #a8402c;
  --rule: #d8cfbd;

  /* The darkest value any cloth texture reaches behind text, and the
     lantern's paper body: every ink is checked against both
     (spec/ink-palette.test.ts). */
  --paper-shade: #ebe3d3;
  --lantern-paper: #f6e6c4;
  --lantern-glow: #f1c27a;

  /* Scenery, taken from the painting. These never colour a visitor's text
     and never mean "yours". */
  --spruce: #3d5a3a;
  --moss: #7b9a5a;
  --granite: #8a8378;
  --clay: #b79d7c;
  --sky: #e6e9e4;
  --silk: #61785a;
  --silk-dark: #46593f;
  --gilt: #b9a06a;
  --wood-dark: #3b2817;
  --wood: #8a6440;
  --wood-mid: #4a3320;

  /* A small silk-slub tile, tiled once and multiplied; never a live filter
     over a large area. */
  /* Room the fixed dock takes at the bottom of the viewport. CSS is
     authoritative so the page clears the dock with no script; app.js may
     refine it by measuring. */
  --dock-h: 9.75rem;
  --dock-safe: env(safe-area-inset-bottom, 0px);

  --slub: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='96' height='96'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.05 .9' numOctaves='2' seed='4'/%3E%3CfeColorMatrix values='0 0 0 0 .2 0 0 0 0 .18 0 0 0 0 .12 0 0 0 -1.2 .62'/%3E%3C/filter%3E%3Crect width='96' height='96' filter='url(%23n)'/%3E%3C/svg%3E");
}

* {
  box-sizing: border-box;
}

.skip-link {
  position: fixed;
  top: 0.5rem;
  left: 0.5rem;
  z-index: 120;
  padding: 0.6rem 0.9rem;
  background: var(--paper);
  color: var(--ink);
  box-shadow: 0 0 0 1px var(--gilt);
  transform: translateY(-200%);
}

.skip-link:focus {
  transform: none;
}

/* Before the scroll: hidden until focused, then a slip of paper in flow
   above the band, so a keyboard user can jump to what follows. */
.scroll-section {
  position: relative;
}

.skip-past {
  position: absolute;
  left: 1rem;
  z-index: 5;
  padding: 0.5rem 0.9rem;
  background: var(--paper);
  color: var(--ink);
  box-shadow: 0 0 0 1px var(--gilt);
  transform: translateY(-200%);
  opacity: 0;
}

.skip-past:focus {
  transform: translateY(-2.6rem);
  opacity: 1;
}

#after-scroll:focus {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
}

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

html {
  background: #dcd7ca;

  /* Anything scrolled into view (focus, a #fragment) lands clear of the
     fixed dock. */
  scroll-padding-bottom: calc(var(--dock-room, var(--dock-h)) + var(--dock-safe) + 1rem);
}

/* The wall the scroll hangs on: quiet plaster, so the silk reads as cloth. */
body {
  margin: 0;
  color: var(--ink);
  font-family: Georgia, "Times New Roman", Times, serif;
  line-height: 1.55;
  background: radial-gradient(ellipse at 50% 30%, #e4e0d4, #d3cdbe);
  background-attachment: fixed;

  /* The wall runs to the bottom of the window on short pages too. */
  min-height: 100vh;
  min-height: 100dvh;
}

a {
  color: var(--ink);
  text-decoration-color: var(--clay);
  text-decoration-thickness: 0.08em;
  text-underline-offset: 0.18em;
}

a:visited {
  color: var(--ink);
}

a:hover {
  color: var(--spruce);
  text-decoration-color: var(--spruce);
}

a:focus-visible,
button:focus-visible,
summary:focus-visible,
textarea:focus-visible,
input:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
}

h1,
h2,
h3 {
  font-weight: normal;
  line-height: 1.2;
}

/* The mount: rollers top and bottom, silk between, paper panels inside. */
.mount {
  max-width: 52rem;
  margin: 0 auto;
  padding: 1.25rem 0 2.5rem;
}

.roller {
  position: relative;
  height: 1.5rem;
  margin: 0 0.4rem;
  border-radius: 0.3rem;
  background: linear-gradient(var(--wood-dark), var(--wood) 35%, var(--wood-mid) 70%, var(--wood-dark));
  box-shadow: 0 0.2rem 0.4rem rgb(0 0 0 / 30%);
}

.roller::before,
.roller::after {
  content: "";
  position: absolute;
  top: -0.25rem;
  width: 0.9rem;
  height: 2rem;
  border-radius: 0.35rem;
  background: linear-gradient(90deg, var(--wood-dark), var(--wood) 50%, var(--wood-dark));
}

.roller::before {
  left: -0.4rem;
}

.roller::after {
  right: -0.4rem;
}

.mount-silk {
  margin: 0 1rem;
  padding: 2.5rem 2.25rem;

  /* Brocade: soft crease and edge shading near the rollers, a diamond
     lattice, a fine warp and weft, and slub, on moss-green silk. */
  background-color: var(--silk);
  background-image:
    linear-gradient(rgb(0 0 0 / 22%), transparent 2.5rem, transparent calc(100% - 2.5rem), rgb(0 0 0 / 22%)),
    linear-gradient(90deg, rgb(0 0 0 / 12%), transparent 1.2rem, transparent calc(100% - 1.2rem), rgb(0 0 0 / 12%)),
    var(--slub),
    repeating-linear-gradient(45deg, rgb(255 255 255 / 6%) 0 1px, transparent 1px 1.1rem),
    repeating-linear-gradient(-45deg, rgb(255 255 255 / 6%) 0 1px, transparent 1px 1.1rem),
    repeating-linear-gradient(0deg, rgb(0 0 0 / 5%) 0 1px, transparent 1px 0.2rem),
    repeating-linear-gradient(90deg, rgb(0 0 0 / 4%) 0 1px, transparent 1px 0.2rem);
  background-size: auto, auto, 6rem 6rem, auto, auto, auto, auto;
  background-blend-mode: normal, normal, multiply, normal, normal, normal, normal;
  box-shadow: 0 0.3rem 1rem rgb(0 0 0 / 30%);
}

.sheet {
  position: relative;
  padding: 2.25rem 2.5rem;
  margin: 0 0 2.5rem;

  /* The paper: a faint warp and weft, kept lowest-amplitude where text sits.
     Lines are translucent --paper-shade (#ebe3d3), so nothing behind text
     gets darker than that. */
  background-color: var(--paper);
  background-image:
    repeating-linear-gradient(0deg, rgb(235 227 211 / 60%) 0 1px, transparent 1px 0.35rem),
    repeating-linear-gradient(90deg, rgb(235 227 211 / 45%) 0 1px, transparent 1px 0.3rem);
  background-size: auto;
  box-shadow:
    0 0 0 1px var(--gilt),
    0 0 0 0.35rem rgb(185 160 106 / 35%),
    0 0.2rem 0.8rem rgb(0 0 0 / 25%);
}

.sheet:last-child {
  margin-bottom: 0;
}

.sheet--colophons {
  max-width: 44rem;
  margin-inline: auto;
}

/* The seal chooser hangs on a wider mount, for more cards a row. */
.mount:has(.sheet--seals) {
  max-width: 72rem;
}

.sheet--colophons.sheet--seals {
  max-width: none;
}

/* Header, with a misty crop of the painting faded to paper behind it. */
.site-header {
  position: relative;
  padding: 0.5rem 3.5rem 1.25rem 0;
  margin-bottom: 1.5rem;
  isolation: isolate;
}

.site-header::before {
  content: "";
  position: absolute;
  inset: -2.25rem -2.5rem 0;
  z-index: -1;
  background: url("/public/painting.svg") center 8% / 120% auto no-repeat;
  opacity: 0.3;
  mix-blend-mode: multiply;
  mask-image:
    linear-gradient(90deg, transparent, #000 30%, #000 70%, transparent),
    linear-gradient(#000 30%, transparent);
  mask-composite: intersect;
}

.site-header h1,
.site-header .site-title {
  margin: 0;
  line-height: 1.2;
  font-size: 2.1rem;
  letter-spacing: 0.02em;
}

.site-header h1 a,
.site-header .site-title a {
  color: inherit;
  text-decoration: none;
}

.site-header nav {
  font-size: 0.95rem;
}

.title-accent {
  position: absolute;
  top: 0.25rem;
  right: 0.25rem;
  margin: 0;
  writing-mode: vertical-rl;
  font-size: 1.5rem;
  letter-spacing: 0.3em;
  color: var(--ink-soft);
}

.kicker {
  margin: 0.2rem 0 0.6rem;
  color: var(--ink-soft);
  font-style: italic;
}

figcaption {
  margin-top: 0.5rem;
  font-size: 0.9rem;
  color: var(--ink-soft);
}

.section-note {
  color: var(--ink-soft);
  font-size: 0.95rem;
}

/* Entries: inscriptions on plain paper, each in its writer's ink. */
.colophon-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

/* Base entry: a passage, then its foot (date, then seal). scroll.css lays
   these out as sheets across the handscroll; without it they still read as
   a plain list. */
.colophon {
  position: relative;
  padding: 0.9rem 0;
  border-bottom: 1px solid var(--rule);
}

.colophon-foot {
  position: relative;
  display: flex;
  align-items: center;
  gap: 0.6rem;
}

.colophon-seal summary {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2.2rem;
  height: 2.2rem;
  font-size: 1.3rem;
  line-height: 1;
  list-style: none;
  cursor: pointer;
  color: var(--ink-soft);
  border: 1.5px solid var(--ink-soft);
  border-radius: 3px;
}

.colophon-seal summary::-webkit-details-marker {
  display: none;
}

/* A chosen seal is a filled stamp; the random hash glyph is an outline, so
   the two never look the same even when they share a character. */
.seal--chosen summary {
  color: var(--paper);
  background: var(--ink-soft);
}

.seal-meaning {
  position: absolute;
  z-index: 2;
  bottom: calc(100% + 0.4rem);
  left: 0;
  width: max-content;
  max-width: min(16rem, 100%);
  margin: 0;
  padding: 0.35rem 0.6rem;
  font-size: 0.9rem;
  background: var(--paper);
  border: 1px solid var(--granite);
  box-shadow: 0 0.15rem 0.4rem rgb(0 0 0 / 15%);
}

.colophon--mine .colophon-seal summary {
  color: var(--seal);
  border-color: var(--seal);
}

.colophon--mine .seal--chosen summary {
  color: var(--paper);
  background: var(--seal);
}

.colophon-body {
  margin: 0 0 0.5rem;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.colophon-date {
  margin: 0;
  font-size: 0.85rem;
  color: var(--ink-soft);
}

/* The date is the link to its line: quiet, the colour of the date, with
   an underline on hover and the usual focus ring. */
.colophon-link,
.colophon-link:visited {
  color: inherit;
  text-decoration-color: transparent;
}

.colophon-link:hover,
.colophon-link:focus-visible {
  color: inherit;
  text-decoration-color: currentcolor;
}

.colophon--mine .colophon-date {
  color: var(--seal);
}

/* The ink palette (src/ink.ts). A visitor's own text only; never red. */
.ink--mo-lan {
  --entry-ink: #2a4f9a;
}

.ink--dian-qing {
  --entry-ink: #1f3a6e;
}

.ink--shi-lu {
  --entry-ink: #2f6b57;
}

.ink--zi {
  --entry-ink: #6a3a5c;
}

.ink--mo {
  --entry-ink: #2b2721;
}

.colophon .colophon-body {
  color: var(--entry-ink, var(--ink));
}

/* A line that has just arrived is fresh ink: deeper, with a faint wet
   sheen, drying to its final colour over about eight seconds. Its seal
   presses down into the paper as it lands. */
.colophon--arriving {
  animation: arrive 1.2s ease-out;
}

.colophon--arriving .colophon-body {
  animation: dry 8s ease-out;
}

.colophon--arriving .colophon-seal summary {
  animation: press 0.7s cubic-bezier(0.3, 1.4, 0.5, 1) 0.3s backwards;
}

@keyframes arrive {
  from {
    opacity: 0;
  }
}

@keyframes dry {
  from {
    filter: saturate(1.6) brightness(0.8);
    text-shadow: 0 0 0.06em currentcolor;
  }
}

@keyframes press {
  from {
    opacity: 0;
    transform: scale(1.6);
  }
}

.empty-note {
  color: var(--ink-soft);
  font-style: italic;
}

.seal-legend {
  font-size: 0.9rem;
  color: var(--ink-soft);
}

.seal-legend summary {
  display: list-item;
  list-style-position: inside;
  width: fit-content;
  min-height: 2.75rem;
  padding-block: 0.6rem;
  font-size: 1rem;
  color: var(--ink);
  cursor: pointer;
}

/* The native disclosure marker: not part of the summary's accessible name,
   unlike text or ::before content. */
.seal-legend summary::marker {
  color: var(--ink-soft);
}

.seal-legend ul {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem 1.25rem;
  margin: 0 0 0.6rem;
  padding: 0;
  list-style: none;
}

.legend-glyph,
.result-glyph {
  font-size: 1.15em;
  color: var(--ink);
}

/* Writing. */
.write-form {
  display: grid;
  gap: 0.6rem;
  max-width: 34rem;
}

label {
  font-size: 0.95rem;
}

.inkwell {
  position: relative;
  --entry-ink: #2a4f9a;
}

.write-form:has(input[value="dian-qing"]:checked) .inkwell {
  --entry-ink: #1f3a6e;
}

.write-form:has(input[value="shi-lu"]:checked) .inkwell {
  --entry-ink: #2f6b57;
}

.write-form:has(input[value="zi"]:checked) .inkwell {
  --entry-ink: #6a3a5c;
}

.write-form:has(input[value="mo"]:checked) .inkwell {
  --entry-ink: #2b2721;
}

textarea,
.inkwell-mirror {
  width: 100%;
  margin: 0;
  padding: 0.6rem;
  font: inherit;
  font-size: 1.05rem;
  line-height: 1.55;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  border: 1px solid var(--ink-soft);
}

textarea {
  display: block;
  min-height: 5.5rem;
  resize: none;
  field-sizing: content;
  background: #fbf8f2;
  color: var(--entry-ink);
}

textarea::placeholder {
  color: var(--ink-soft);
  opacity: 1;
}

.inkwell-mirror {
  position: absolute;
  inset: 0;
  overflow: hidden;
  border-color: transparent;
  color: var(--entry-ink);
  pointer-events: none;
}

.inkwell--live textarea {
  color: transparent;
  caret-color: var(--entry-ink);
}

.inkwell--live textarea::selection {
  color: transparent;
  background: rgb(97 120 90 / 25%);
}

.ink-ch {
  animation: ink-in 0.55s ease-out;
}

.ink-blot {
  position: absolute;
  color: var(--entry-ink);
  animation: blot 0.7s ease-out forwards;
}

@keyframes ink-in {
  from {
    opacity: 0;
    filter: blur(3px);
  }
}

@keyframes blot {
  to {
    opacity: 0;
    filter: blur(4px);
    transform: scale(1.6, 1.3);
  }
}

.count {
  margin: -0.2rem 0 0;
  font-size: 0.85rem;
  color: var(--ink-soft);
}

.inks {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin: 0;
  padding: 0;
  border: 0;
}

.inks legend {
  width: 100%;
  margin-bottom: 0.3rem;
  padding: 0;
  font-size: 0.95rem;
}

.swatch {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  min-height: 2.75rem;
  padding: 0.3rem 0.65rem 0.3rem 0.45rem;
  font-size: 0.9rem;
  color: var(--entry-ink);
  border: 1px solid var(--rule);
  border-radius: 2rem;
  background: #fbf8f2;
  cursor: pointer;
}

.swatch input {
  margin: 0;
  accent-color: var(--entry-ink);
}

.swatch-chip {
  width: 1rem;
  height: 1rem;
  border-radius: 50%;
  background: var(--entry-ink);
}

.swatch:has(input:checked) {
  border-color: var(--entry-ink);
  box-shadow: inset 0 0 0 1px var(--entry-ink);
}

.swatch:has(input:focus-visible) {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
}

.swatch input:focus-visible {
  outline: none;
}

button {
  font: inherit;
  font-size: 1rem;
  min-height: 2.75rem;
  padding: 0.5rem 1.1rem;
  border: 1px solid var(--ink);
  background: var(--ink);
  color: var(--paper);
  width: fit-content;
  cursor: pointer;
}

/* A secondary action, quieter than the primary: giving up a seal. */
.button-quiet {
  border-color: var(--ink-soft);
  background: transparent;
  color: var(--ink);
}

.button-quiet:hover {
  background: var(--paper-shade);
  color: var(--ink);
}

button[aria-disabled="true"] {
  cursor: progress;
  opacity: 0.75;
}

button:hover {
  background: #46403a;
}

.count--over {
  color: var(--ink);
  font-weight: bold;
}

.form-error {
  color: var(--ink);
  font-weight: bold;
}

.notice {
  padding: 0.5rem 0.75rem;
  border-left: 3px solid var(--moss);
}

/* Seal chooser. */
.seal-search {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 0.5rem;
  margin: 1rem 0;
}

.seal-search label {
  width: 100%;
}

.seal-search input {
  flex: 1 1 12rem;
  min-height: 2.75rem;
  padding: 0.4rem 0.6rem;
  font: inherit;
  border: 1px solid var(--ink-soft);
  background: #fbf8f2;
  color: var(--ink);
}

.seal-themes h2,
.seal-count {
  margin: 1.5rem 0 0.4rem;
  font-size: 1.1rem;
}

.seal-themes ul {
  display: flex;
  flex-wrap: wrap;
  gap: 0 1rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Each theme link is a 44px-tall target. */
.seal-themes a,
.seal-pages a {
  display: inline-block;
  padding-block: 0.65rem;
}

.seal-themes a[aria-current="page"] {
  font-weight: bold;
  text-decoration-thickness: 0.15em;
}

.seal-pages {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0 1.25rem;
  margin-top: 0.5rem;
  color: var(--ink-soft);
}

/* Results as a grid of compact seal cards, so a page of sixty fits in a
   few screens; each card keeps a 44px Take button. */
.seal-results {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(8.5rem, 1fr));
  gap: 0.5rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* The card's own text is the pinyin and gloss, small; the glyph is its
   own block above. */
.seal-result {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.15rem;
  font-size: 0.85rem;
  line-height: 1.3;
  padding: 0.5rem 0.6rem 0.6rem;
  border: 1px solid var(--rule);
  background: rgb(251 248 242 / 60%);
}

.seal-result .result-glyph {
  font-size: 1.9rem;
  line-height: 1.1;
}

.seal-result em {
  margin-top: auto;
}

.seal-result--taken {
  color: var(--ink-soft);
}

.seal-result--yours {
  border-color: var(--ink-soft);
}

.claim-form {
  margin-top: auto;
  padding-top: 0.3rem;
}

.claim-form button {
  padding-inline: 0.8rem;
}

/* Phone: two columns of short cards, the glyph beside its meaning and the
   Take button under both. */
@media (width < 48rem) {
  .seal-results {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0.35rem;
  }

  .seal-result {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    align-items: center;
    gap: 0.1rem 0.5rem;
    padding: 0.4rem 0.45rem 0.45rem;
  }

  .seal-result .result-glyph {
    grid-row: span 2;
    font-size: 1.6rem;
  }

  .seal-result .claim-form,
  .seal-result em {
    grid-column: 2;
    padding-top: 0;
  }
}

#results,
#after-scroll {
  scroll-margin-top: 1rem;
}

.seal-count:focus {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
}

.release-form {
  margin: 1.5rem 0;
}

footer {
  margin-top: 2.5rem;
  padding-top: 1rem;
  border-top: 1px solid var(--rule);
  color: var(--ink-soft);
  font-size: 0.9rem;
}

.prose h2 {
  margin-top: 2rem;
}

.prose blockquote {
  margin: 0;
  padding-left: 1rem;
  border-left: 3px solid var(--ink-soft);
  color: var(--ink-soft);
}

/* The lantern: a reward for an accepted line, never for a failed click.
   Only transform and opacity animate; the sky clips its own overflow. */
.lantern-sky {
  position: fixed;
  inset: 0;

  /* Lanterns rise over the page, never over the dock. */
  bottom: calc(var(--dock-room, 0px) + var(--dock-lift, 0px));
  z-index: 50;
  overflow: hidden;
  pointer-events: none;
}

.lantern {
  position: absolute;
  width: 5.4rem;
  height: 7.5rem;
  animation: lantern-rise 4s cubic-bezier(0.4, 0.1, 0.6, 1) forwards;
}

.lantern::before {
  content: "";
  position: absolute;
  inset: -1.5rem;
  border-radius: 50%;
  background: radial-gradient(closest-side, rgb(241 194 122 / 55%), transparent);
}

.lantern svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

.lantern-text {
  position: absolute;
  top: 1.25rem;
  left: 0.75rem;
  right: 0.75rem;
  height: 4.4rem;
  overflow: hidden;
  font-size: var(--lantern-size, 0.6rem);
  line-height: 1.2;
  text-align: center;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  color: var(--entry-ink);
  mask-image: linear-gradient(#000 70%, transparent);
}

.lantern-seal {
  position: absolute;
  bottom: 1.25rem;
  left: 50%;
  translate: -50% 0;
  font-size: 0.6rem;
  line-height: 1;
  padding: 0.05rem 0.1rem;
  color: var(--ink-soft);
  border: 1px solid var(--ink-soft);
}

.lantern--faint {
  width: 2rem;
  height: 2.8rem;
  opacity: 0.35;
  animation-duration: 7s;
}

@keyframes lantern-rise {
  0% {
    opacity: 0;
    transform: translate(0, 0) scale(1);
  }

  10% {
    opacity: 1;
  }

  70% {
    opacity: 0.9;
  }

  100% {
    opacity: 0;
    transform: translate(var(--drift, 1.5rem), calc(-1 * var(--rise, 60vh))) scale(0.7);
  }
}

/* The fan: the page opens through a fan-shaped opening pivoting at the
   bottom centre, with faint pleat ribs. Content stays in the DOM and
   readable underneath; the veil only covers it visually, and is hidden
   (and, with script, removed) once open. */
.fan-veil,
.fan-ribs {
  position: fixed;
  inset: 0;
  z-index: 100;
  pointer-events: none;
}

.fan-veil {
  background: var(--silk-dark);
  mask-image: conic-gradient(from -90deg at 50% 100%, transparent 0 var(--fan), #000 var(--fan));
  animation: fan-open 1.4s ease-out forwards;
}

.fan-ribs {
  background: repeating-conic-gradient(from -90deg at 50% 100%, rgb(185 160 106 / 45%) 0 0.35deg, transparent 0.35deg 9deg);
  mask-image: conic-gradient(from -90deg at 50% 100%, #000 0 var(--fan), transparent var(--fan));
  animation: fan-ribs 1.4s ease-out forwards;
}

@keyframes fan-open {
  0% {
    --fan: 0deg;
  }

  85% {
    --fan: 180deg;
    opacity: 1;
  }

  100% {
    --fan: 180deg;
    opacity: 0;
    visibility: hidden;
  }
}

@keyframes fan-ribs {
  0% {
    --fan: 0deg;
    opacity: 1;
  }

  85% {
    --fan: 180deg;
  }

  100% {
    --fan: 180deg;
    opacity: 0;
    visibility: hidden;
  }
}

.fan-seen .fan-veil,
.fan-seen .fan-ribs {
  display: none;
}


/* The scroll page: no hanging mount. A compact paper strip for the header,
   the handscroll laid across the wall, and a paper label slip under it for
   the note, the legend and the tradition. Everything with text sits on
   paper: --ink-soft on the plaster would fall under 4.5:1. */
.mount--wall {
  max-width: none;
  padding: 0.9rem 0 1.5rem;
}

.page-scroll .site-header,
.page-scroll .section-note,
.page-scroll .seal-legend,
.page-scroll .tradition {
  background-color: var(--paper);
  background-image:
    repeating-linear-gradient(0deg, rgb(235 227 211 / 60%) 0 1px, transparent 1px 0.35rem),
    repeating-linear-gradient(90deg, rgb(235 227 211 / 45%) 0 1px, transparent 1px 0.3rem);
}

/* Fallback paper for the scroll if scroll.css hasn't loaded; scroll.css
   uses the same selectors and comes later, so it replaces all of this. */
.handscroll {
  margin: 0 0.5rem;
  padding: 0.25rem 1rem;
  background-color: var(--paper);
}

.site-header--compact {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.1rem 1rem;
  max-width: 72rem;
  margin: 0 auto 1.1rem;
  padding: 0.55rem 1.1rem;
  overflow: hidden;
  box-shadow:
    0 0 0 1px var(--gilt),
    0 0.15rem 0.5rem rgb(0 0 0 / 18%);
}

.site-header--compact::before {
  inset: 0;
  background-position: center 20%;
  background-size: 100% auto;
  opacity: 0.22;
}

.site-header--compact h1 {
  font-size: 1.6rem;
}

.site-header--compact .kicker {
  margin: 0;
}

.site-header--compact nav {
  margin-left: auto;
}

.page-scroll .section-note,
.page-scroll .seal-legend {
  max-width: 52rem;
  margin: 0 auto;
  padding: 0 1.1rem;
}

.page-scroll .section-note {
  margin-top: 1.4rem;
  padding-top: 0.7rem;
  box-shadow: 0 -1px 0 var(--gilt);
}

.page-scroll .seal-legend {
  padding-bottom: 0.15rem;
  box-shadow: 0 1px 0 var(--gilt);
}

.tradition {
  max-width: 52rem;
  margin: 1.25rem auto 0;
  padding: 0.75rem 1.1rem;
  box-shadow: 0 0 0 1px var(--gilt);
}

.tradition img {
  display: block;
  width: 100%;
  height: 7rem;
  object-fit: cover;
  object-position: 60% 50%;
  border: 1px solid var(--rule);
}

.tradition figcaption {
  margin-top: 0.5rem;
}

@media (width < 48rem) {
  .site-header--compact {
    margin-inline: 0.5rem;
    padding: 0.45rem 0.8rem 0.55rem;
  }

  .site-header--compact h1 {
    font-size: 1.4rem;
  }

  .site-header--compact nav {
    flex-basis: 100%;
    margin-left: 0;
    font-size: 0.9rem;
  }

  .page-scroll .section-note,
  .page-scroll .seal-legend,
  .tradition {
    margin-inline: 0.5rem;
  }

  /* On a first phone screen the scroll and its cue fill the room above the
     dock; the note starts below the fold rather than being sliced by it. */
  .page-scroll .section-note {
    margin-top: 3rem;
  }

  .tradition img {
    height: 5.5rem;
  }
}

/* The frontispiece at the oldest end of the scroll: the title written
   large and vertical in plain ink, the name and year small beside it. Never
   --seal; it belongs to nobody. The scroll lays the list and it in one row
   (scroll.css owns .scroll; this rule only applies when a frontispiece is
   there, and can move into scroll.css). */
.scroll:has(> .yinshou) {
  display: flex;
  align-items: stretch;
}

.scroll:has(> .yinshou) > .colophon-list {
  flex: none;
}

.yinshou {
  display: flex;
  flex: none;
  flex-direction: row-reverse;
  align-items: flex-start;
  justify-content: center;
  gap: 0.6rem;
  width: 9rem;
  padding: 2rem 1.25rem;
  border-left: 1px solid rgb(185 160 106 / 45%);
}

.yinshou-title {
  writing-mode: vertical-rl;
  font-size: 3.2rem;
  line-height: 1;
  letter-spacing: 0.35em;
  color: var(--ink);
}

.yinshou-name,
.yinshou-begun {
  writing-mode: vertical-rl;
  font-size: 0.85rem;
  letter-spacing: 0.08em;
  color: var(--ink-soft);
}

.yinshou-name {
  font-style: italic;
}

/* The dock: the writing form, fixed to the bottom of the viewport by CSS
   alone, outside the scroll's flow. A paper panel under a strip of mount
   silk; the body is padded by --dock-h so nothing stays covered. Grows
   upward only, so the button never moves under a finger mid-tap. */
body.page-scroll {
  padding-bottom: calc(var(--dock-room, var(--dock-h)) + var(--dock-safe) + 0.75rem);
}

.dock {
  position: fixed;
  inset-inline: 0;
  bottom: var(--dock-lift, 0px);
  z-index: 40;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  grid-template-areas:
    "error error error"
    "well inks button"
    "count seal seal";
  align-items: center;
  gap: 0.35rem 0.9rem;
  padding: 0.7rem max(1rem, calc((100% - 72rem) / 2)) calc(0.8rem + env(safe-area-inset-bottom, 0px));
  background-color: var(--paper);
  background-image:
    repeating-linear-gradient(0deg, rgb(235 227 211 / 60%) 0 1px, transparent 1px 0.35rem),
    repeating-linear-gradient(90deg, rgb(235 227 211 / 45%) 0 1px, transparent 1px 0.3rem);
  border-top: 1px solid var(--gilt);
  box-shadow:
    0 -0.35rem 0 var(--silk),
    0 -0.4rem 0 var(--silk-dark),
    0 -0.4rem 1.2rem rgb(0 0 0 / 25%);
}

.dock .write-form {
  display: contents;
}

.dock .form-error {
  grid-area: error;
  margin: 0;
}

.dock .inkwell {
  grid-area: well;
}

.dock textarea {
  min-height: 4.2rem;
  max-height: 7.5rem;
  padding: 0.45rem 0.6rem;
}

.dock .inks {
  grid-area: inks;
  flex-wrap: nowrap;
  gap: 0.3rem;
}

/* In the dock the chip is the radio: the native circle is laid invisibly
   over the whole swatch (so the hit area is the swatch), and the checked
   ring and focus outline are drawn on the label. */
.dock .swatch {
  position: relative;
  padding-inline: 0.55rem 0.7rem;
  font-size: 0.85rem;
  white-space: nowrap;
}

.dock .swatch input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  cursor: pointer;
}

.dock .swatch:has(input:checked) {
  box-shadow: inset 0 0 0 2px var(--entry-ink);
}

.dock .swatch:has(input:checked) .swatch-chip {
  box-shadow: 0 0 0 2px #fbf8f2, 0 0 0 3.5px var(--entry-ink);
}

.dock button[type="submit"] {
  grid-area: button;
  justify-self: end;
  white-space: nowrap;
}

.dock .count {
  grid-area: count;
  margin: 0;
}

.dock-seal {
  grid-area: seal;
  justify-self: end;
  margin: 0;
  font-size: 0.85rem;
  color: var(--ink-soft);
}

/* A 44px-tall hit area without making the seal line any taller. */
.dock-seal a {
  display: inline-block;
  padding: 0.75rem 0.4rem;
  margin: -0.75rem -0.4rem;
  color: var(--ink);
}

.dock-glyph {
  display: inline-block;
  padding: 0 0.15rem;
  font-size: 1.05rem;
  line-height: 1.2;
  color: var(--ink);
  border: 1px solid var(--ink-soft);
  border-radius: 2px;
}

.dock-glyph--chosen {
  color: var(--paper);
  background: var(--ink-soft);
}

/* Tablet and narrow desktop: the text gets its own row. */
@media (48rem <= width < 76rem) {
  :root {
    --dock-h: 10.5rem;
  }

  .dock {
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas:
      "error error"
      "well well"
      "inks button"
      "count seal";
  }

  .dock textarea {
    min-height: 2.75rem;
  }
}

/* Phone, or any short viewport (em-based, so large text or zoom counts):
   compact until focused. One line of text, five 44px ink chips beside the
   button, the seal line under them. Focus anywhere in the dock opens the
   text to three lines and shows the count beside the seal line; the rows
   below the button keep their height, so it stays put. */
@media (width < 48rem), (height < 40em) {
  :root {
    --dock-h: 9.25rem;
  }

  .dock {
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas:
      "error error"
      "well well"
      "inks button"
      "seal count";
    gap: 0.4rem 0.5rem;
    padding-inline: 0.75rem;
  }

  .dock textarea {
    min-height: 2.75rem;
    max-height: 2.75rem;
    padding-block: 0.4rem;
    overflow-y: auto;
  }

  .dock:focus-within textarea {
    min-height: 5.6rem;
    max-height: 5.6rem;
  }

  .dock .inks {
    flex-wrap: nowrap;
    gap: 0.25rem;
  }

  .dock .swatch {
    position: relative;
    justify-content: center;
    width: 2.75rem;
    height: 2.75rem;
    padding: 0;
  }

  .dock .swatch-chip {
    width: 1.5rem;
    height: 1.5rem;
  }

  .dock .swatch-name {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  .dock .count {
    display: none;
    justify-self: end;
    font-size: 0.8rem;
  }

  .dock:focus-within .count,
  .dock .count--over {
    display: block;
  }

  .dock-seal {
    justify-self: start;
    font-size: 0.8rem;
  }
}

/* Short viewports (landscape phones, large text or 200% zoom): one row,
   text, chips, button. The seal line and count come back while writing,
   when focus is in the dock; the Change link follows the text in Tab
   order, so it's shown by the time Tab reaches it. */
@media (height < 40em) and (width >= 34em) {
  :root {
    --dock-h: 4.5rem;
  }

  .dock {
    grid-template-columns: minmax(0, 1fr) auto auto;
    grid-template-areas:
      "error error error"
      "well inks button"
      "seal seal count";
    padding-block: 0.5rem calc(0.5rem + env(safe-area-inset-bottom, 0px));
  }

  .dock-seal,
  .dock .count {
    display: none;
  }

  .dock:focus-within .dock-seal,
  .dock:focus-within .count,
  .dock .count--over {
    display: block;
  }

  .dock:focus-within textarea {
    min-height: 2.75rem;
    max-height: 5.6rem;
  }
}

/* Very narrow phones: the chips shrink their gap before anything else. */
@media (width < 23rem) {
  .dock {
    padding-inline: 0.5rem;
  }

  .dock .inks {
    gap: 0.1rem;
  }

  .dock button[type="submit"] {
    padding-inline: 0.7rem;
  }
}

@media (width <= 600px) {
  .mount {
    padding-top: 0.75rem;
  }

  .mount-silk {
    margin: 0 0.4rem;
    padding: 1.1rem 0.55rem;
  }

  .sheet {
    padding: 1.4rem 1rem;
    margin-bottom: 1.25rem;
  }

  .site-header::before {
    inset: -1.4rem -1rem 0;
  }

}

@media (prefers-reduced-motion: reduce) {
  .fan-veil,
  .fan-ribs,
  .lantern-sky {
    display: none;
  }

  .ink-ch,
  .colophon--arriving,
  .colophon--arriving .colophon-body,
  .colophon--arriving .colophon-seal summary {
    animation: none;
  }

  .ink-blot {
    display: none;
  }
}

/* Forced colours: the chosen ink is otherwise marked only by its own
   colour, which the system palette replaces. */
@media (forced-colors: active) {
  .swatch:has(input:checked) {
    outline: 3px solid Highlight;
    outline-offset: 1px;
  }

  .scroll-overview {
    forced-color-adjust: none;
  }
}

@media (prefers-contrast: more) {
  body,
  .sheet,
  .mount-silk {
    background-image: none;
  }

  .site-header::before {
    display: none;
  }
}

@media print {
  html,
  body {
    background: #fff;
  }

  .roller,
  .fan-veil,
  .fan-ribs,
  .lantern-sky,
  .dock {
    display: none;
  }

  .mount-silk,
  .sheet {
    margin: 0;
    padding: 0;
    background: none;
    box-shadow: none;
  }

  .site-header::before {
    display: none;
  }
}
