/*
  The card file.

  One card is a fixed 5:3 rectangle of warm stock. Everything on it sizes
  itself from the card's own width, so a card looks identical at any size.
*/

@font-face {
  font-family: 'Cutive Mono';
  font-style: normal;
  font-weight: 400;
  font-display: block;
  src: url('fonts/cutive-mono-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
                 U+02DA, U+02DC, U+2000-206F, U+20AC, U+2122, U+2191,
                 U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Cutive Mono';
  font-style: normal;
  font-weight: 400;
  font-display: block;
  src: url('fonts/cutive-mono-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
                 U+02DD-02FF, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF,
                 U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F,
                 U+A720-A7FF;
}

:root {
  /* The room the cards sit in. */
  --dark: #100d0a;

  /* The stock. One paper, used by every card and every sheet. */
  --stock-top: #f0e5cc;
  --stock-bottom: #e6d7b8;

  /* Ink is a warm brown black, never pure black. */
  --ink: #2b2116;
  --ink-muted: #8a7048;
  --rule-red: #b0342a;

  --card-radius: 3px;
}


/* ---- The room ------------------------------------------------------- */

html {
  background: var(--dark);
  color: var(--ink);
  font-family: 'Cutive Mono', ui-monospace, Consolas, monospace;
}

body {
  margin: 0;
  min-height: 100vh;
  background:
    radial-gradient(58% 62% at 50% 46%, rgba(255, 226, 168, 0.10), transparent 70%),
    var(--dark);
}


/* ---- The card ------------------------------------------------------- */

.card {
  container-type: inline-size;
  position: relative;
  width: 480px;
  aspect-ratio: 5 / 3;
  border-radius: var(--card-radius);
  background: linear-gradient(to bottom, var(--stock-top), var(--stock-bottom));
  overflow: hidden;
}

/*
  Aging. One of four profiles, assigned per card and never changed.

  No blend mode. Multiply forces the browser to read back what is behind
  the layer, and with a card that also carries a filter inside a 3D
  transform that is what was drawing stray seams across the paper. These
  are warm browns over cream, so plain compositing lands in much the same
  place for a fraction of the work.
*/
.card::before {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 2;
}

/*
  Grain. Identical on every card, whatever its age.

  A flat image, made by tools/make_grain.py. It was a live SVG turbulence
  filter, which every card ran its own copy of; that was the bulk of what
  made the page stall. The noise is in the alpha channel over a fixed
  brown, so no blend mode is needed to keep the stock from going grey.
  The tile is pinned to a fixed pixel size: paper fibre does not get
  bigger when the card does.
*/
.card::after {
  content: '';
  position: absolute;
  inset: 0;
  opacity: 0.38;
  pointer-events: none;
  z-index: 3;
  background-image: url('grain.png');
  background-size: 192px 192px;
}

.card-inner {
  position: absolute;
  inset: 0;
  z-index: 1;
  padding: 18px 22px;     /* the paper margin, set on its own */
  display: flex;
}

/*
  The column fills the card. It used to be pinned to exactly 44ch, which
  quietly tied three things together: the type size set the column width,
  and the column width set the paper margin. 44 by 9 is a limit on what a
  card may hold, not a width the column has to be. The build still refuses
  a line over 44 characters; the column just has to be wide enough to take
  one, and at 436px it takes one with room to spare.

  So the card size, the type size and the margin are now independent.
*/
.card-col {
  width: 100%;
  font-size: 13.3333px;   /* 10pt */
  display: flex;
  flex-direction: column;
}


/* ---- Header, rule, body, footer ------------------------------------- */

.card-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
}

/*
  The name is one point larger than the body and no more. On a typewriter
  a heading is the same type as the text, so the only thing separating
  them here is a hair of size and the red rule underneath.
*/
.card-name {
  margin: 0;              /* it is an h2, and the default margin overflows the card */
  font-size: 16px;        /* 12pt */
  font-weight: 400;       /* the typeface has one weight; do not let the browser fake one */
  line-height: 1.2;
  letter-spacing: 0.005em;
  white-space: nowrap;    /* a name never wraps; the build refuses one that would */
  text-shadow: 0 0 0.6px rgba(43, 33, 22, 0.34);
}

.card-code {
  flex: none;
  font-size: 16px;        /* 12pt, same as the name */
  line-height: 1.2;
  letter-spacing: 0.16em;
  color: var(--ink-muted);
}

.card-rule {
  margin-top: 6px;
  border-top: 1px solid var(--rule-red);
}

.card-body {
  margin-top: 9px;
  line-height: 1.3;
  white-space: pre-wrap;
  text-shadow: 0 0 0.6px rgba(43, 33, 22, 0.3);
}

.card-foot {
  margin-top: auto;
  padding-top: 4px;
  text-align: right;
  font-size: 16px;        /* 12pt, the size of the name and the code */
  line-height: 1.2;
  color: var(--ink-muted);
}


/* ---- The quote card ------------------------------------------------- */

/*
  A quote is set as large as the card will take and the build works out how
  large that is, so the type size arrives per card in --quote-size. A short
  quote fills the face; a long one steps down until it fits. The card never
  grows to meet the words.

  The mark stands ahead of the first line rather than over it, which is why
  the words give up a column on the left. At the full width of the card
  there is nowhere for it to stand.
*/
.card-quote .card-col { justify-content: flex-start; }

.quote-face {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
}

.quote-block {
  display: flex;
  align-items: flex-start;
  gap: 16px;
  margin: auto 0;          /* sits in the middle of what is left */
}

/*
  Cutive Mono has no curly quote, so this is two upright strokes. Their ink
  is about a fifth of the em box and sits high in it, so the words are
  dropped 13px to bring the strokes level with the first line. Measured off
  the rendered card: the mark's red ink then starts 3.5px above the top of
  the letters, which is where hanging punctuation sits.

  The drop is on the words rather than a lift on the mark, which is how it
  was first written. Lifting the mark hangs it above the block, and a quote
  tall enough to fill the face then puts its ink back on the red rule.
*/
.quote-mark {
  flex: none;
  font-size: 84px;
  line-height: 1;
  color: var(--rule-red);
}

.quoted {
  flex: 1;
  min-width: 0;
  margin-top: 13px;
  font-size: var(--quote-size, 18px);
  line-height: 1.34;
  white-space: pre-wrap;   /* the build did the wrapping, to its own measure */
  text-shadow: 0 0 0.6px rgba(43, 33, 22, 0.3);
}

.said {
  text-align: right;
  font-size: 13.3333px;    /* 10pt, same as a note's body */
  line-height: 1.2;
  color: var(--ink-muted);
}


/* ---- The four aging profiles ---------------------------------------- */
/*
  All four work only with large soft gradients. No hard edges, no spots.
  Variation is how much weather a card took, never a different paper.
*/

.age-barely::before {
  background:
    radial-gradient(118% 118% at 50% 50%,
      rgba(122, 94, 54, 0) 78%, rgba(122, 94, 54, 0.22) 100%);
}

/*
  Sun does not arrive as a straight band. The warmth along the top is made
  of three wide ellipses hung off the top edge at different heights and
  strengths, so the line where it fades is uneven the way a real sunned
  edge is. Still only large soft gradients, still no spots.
*/
.age-sunned::before {
  background:
    radial-gradient(86% 54% at 26% -16%,
      rgba(178, 128, 46, 0.36) 0%, rgba(178, 128, 46, 0) 72%),
    radial-gradient(58% 42% at 74% -9%,
      rgba(178, 128, 46, 0.27) 0%, rgba(178, 128, 46, 0) 70%),
    radial-gradient(120% 30% at 44% 1%,
      rgba(178, 128, 46, 0.15) 0%, rgba(178, 128, 46, 0) 80%),
    radial-gradient(126% 114% at 54% 44%,
      rgba(122, 94, 54, 0) 70%, rgba(122, 94, 54, 0.19) 100%);
}

.age-yellowed::before {
  background:
    radial-gradient(74% 66% at 86% 82%,
      rgba(158, 116, 46, 0.30) 0%, rgba(158, 116, 46, 0) 72%),
    radial-gradient(66% 60% at 10% 12%,
      rgba(158, 116, 46, 0.24) 0%, rgba(158, 116, 46, 0) 70%),
    radial-gradient(118% 118% at 50% 50%,
      rgba(122, 94, 54, 0) 72%, rgba(122, 94, 54, 0.18) 100%);
}

/*
  Handling wears a card unevenly. Two matched radials in the two bottom
  corners read as exactly what they are, so the thumbing is one big patch
  low left, a smaller one low right, and the perimeter darkening is a
  wide off-centre ellipse rather than a ring around the middle.
*/
.age-handled::before {
  background:
    radial-gradient(62% 46% at 4% 106%,
      rgba(104, 76, 42, 0.38) 0%, rgba(104, 76, 42, 0) 70%),
    radial-gradient(34% 30% at 91% 103%,
      rgba(104, 76, 42, 0.30) 0%, rgba(104, 76, 42, 0) 74%),
    radial-gradient(40% 72% at 104% 42%,
      rgba(104, 76, 42, 0.24) 0%, rgba(104, 76, 42, 0) 76%),
    radial-gradient(56% 34% at 34% -8%,
      rgba(104, 76, 42, 0.20) 0%, rgba(104, 76, 42, 0) 78%),
    radial-gradient(132% 106% at 43% 56%,
      rgba(104, 76, 42, 0) 50%, rgba(104, 76, 42, 0.31) 100%);
}


/* ---- The rail ------------------------------------------------------- */

#file {
  min-height: 100vh;
  display: grid;
  place-items: center;
}

/* Without scripts the fan is meaningless, so the cards just stack up. */
#rail {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 44px;
  padding: 64px 0;
}

html.js #rail {
  display: block;
  position: relative;
  width: 100%;
  height: 74vh;
  padding: 0;
  perspective: 1600px;
  /*
    No transform-style: preserve-3d here, and it must not come back.

    perspective is what gives the fan its depth and has to stay.
    preserve-3d only matters when children need to share one 3D space and
    intersect each other, which cards in a flat fan never do. What it does
    do is put every card in its own 3D rendering context, and so on its own
    composited layer, and Chromium draws faint seam lines at those layer
    boundaries. They showed on a repaint, so opening the reverse, closing
    it and mousing over the marker again brought them out reliably.

    Found on 2026-08-12 by switching one suspect off at a time on the real
    machine, after three wrong fixes aimed at the grain, the blend modes
    and the filters, none of which had anything to do with it. It never
    reproduced from a screenshot here: the display is P3 at 125% and a
    screenshot is taken before the compositor draws those layers out.
  */
}

html.js #rail .card {
  position: absolute;
  left: 50%;
  top: 50%;
  visibility: hidden;
  transform-origin: 50% 50%;
  transition:
    transform 0.42s cubic-bezier(.22, .7, .28, 1),
    opacity   0.42s cubic-bezier(.22, .7, .28, 1),
    filter    0.42s cubic-bezier(.22, .7, .28, 1);
}

.card {
  box-shadow:
    0 22px 48px rgba(0, 0, 0, 0.55),
    0 3px 9px rgba(0, 0, 0, 0.42);
}

html.js #rail .card:not(.focused) {
  cursor: pointer;
}


/* ---- An image on the card ------------------------------------------- */

/* A photograph or a drawing. Either way the card carries no text under the
   rule, so the column starts at the top and the image takes the rest. */
.card-image .card-col {
  justify-content: flex-start;
}

/* Nothing stands above the print now, so it just centres in the space. */
.print-assembly {
  position: relative;
  margin: auto;
  transform: rotate(-1.5deg);
}

.print-landscape { width: 44cqw; }
.print-portrait  { width: 27cqw; }

/*
  Photographic paper is brighter and cooler than card stock. It has to be
  or its edge vanishes into the card, and then the clip has no visible
  edge to grip and reads as lying flat on the surface. The wider border
  gives the clip some paper to hold rather than sitting on the image.
*/
.print {
  position: relative;
  z-index: 2;
  padding: 7%;
  background: linear-gradient(to bottom, #fbf4e4, #efe4ca);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.8),
    0 7px 16px rgba(28, 18, 6, 0.4),
    0 1px 3px rgba(28, 18, 6, 0.36);
  cursor: pointer;
}

.print img {
  display: block;
  width: 100%;
  height: auto;
}

/*
  Corner mounts. Gummed paper triangles, the way a print goes into an
  album: the corner of the photograph slides into the pocket and the
  mount is stuck to the page. Sized off the card, not off the print, so
  a landscape card and a portrait one carry the same object.
*/
.corner {
  position: absolute;
  z-index: 3;
  width: 3.8cqw;
  aspect-ratio: 1;
  pointer-events: none;
  background:
    linear-gradient(135deg, #4a3a26 0%, #33281a 58%, #211a10 100%);
  box-shadow: 0 1px 2px rgba(26, 16, 6, 0.42);
}

.corner-tl { top: 0;    left: 0;  clip-path: polygon(0 0, 100% 0, 0 100%); }
.corner-tr { top: 0;    right: 0; clip-path: polygon(100% 0, 100% 100%, 0 0); }
.corner-br { bottom: 0; right: 0; clip-path: polygon(100% 100%, 0 100%, 100% 0); }
.corner-bl { bottom: 0; left: 0;  clip-path: polygon(0 100%, 0 0, 100% 100%); }


/* ---- The drawing on the card ---------------------------------------- */

/*
  A slip, not a print. No paper border and no mounts: the sheet the drawing
  is on has its own edge, and that edge is the only thing separating this
  from a photograph.

  The width comes from the drawing's own proportions, set per card by the
  build, because a sketch may be any shape. Height is what runs out first
  on a card this wide, so max-height holds it and the width only matters
  for a panorama. 92% rather than 100% is the room the tilt costs: turned
  1.5 degrees, a slip needs its own height plus its width times sin(1.5),
  which at the widest shape is about 5% more than it started with.
*/
.slip {
  margin: auto;
  transform: rotate(-1.5deg);
  box-shadow:
    0 5px 13px rgba(28, 18, 6, 0.42),
    0 1px 3px rgba(28, 18, 6, 0.34);
  cursor: pointer;
  line-height: 0;
}

/* The drawing's own paper is left exactly as it arrives. Tinting it toward
   the cream washes the ink out, and a blend mode is what drew the
   hairlines, so neither is available. */
.slip img {
  display: block;
  width: 100%;
  height: auto;
}


/* ---- The two controls ----------------------------------------------- */

#controls {
  position: fixed;
  left: 26px;
  bottom: 22px;
  display: flex;
  gap: 14px;
  z-index: 60;
}

#controls button {
  width: 27px;
  height: 27px;
  padding: 0;
  border: 0;
  background: none;
  color: #c3b291;
  opacity: 0.26;
  cursor: pointer;
  transition: opacity 0.22s ease;
}

#controls button:hover,
#controls button:focus-visible { opacity: 0.72; }

#controls svg {
  width: 100%;
  height: 100%;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
}

#controls svg .dot { fill: currentColor; stroke: none; }


/* ---- The filter note ------------------------------------------------ */

#filter-note {
  position: fixed;
  left: 50%;
  bottom: 26px;
  transform: translateX(-50%);
  display: flex;
  gap: 14px;
  align-items: baseline;
  z-index: 60;
  font-size: 13px;
  letter-spacing: 0.13em;
  color: #c3b291;
  opacity: 0.34;
}

#filter-note button {
  border: 0;
  background: none;
  padding: 0;
  font: inherit;
  letter-spacing: inherit;
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 0.3em;
  text-decoration-color: rgba(195, 178, 145, 0.5);
  cursor: pointer;
}

#filter-note:hover { opacity: 0.62; }

#filter-note[hidden] { display: none; }


/* ---- Overlays ------------------------------------------------------- */

.overlay {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: grid;
  place-items: center;
  padding: 40px;
  background: rgba(10, 8, 6, 0.62);
}

.overlay[hidden] { display: none; }

body.behind #file,
body.behind #controls,
body.behind #filter-note {
  filter: blur(3.5px) brightness(0.46);
  transition: filter 0.3s ease;
}


/* ---- The reverse ---------------------------------------------------- */

/*
  Fixed to the measure the text is written to, 68 characters, rather than a
  share of the window. The body is pre-wrap so authored line breaks are
  honoured, and if the sheet were narrower than the lines were written to
  every one of them would wrap a second time and leave orphans. 68
  characters at 16.5px plus the padding comes to 792.

  The extra 16 is the scrollbar. A reverse long enough to scroll takes its
  scrollbar out of the content box, which left 673px for a line written to
  need 680, so the longest lines wrapped a second time exactly when the
  reverse was long enough for anyone to notice. The gutter is reserved
  whether the sheet scrolls or not, so the measure does not change when it
  starts to.
*/
.sheet {
  width: 808px;
  max-width: 92vw;
  aspect-ratio: auto;
  max-height: 80vh;
  display: flex;
  container-type: normal;
}

.sheet-inner {
  position: relative;
  z-index: 1;
  padding: 46px 54px 50px;
  overflow-y: auto;
  scrollbar-gutter: stable;
  width: 100%;
  font-size: 16.5px;

  scrollbar-width: thin;
  scrollbar-color: #4a3a26 rgba(139, 112, 72, 0.17);
}

.sheet-inner::-webkit-scrollbar { width: 9px; }
.sheet-inner::-webkit-scrollbar-track {
  background: rgba(139, 112, 72, 0.17);
}
.sheet-inner::-webkit-scrollbar-thumb {
  background: #4a3a26;
  border-radius: 0;
}
.sheet-inner::-webkit-scrollbar-thumb:hover { background: #3a2c1c; }

.sheet-name {
  margin: 0;
  font-size: 1.34em;
  font-weight: 400;
  line-height: 1.2;
}

/*
  cqw only means something inside a card. On these sheets there is no
  container, so it falls back to the viewport and the rule came out three
  times too thick. Pin it to the same hairline the cards use.
*/
.sheet .card-rule,
.photo-sheet .card-rule,
.single-reverse .card-rule { border-top-width: 1px; }

.sheet .card-rule { margin-top: 12px; }

/*
  The same leading as a card face, 1.3. It was 1.72, chosen for reading a
  wall of text, and measured against the front it made the reverse read as
  a different document rather than the back of the same card. Nothing else
  on the site is set looser than 1.34.
*/
.sheet-body {
  margin-top: 22px;
  line-height: 1.3;
  white-space: pre-wrap;
  text-shadow: 0 0 0.6px rgba(43, 33, 22, 0.3);
}

.sheet-body a {
  color: #7d3323;
  text-underline-offset: 0.22em;
  text-decoration-color: rgba(125, 51, 35, 0.42);
}


/* ---- The photograph, opened ----------------------------------------- */

.photo-sheet {
  width: auto;
  max-width: 86vw;
  aspect-ratio: auto;
  padding: 30px 30px 26px;
  display: flex;
  flex-direction: column;
  container-type: normal;
}

.photo-mount {
  position: relative;
  z-index: 1;
  display: flex;
  justify-content: center;
}

.photo-sheet img {
  display: block;
  max-height: 64vh;
  max-width: 100%;
  width: auto;
  height: auto;
  box-shadow: 0 4px 14px rgba(24, 15, 4, 0.34);
}

.photo-sheet .card-rule {
  position: relative;
  z-index: 1;
  margin-top: 24px;
}

.photo-caption {
  position: relative;
  z-index: 1;
  margin: 14px 0 0;
  max-width: 62ch;
  font-size: 15px;
  line-height: 1.62;
  color: #4a3a26;
}


/* ---- The index card ------------------------------------------------- */

.index-sheet {
  aspect-ratio: 5 / 4.5;
}

.index-sheet .card-col {
  height: 100%;
}

.index-sheet .card-name {
  display: flex;
  align-items: center;
  gap: 0.5em;
  margin: 0;
  font-weight: 400;
}

#toggle-search {
  font: inherit;          /* buttons do not inherit it, and em depends on it */
  width: 0.78em;
  height: 0.78em;
  padding: 0;
  border: 0;
  background: none;
  color: var(--ink-muted);
  cursor: pointer;
  opacity: 0.7;
}

#toggle-search:hover { opacity: 1; }

#toggle-search svg {
  width: 100%;
  height: 100%;
  fill: none;
  stroke: currentColor;
  stroke-width: 2.1;
  stroke-linecap: round;
}

#search-row { margin-top: 1.6cqw; }

#search {
  width: 100%;
  padding: 0.3em 0;
  border: 0;
  border-bottom: 1px solid rgba(138, 112, 72, 0.5);
  background: none;
  font: inherit;
  color: var(--ink);
  outline: none;
}

#index-types,
#search-results {
  list-style: none;
  margin: 2cqw 0 0;
  padding: 0;
  flex: 1;
  overflow-y: auto;

  scrollbar-width: thin;
  scrollbar-color: #4a3a26 rgba(139, 112, 72, 0.17);
}

.index-type,
.search-hit {
  display: flex;
  width: 100%;
  justify-content: space-between;
  align-items: baseline;
  gap: 1em;
  padding: 0.34em 0;
  border: 0;
  background: none;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}

.index-type:hover span:first-child,
.search-hit:hover span:first-child {
  text-decoration: underline;
  text-underline-offset: 0.28em;
  text-decoration-color: rgba(176, 52, 42, 0.6);
}

.index-count {
  flex: none;
  font-size: 0.86em;
  letter-spacing: 0.14em;
  color: var(--ink-muted);
}

.no-hits {
  padding: 0.34em 0;
  color: var(--ink-muted);
}

.index-foot-rule { margin-top: 2cqw; }

#random {
  margin-top: 1.6cqw;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  color: inherit;
  text-align: center;
  cursor: pointer;
}

#random:hover {
  text-decoration: underline;
  text-underline-offset: 0.28em;
  text-decoration-color: rgba(176, 52, 42, 0.6);
}


/* ---- The guestbook -------------------------------------------------- */

/*
  The guestbook's card carries one thing to do, so the way in sits in the
  middle of the face rather than in the footer a reverse marker uses.

  Absolute against the card, not centred in the room left under the body:
  centred in what is left, the header's own height pushes it below the
  middle of the card, and it reads as slightly fallen.
*/
.gb-card-mark {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: grid;
  place-items: center;
  pointer-events: none;
}

.gb-card-mark .see-reverse { pointer-events: auto; }

/* The sheet holds a scrolling list above a form that stays put, so the
   sheet itself does not scroll the way a reverse does. */
.guestbook-sheet .sheet-inner {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  max-height: 74vh;
}

.gb-entries {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  margin-top: 22px;
  padding-right: 10px;
  scrollbar-width: thin;
  scrollbar-color: #4a3a26 rgba(139, 112, 72, 0.17);
}

/*
  When the list is longer than its box its last line is cut wherever the
  box ends. Cut hard against the foot rule that reads as a fault rather
  than as more text below, so the last 34px fades out and says so.

  Only when it actually overflows. Applied always, it fades out the last
  note on a short guestbook where nothing is below it, which reads as the
  same fault it was added to prevent. The script puts the class on.
*/
.gb-entries.gb-more {
  mask-image: linear-gradient(to bottom, #000 calc(100% - 34px), transparent 100%);
}

.gb-entries::-webkit-scrollbar { width: 9px; }
.gb-entries::-webkit-scrollbar-track { background: rgba(139, 112, 72, 0.17); }
.gb-entries::-webkit-scrollbar-thumb { background: #4a3a26; border-radius: 0; }

.gb-entry + .gb-entry { margin-top: 26px; }

.gb-by {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1em;
}

/* Who signed it, in the rule's red. It is what breaks the list into
   entries, which lets the dates stay quiet out in the margin. */
.gb-who { color: var(--rule-red); }

.gb-when {
  flex: none;
  font-size: 0.82em;
  letter-spacing: 0.1em;
  color: var(--ink-muted);
}

.gb-note {
  margin-top: 4px;
  line-height: 1.3;
  white-space: pre-wrap;
  overflow-wrap: anywhere;    /* a visitor can type one very long word */
  text-shadow: 0 0 0.6px rgba(43, 33, 22, 0.3);
}

/* Waiting, empty, or unreachable. All three are one plain line. */
.gb-quiet {
  margin: 0;
  line-height: 1.3;
  white-space: pre-line;
  color: var(--ink-muted);
}

.gb-foot-rule { margin-top: 24px; }

.gb-foot-rule[hidden] { display: none; }

/* No boxes: a label and a ruled line, the way a form is printed on paper.
   The same underline the index card's search box uses. */
.gb-form {
  margin-top: 20px;
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 10px 16px;
  align-items: baseline;
}

.gb-form[hidden] { display: none; }

.gb-form label { color: var(--ink-muted); }

.gb-form input,
.gb-form textarea {
  width: 100%;
  /* The note line is grown to fit by the script, which sets a height in
     pixels. Content-box makes that height the content alone, so the
     padding is counted a second time and the line sits 6px too low. */
  box-sizing: border-box;
  padding: 0.16em 0;
  border: 0;
  border-bottom: 1px solid rgba(138, 112, 72, 0.5);
  background: none;
  font: inherit;
  line-height: 1.3;
  color: var(--ink);
  outline: none;
  resize: none;
  overflow: hidden;         /* it is grown to fit, so it never scrolls */
}

.gb-form input:focus,
.gb-form textarea:focus { border-bottom-color: var(--rule-red); }

/* Only a bot fills this in. Off screen rather than display:none, because
   some bots skip what is hidden outright. */
.gb-trap {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.gb-send {
  grid-column: 2;
  justify-self: end;
  margin-top: 2px;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  color: var(--rule-red);
  cursor: pointer;
}

.gb-send:hover,
.gb-send:focus-visible {
  text-decoration: underline;
  text-underline-offset: 0.3em;
  text-decoration-color: rgba(176, 52, 42, 0.65);
}

.gb-send[disabled] {
  color: var(--ink-muted);
  cursor: default;
  text-decoration: none;
}

.gb-status {
  grid-column: 2;
  margin: 0;
  color: var(--ink-muted);
}

.gb-status[hidden] { display: none; }

/* On its own page there is no overlay to size against, so the sheet takes
   the room the page gives it. */
.single .guestbook-sheet .sheet-inner { max-height: 78vh; }


/* ---- The see reverse marker ----------------------------------------- */

/*
  In the rule's red, with an arrowhead ahead of it, at the size the card's
  name and code are set at rather than the body's. A card with little on
  it and the whole poem behind it was giving no sign worth noticing, and
  in muted ink at body size the marker read as a footnote.

  12pt is not a new size on the card, it is the header's, so this borrows
  rather than introduces.
*/
.see-reverse {
  color: var(--rule-red);
  text-decoration: none;
  cursor: pointer;
}

/*
  The arrowhead is drawn, not typed. It is U+27A4, which the typeface does
  not have, so as a character it would be borrowed from whatever symbol
  font the machine happens to carry: a different shape on every machine
  and an empty box on one without it. Nine bytes of path is the same
  arrowhead everywhere. The swept back edge is what makes it that
  arrowhead rather than a plain triangle.
*/
.see-reverse svg {
  width: 0.86em;
  height: 0.72em;
  vertical-align: -0.02em;
  margin-right: 0.42em;
  fill: currentColor;
}

.see-reverse:hover {
  text-decoration: underline;
  text-underline-offset: 0.3em;
  text-decoration-color: rgba(176, 52, 42, 0.65);
}


/* ---- Card pages, and the floor without scripts ---------------------- */

.single {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 30px;
  padding: 60px 24px;
}

.single-reverse {
  width: 792px;              /* the same 68 character measure as the sheet */
  max-width: 92vw;
  padding: 28px 54px 34px;
  background: linear-gradient(to bottom, var(--stock-top), var(--stock-bottom));
  border-radius: var(--card-radius);
}

.single-back {
  margin: 0;
  font-size: 14px;
  letter-spacing: 0.1em;
  color: #c3b291;
  opacity: 0.4;
}

.single-back a {
  color: inherit;
  text-underline-offset: 0.3em;
}


/* ---- Too narrow ----------------------------------------------------- */

#too-small { display: none; }

@media (max-width: 999px) {
  #too-small {
    display: grid;
    place-items: center;
    min-height: 100vh;
    padding: 2.4rem;
  }
  #too-small p {
    max-width: 26rem;
    margin: 0;
    font-size: 16px;
    line-height: 1.75;
    color: #c9b896;
  }
  #file,
  #controls,
  #filter-note,
  .single { display: none !important; }
}


/* ---- Focus ----------------------------------------------------------- */
/*
  Keyboard focus has to be visible, but the browser default is a heavy
  black box, and it lands on the see reverse marker every time the sheet
  is closed with Escape. Text controls get the same red underline they
  get on hover; the two icon buttons get a thin red ring, since there is
  no text on them to underline.
*/

.see-reverse:focus-visible,
#random:focus-visible,
.index-type:focus-visible,
.search-hit:focus-visible,
#filter-note button:focus-visible,
.single-back a:focus-visible,
.sheet-body a:focus-visible {
  outline: none;
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.3em;
  text-decoration-color: var(--rule-red);
}

#controls button:focus-visible,
#toggle-search:focus-visible {
  outline: 1px solid rgba(176, 52, 42, 0.55);
  outline-offset: 5px;
  opacity: 0.72;
}

#search:focus-visible {
  outline: none;
  border-bottom-color: var(--rule-red);
}


/* ---- Depth without a filter ------------------------------------------ */
/*
  Cards used to be pushed back with filter: brightness(). A filter on an
  animating element makes the browser re-rasterise it every frame, and
  with twelve cards moving at once that is what stopped the cursor.

  A flat dark layer at the same opacity looks identical, because black at
  opacity o over a colour is exactly brightness(1 - o). Opacity is
  composited on the GPU and costs nothing to animate.
*/
.card-dim {
  position: absolute;
  inset: 0;
  z-index: 4;
  background: #0b0906;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.42s cubic-bezier(.22, .7, .28, 1);
}
