/* Aerogramme — the note is correspondence that traveled to reach you.
   Committed to a single light world on purpose: this is paper, and a dark
   airmail letter is not a thing. */

:root {
  /* Three stocks, light to dark: the letter, the envelope, the desk it sits on. */
  --letter: #fbf9f4;
  --envelope: #e7e0d1;
  --envelope-shade: #ded6c4;
  --paper: #efece3;

  --ink: #1d2b3a;
  /* Both greys are measured against --envelope, the darkest of the three
     stocks, since meta lines sit on all of them. Every use is under 18px, so
     4.5:1 is the bar, not 3:1. The old #5d6f80 / #98a1a8 pair read as 3.95:1
     and 2.00:1 there. The dark theme was corrected earlier; this is the light
     side catching up. */
  --soft: #46586a;
  --faint: #5a636b;
  --red: #b8283a;
  --blue: #1f4e88;
  --rule: rgba(29, 43, 58, 0.16);
  --rule-faint: rgba(29, 43, 58, 0.09);

  /* Card elevation for the letters modes, in three steps: resting, under the
     pointer, raised. Pile gets its own pair because its cards overlap downward,
     so each card's top edge lies on the card beneath it. A shadow that only
     falls downward says nothing at that edge and the pile reads as one long
     card with rules across it, hence the negative-y half. */
  --card-shadow: 0 4px 14px rgba(29, 43, 58, 0.07);
  --card-shadow-hover: 0 7px 20px rgba(29, 43, 58, 0.12);
  --pile-shadow: 0 -3px 8px rgba(29, 43, 58, 0.07), 0 5px 16px rgba(29, 43, 58, 0.1);
  --pile-shadow-hover: 0 -5px 13px rgba(29, 43, 58, 0.13), 0 11px 26px rgba(29, 43, 58, 0.16);
  --card-shadow-raised: 0 -5px 14px rgba(29, 43, 58, 0.11), 0 16px 36px rgba(29, 43, 58, 0.18);

  --serif: "Fraunces", "Iowan Old Style", "Hoefler Text", ui-serif, Georgia, serif;
  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
  --mono: ui-monospace, Menlo, monospace;
}

@font-face {
  font-family: "Fraunces";
  src: url("/fonts/fraunces-latin-400-normal.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Fraunces";
  src: url("/fonts/fraunces-latin-400-italic.woff2") format("woff2");
  font-weight: 400;
  font-style: italic;
  font-display: swap;
}

/* Keepsake: the runner-up direction, kept as an alternate skin. Palette only —
   the layout and type scale are shared, so themes can't drift apart. */
html[data-theme='keepsake'] {
  --letter: #17191d;
  --envelope: #101215;
  --envelope-shade: #0b0d10;
  --paper: #0e1013;

  --ink: #e8e4dc;
  --soft: #a9a29a;
  /* Lifted from the light theme's value: hints, placeholders and meta lines all
     use --faint, and the paper theme's grey fell under 4.5:1 on a dark ground. */
  --faint: #949086;
  --red: #b08d57;
  --blue: #8fa3b8;
  --rule: rgba(232, 228, 220, 0.16);
  --rule-faint: rgba(232, 228, 220, 0.09);

  /* The stocks invert here, so a shadow cast in --ink lands on ground darker
     than itself and disappears. What separates two sheets on a near-black desk
     is the light catching the upper one's edge, so elevation is drawn in the
     page's own light instead: a hairline at the top edge, carrying the weight,
     with the soft halves kept faint so cards glow rather than flare. */
  --card-shadow: 0 -1px 0 rgba(232, 228, 220, 0.09), 0 5px 16px rgba(232, 228, 220, 0.03);
  --card-shadow-hover: 0 -1px 0 rgba(232, 228, 220, 0.2), 0 6px 20px rgba(232, 228, 220, 0.06);
  --pile-shadow: 0 -1px 0 rgba(232, 228, 220, 0.13), 0 -5px 12px rgba(232, 228, 220, 0.045);
  --pile-shadow-hover: 0 -1px 0 rgba(232, 228, 220, 0.3), 0 -7px 16px rgba(232, 228, 220, 0.09),
    0 8px 20px rgba(232, 228, 220, 0.05);
  --card-shadow-raised: 0 -1px 0 rgba(232, 228, 220, 0.42), 0 -8px 20px rgba(232, 228, 220, 0.1),
    0 16px 38px rgba(232, 228, 220, 0.07);
}

* { box-sizing: border-box; }

body {
  margin: 0;
  min-height: 100vh;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--serif);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

/* Airmail banding, used both as the page frame and as the envelope border.
   Real par avion stock carries it around the whole perimeter, not just the top. */
.frame span {
  background: repeating-linear-gradient(
    -45deg,
    var(--red) 0 9px,
    var(--letter) 9px 18px,
    var(--blue) 18px 27px,
    var(--letter) 27px 36px
  );
}

.frame {
  position: fixed;
  inset: 0;
  z-index: 10;
  pointer-events: none;
}

.frame span { position: absolute; }
.frame .t { top: 0; left: 0; right: 0; height: 8px; }
.frame .b { bottom: 0; left: 0; right: 0; height: 8px; }
.frame .l { top: 0; bottom: 0; left: 0; width: 8px; }
.frame .r { top: 0; bottom: 0; right: 0; width: 8px; }

/* How much airmail edge the board wears. Top-only is the default, and it is the
   default in CSS itself: a missing or bogus data-border degrades to top-only. */
.frame .b,
.frame .l,
.frame .r { display: none; }

html[data-border='perimeter'] .frame .b,
html[data-border='perimeter'] .frame .l,
html[data-border='perimeter'] .frame .r { display: block; }

html[data-border='none'] .frame { display: none; }

.label {
  font-family: var(--sans);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--soft);
}

/* ---------- board ---------- */

header { padding: 46px 32px 20px; }
.masthead { display: flex; justify-content: center; align-items: center; gap: 26px; }
/* Tracking is added after every character including the space, so the gap
   between the two words grows more than the gaps inside them. The negative
   word-spacing cancels that back out and then some. */
header h1 { font-family: var(--serif); font-size: 42px; font-weight: 400; letter-spacing: .05em; word-spacing: -.09em; margin: 0; line-height: 1; }

/* A rubber stamp, not a date line: the circle, the cancellation bars, the tilt.
   Airmail red, matching the letter's own cancellation mark. */
.postmark { position: relative; width: 76px; height: 76px; flex: none; border: 1.5px solid var(--red); border-radius: 50%; box-shadow: inset 0 0 0 3px var(--paper), inset 0 0 0 4px var(--red); display: grid; place-content: center; gap: 3px; text-align: center; color: var(--red); transform: rotate(-8deg); opacity: .9; }
.postmark .pm-place { font-family: var(--mono); font-size: 7px; letter-spacing: .22em; text-transform: uppercase; }
.postmark .pm-date { font-family: var(--mono); font-size: 9.5px; letter-spacing: .1em; }
.postmark .pm-cancel, .postmark .pm-cancel::before, .postmark .pm-cancel::after { content: ''; position: absolute; left: 100%; width: 34px; height: 1.5px; background: var(--red); }
.postmark .pm-cancel { top: 30px; margin-left: 6px; }
.postmark .pm-cancel::before { top: 8px; left: -4px; }
.postmark .pm-cancel::after { top: 16px; left: 2px; }

@media (max-width: 480px) {
  .masthead { gap: 18px; }
  header h1 { font-size: 31px; }
  .postmark { width: 60px; height: 60px; }
  /* The ring shrinks but the type does not, and the chord at the place line is
     narrower than the word. Measured: 46px of ink in a 42px chord. */
  .postmark .pm-place { font-size: 6px; letter-spacing: .14em; }
  .postmark .pm-cancel, .postmark .pm-cancel::before, .postmark .pm-cancel::after { display: none; }
}

/* The first thing a new visitor reads. Scoped away from `section h2` on purpose:
   these are sentences, not column labels. */
.promise {
  max-width: 560px;
  margin: 0 auto;
  /* Bottom padding has to clear two things: the board's own top padding and the
     writing column's -22px margin, which lifts that card above its neighbours.
     Without it the supporting line sits directly on the card's edge. */
  padding: 6px 24px 34px;
  text-align: center;
}

.promise h2 {
  font-family: var(--serif);
  font-size: 22px;
  font-weight: 400;
  line-height: 1.28;
  color: var(--ink);
  text-wrap: balance;
  margin: 0;
}

.promise p {
  font-family: var(--sans);
  font-size: 13px;
  line-height: 1.55;
  color: var(--soft);
  margin: 10px 0 0;
}

.board {
  display: grid;
  /* The middle column is where the writing happens, so it gets the width. */
  grid-template-columns: 1fr 1.28fr 1fr;
  gap: 36px;
  max-width: 1020px;
  margin: 0 auto;
  padding: 18px 32px 72px;
  align-items: start;
}

@media (max-width: 860px) {
  .board { grid-template-columns: minmax(0, 1fr); gap: 34px; padding: 18px 24px 56px; }
  /* One column puts the DOM order on screen, and the DOM order is the desktop
     one: Before, then Today. That buries the textarea under a screenful of
     rereading, so writing comes first here. Order only, so desktop is untouched. */
  .writing { order: -1; }

  /* The letters modes used to flatten to nothing here. See the narrow-screen
     block at the end of the letters section for what replaced it and why. */
}

/* The column you write in sits on letter stock. The two you read sit on the desk. */
.writing {
  background: var(--letter);
  padding: 22px 24px 26px;
  margin-top: -22px;
  box-shadow: 0 6px 18px rgba(29, 43, 58, 0.07);
}

section h2 {
  font-family: var(--sans);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.19em;
  text-transform: uppercase;
  color: var(--soft);
  margin: 0 0 4px;
  padding-bottom: 9px;
  border-bottom: 1px solid var(--rule);
}

section .hint {
  font-family: var(--sans);
  font-size: 11.5px;
  color: var(--faint);
  margin: 9px 0 18px;
}

.received h2 { color: var(--red); border-bottom-color: rgba(184, 40, 58, 0.32); }

/* ---------- entries ---------- */

.entry {
  font-size: 15px;
  line-height: 1.5;
  margin-bottom: 20px;
  text-wrap: pretty;
}

.entry .who {
  display: block;
  font-family: var(--sans);
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--blue);
  margin-bottom: 3px;
}

.entry.mine .who { color: var(--soft); }

.entry .meta {
  display: block;
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--faint);
  margin-top: 5px;
}

/* Controls stay out of the way on a mouse and stay reachable on a phone. */
.entry .controls {
  display: flex;
  gap: 14px;
  margin-top: 7px;
  opacity: 0;
  transition: opacity 120ms ease;
}

.entry:hover .controls,
.entry:focus-within .controls { opacity: 1; }

@media (hover: none), (max-width: 860px) {
  .entry .controls { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .entry .controls { transition: none; }
}

.entry .controls .ghost {
  border: 0;
  border-bottom: 1px solid transparent;
  padding: 0 0 2px;
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--faint);
}

.entry .controls .ghost:hover {
  background: transparent;
  color: var(--red);
  border-bottom-color: var(--red);
}

.edit-box {
  margin: 6px 0 0;
  font-size: 15px;
  min-height: 84px;
}

/* An entry in every other respect, lifted onto letter stock: a keepsake pulled
   out onto the desk rather than another line in the list. */
.memory {
  background: var(--letter);
  padding: 15px 18px 13px;
  box-shadow: 0 4px 14px rgba(29, 43, 58, 0.07);
}

.memory .mem-label { margin: 0 0 8px; }

.empty {
  font-size: 14px;
  color: var(--faint);
  font-style: italic;
  margin: 0;
}

/* ---------- compose ---------- */

/* Inputs are ruled lines, not boxes. Writing on paper, not filling a form. */
textarea,
input[type="text"],
input[type="email"],
input[type="date"] {
  width: 100%;
  background: transparent;
  color: var(--ink);
  border: 0;
  border-bottom: 1px solid var(--rule);
  border-radius: 0;
  padding: 8px 0;
  margin-bottom: 16px;
  font-family: var(--serif);
  font-size: 16px;
  line-height: 1.5;
}

textarea {
  min-height: 104px;
  resize: vertical;
  font-size: 17px;
}

textarea::placeholder,
input::placeholder { color: var(--faint); font-style: italic; }

textarea:focus,
input:focus {
  outline: 0;
  border-bottom-color: var(--blue);
}

input[type="date"] {
  font-family: var(--mono);
  font-size: 13px;
  letter-spacing: 0.06em;
  color: var(--soft);
}

label.row {
  display: flex;
  align-items: center;
  gap: 9px;
  font-family: var(--sans);
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--soft);
  margin-bottom: 16px;
  cursor: pointer;
}

label.row input { width: auto; margin: 0; accent-color: var(--red); }

#send-options .hint { margin-top: -8px; }

button {
  font-family: var(--sans);
  background: transparent;
  color: var(--ink);
  border: 1px solid var(--ink);
  border-radius: 0;
  padding: 11px 24px;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  cursor: pointer;
  transition: background 140ms ease, color 140ms ease;
}

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

button:focus-visible { outline: 2px solid var(--blue); outline-offset: 3px; }

button.ghost {
  border: 0;
  border-bottom: 1.5px solid var(--red);
  padding: 0 0 3px;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.14em;
}

button.ghost:hover { background: transparent; color: var(--red); }

button:disabled { opacity: 0.45; cursor: default; }

@media (prefers-reduced-motion: reduce) {
  button { transition: none; }
}

/* ---------- the share slip ---------- */

.share {
  margin-top: 22px;
  padding: 16px 0 0;
  border-top: 1px solid var(--rule);
  font-family: var(--sans);
  font-size: 12px;
  color: var(--soft);
}

.share code {
  display: block;
  font-family: var(--mono);
  font-size: 11px;
  line-height: 1.6;
  word-break: break-all;
  color: var(--blue);
  margin: 9px 0 14px;
}

#today .entry:first-of-type { margin-top: 26px; }

footer {
  text-align: center;
  font-family: var(--mono);
  font-size: 9px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--faint);
  padding: 0 24px 44px;
}

/* ---------- settings ---------- */

#settings-toggle {
  border: 0;
  border-bottom: 1px solid transparent;
  padding: 0 0 2px;
  font-family: var(--mono);
  font-size: 9px;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--faint);
  margin-bottom: 14px;
}

#settings-toggle:hover {
  background: transparent;
  color: var(--red);
  border-bottom-color: var(--red);
}

.settings {
  max-width: 460px;
  margin: 0 auto 22px;
  padding: 20px 24px 22px;
  background: var(--letter);
  border: 1px solid var(--rule);
  text-align: left;
}

.settings .group + .group { margin-top: 18px; }

.settings h3 {
  font-family: var(--sans);
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--soft);
  margin: 0 0 9px;
}

.settings .options { display: flex; flex-wrap: wrap; gap: 8px; }

.settings .opt {
  font-family: var(--sans);
  background: transparent;
  border: 1px solid var(--rule);
  color: var(--soft);
  padding: 8px 14px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.07em;
  text-transform: uppercase;
}

.settings .opt:hover { background: transparent; color: var(--ink); border-color: var(--ink); }

.settings .opt[aria-pressed='true'] {
  border-color: var(--red);
  color: var(--red);
}

.settings .sync-status {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.08em;
  color: var(--faint);
  margin: 0 0 14px;
}

.settings .sync-row {
  display: flex;
  align-items: baseline;
  gap: 16px;
  margin: 0 0 12px;
}

.settings .sync-label {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--soft);
}

.settings .sync-row input {
  flex: 1;
  margin-bottom: 0;
  font-family: var(--mono);
  font-size: 12px;
}

.settings .board-code {
  display: block;
  font-family: var(--mono);
  font-size: 11px;
  line-height: 1.6;
  word-break: break-all;
  user-select: all;
  color: var(--blue);
  margin: 0 0 10px;
}

.settings .sync-error { color: var(--red); }

/* Its buttons sit in a sync-row of their own now, which carries the bottom
   rhythm, so the box adds none. */

/* The recovery offer. A settings group in all but name, so it gets a group's
   top rule rather than floating against the code box above it. */
#email-code-box {
  margin-top: 18px;
  padding-top: 16px;
  border-top: 1px solid var(--rule-faint);
}

#email-code-box .ec-label { margin: 0 0 8px; }
#email-code-box .ec-body { margin-top: 0; }

/* Its lead sits above the form rather than under it. */
.stay-loop .kc-lead { margin: 0 0 14px; }

.settings .note {
  font-family: var(--sans);
  font-size: 11px;
  line-height: 1.5;
  color: var(--faint);
  text-transform: none;
  letter-spacing: 0;
  margin: 16px 0 0;
}

/* ---------- what the recipient opens ---------- */

/* Flex column rather than a centring grid: the page now holds two objects, the
   envelope and our asks below it, and two auto grid rows would each stretch to
   half the viewport and tear them apart. */
.note-page {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 48px 26px 44px;
}

/* The envelope: a flap folded open at the top, the letter resting inside it. */
.envelope {
  position: relative;
  width: 100%;
  max-width: 512px;
  background: var(--envelope);
  padding: 8px;
  box-shadow: 0 20px 46px rgba(29, 43, 58, 0.17), 0 2px 5px rgba(29, 43, 58, 0.1);
}

.envelope .border {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.envelope .border span { position: absolute; }
.envelope .border .t { top: 0; left: 0; right: 0; height: 8px; }
.envelope .border .b { bottom: 0; left: 0; right: 0; height: 8px; }
.envelope .border .l { top: 0; bottom: 0; left: 0; width: 8px; }
.envelope .border .r { top: 0; bottom: 0; right: 0; width: 8px; }

.envelope .border span {
  background: repeating-linear-gradient(
    -45deg,
    var(--red) 0 8px,
    var(--envelope) 8px 16px,
    var(--blue) 16px 24px,
    var(--envelope) 24px 32px
  );
}

.flap {
  height: 78px;
  margin: 0 0 -30px;
  background: var(--envelope-shade);
  clip-path: polygon(0 0, 50% 100%, 100% 0);
  position: relative;
  z-index: 1;
}

.par-avion {
  position: absolute;
  top: 26px;
  left: 26px;
  z-index: 3;
  font-family: var(--mono);
  font-size: 8px;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--soft);
  border: 1px solid var(--blue);
  padding: 3px 7px;
  transform: rotate(-3deg);
}

.note-card {
  position: relative;
  z-index: 2;
  background: var(--letter);
  width: 100%;
  text-align: center;
  padding: 46px 32px 40px;
  box-shadow: 0 -1px 0 rgba(29, 43, 58, 0.07), 0 8px 20px rgba(29, 43, 58, 0.09);
}

@media (max-width: 480px) {
  .flap { height: 62px; margin-bottom: -24px; }
  .note-card { padding: 38px 24px 34px; }
  .par-avion { top: 20px; left: 20px; }
}

.note-card .to { margin: 0 0 30px; }

.note-card .body {
  font-size: 23px;
  line-height: 1.52;
  font-style: italic;
  margin: 0 0 30px;
  text-wrap: pretty;
}

.note-card .sig { font-size: 16px; color: var(--soft); margin: 0 0 5px; }

/* The red cancellation mark in the corner: the same 1.5px --red ring as the
   masthead's .postmark, minus the cancel bars, since this one sits beside a
   letter someone is reading rather than above a board. */
.postmark-stamp {
  position: absolute;
  top: 20px;
  right: 24px;
  z-index: 3;
  width: 46px;
  height: 46px;
  border: 1.5px solid var(--red);
  border-radius: 50%;
  display: grid;
  place-content: center;
  gap: 2px;
  color: var(--red);
  transform: rotate(-8deg);
  opacity: .9;
}

/* Two lines, not one: at this diameter "14 AUG 2026" on a single line runs
   wider than the ring. */
.postmark-stamp .ps-day,
.postmark-stamp .ps-year {
  display: block;
  font-family: var(--mono);
  font-size: 7.5px;
  line-height: 1.2;
  letter-spacing: .05em;
  text-align: center;
  white-space: nowrap;
}

/* Room for the stamp above the centred label, so the two never share a line.
   Keyed to a class JS adds with the stamp, not to :has(): that was tried and
   dropped, because where :has() is unsupported the clearance vanished and the
   stamp could land on top of a long recipient line. The stampless error card
   takes the plain padding instead of this lopsided clearance. */
.note-card.stamped { padding-top: 78px; }

@media (max-width: 480px) {
  .note-card.stamped { padding-top: 66px; }
  .postmark-stamp { top: 14px; right: 16px; width: 38px; height: 38px; }
  .postmark-stamp .ps-day,
  .postmark-stamp .ps-year { font-size: 6.5px; }
}

/* ---------- what we ask, on the desk rather than on the letter ---------- */

/* Unbacked on purpose: no card, no panel, just the page's own --paper. A second
   sheet here would invite the eye to compare two pieces of paper, and the letter
   would lose by the comparison. The column matches the letter's own measure
   (envelope max-width, less the envelope padding and the card's gutters) so the
   two read as one column with an object boundary in the middle. */
.after-letter {
  width: 100%;
  max-width: 512px;
  /* Wider than the rule the write-back used to sit under, because the gap now
     crosses an object boundary and has to read as "off the letter" on its own,
     with the envelope's drop shadow eating the top of it. */
  margin-top: 72px;
  padding: 0 40px;
  text-align: center;
  transition: opacity 300ms ease;
}

/* Held back by opacity, not by `hidden`, so the block still occupies its space
   while the envelope opens and the page does not jump when it arrives. Removed
   in unseal(), which every opening path funnels through. */
.after-letter.waiting { opacity: 0; }

@media (max-width: 480px) {
  .after-letter { margin-top: 52px; padding: 0 32px; }
}

.after-letter .kept {
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--faint);
  margin: 28px 0 0;
  padding-top: 20px;
  border-top: 1px solid var(--rule-faint);
}

/* Same footer slot, but a sentence rather than a two-word stamp: uppercased
   mono at this size would wrap a plain-English line into a wall. */
.after-letter .kept-unsaved {
  font-family: var(--sans);
  font-size: 12px;
  letter-spacing: 0;
  text-transform: none;
  line-height: 1.5;
  text-wrap: pretty;
}

/* The invitation to write back. It is the first thing on the ground below the
   envelope, so it carries no rule and no top margin of its own: the envelope's
   own edge and the gap above already divide it from the letter. */
.write-back .wb-label { color: var(--red); margin: 0 0 10px; }

.write-back .wb-line {
  font-size: 15px;
  font-style: italic;
  color: var(--soft);
  margin: 0 0 20px;
  text-wrap: pretty;
}

.write-back .wb-button {
  display: inline-block;
  text-decoration: none;
  font-family: var(--sans);
  color: var(--ink);
  border: 1px solid var(--ink);
  padding: 11px 24px;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  transition: background 140ms ease, color 140ms ease;
}

.write-back .wb-button:hover { background: var(--ink); color: var(--letter); }

.write-back .wb-button:focus-visible { outline: 2px solid var(--blue); outline-offset: 3px; }

/* An afterthought to the letter on purpose: smaller type, faintest rule. */
.stay-loop {
  margin-top: 28px;
  padding-top: 20px;
  border-top: 1px solid var(--rule-faint);
  /* Centred to match the write-back block above it. Left-aligned text inside a
     centred column was the whole reason the tail below the envelope read as
     loose: two blocks in one column, disagreeing about their own axis. */
  text-align: center;
}

.stay-loop .sl-label { margin: 0 0 6px; }

.sl-form {
  display: flex;
  align-items: baseline;
  gap: 16px;
}

.sl-form input {
  flex: 1;
  margin-bottom: 0;
  font-size: 14px;
}

.stay-loop .sl-hint,
.stay-loop .sl-error {
  font-family: var(--sans);
  font-size: 11px;
  line-height: 1.5;
  color: var(--faint);
  margin: 10px 0 0;
}

.stay-loop .sl-error { color: var(--red); }

.stay-loop .sl-done {
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--soft);
  margin: 0;
}

/* ---------- the envelope opening ---------- */

/* Sealed means the letter is genuinely inside: pushed a full card-height down
   and clipped away by the envelope, with a closed lid over the top. The final
   state is exactly the static layout, so no-JS and errors degrade to it. */
.envelope.sealed,
.envelope.opening { overflow: hidden; }

.envelope.sealed .note-card { transform: translateY(100%); }

/* The lid only exists while the envelope is closing or opening. Absolutely
   positioned so it never touches the resting layout. */
.lid {
  position: absolute;
  top: 8px;
  left: 8px;
  right: 8px;
  height: 190px;
  z-index: 5;
  background: linear-gradient(178deg, var(--envelope-shade), #d3c9b4);
  clip-path: polygon(0 0, 50% 100%, 100% 0);
  transform-origin: 50% 0;
  backface-visibility: hidden;
}

.envelope.opening .lid { animation: lid-open 520ms cubic-bezier(0.4, 0, 0.3, 1) forwards; }

.envelope.opening .note-card {
  transform: none;
  transition: transform 640ms cubic-bezier(0.16, 0.8, 0.3, 1) 220ms;
}

@keyframes lid-open {
  0% { transform: rotateX(0); opacity: 1; }
  70% { transform: rotateX(-150deg); opacity: 1; }
  100% { transform: rotateX(-178deg); opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .envelope.opening .lid,
  .envelope.opening .note-card {
    animation: none;
    transition: none;
  }
  .write-back .wb-button { transition: none; }
  /* Still revealed, just without the fade. */
  .after-letter { transition: none; }
}

/* The address side of a held envelope. It never opens. */
.face {
  position: relative;
  z-index: 4;
  background: var(--envelope);
  text-align: center;
  padding: 44px 30px 38px;
}

/* A held note is still in transit. Same page, no body, a postmark instead. */
.in-transit .headline {
  font-size: 22px;
  line-height: 1.45;
  margin: 0 0 22px;
  text-wrap: balance;
}

.in-transit .arrives {
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--red);
  border: 1px solid rgba(184, 40, 58, 0.35);
  border-radius: 999px;
  padding: 9px 18px;
  display: inline-block;
  margin: 0 0 32px;
}

/* Quieter than "Wait for it": an offer, in the same afterthought register as
   the stay-in-the-loop block on an opened letter. */
.notify-me {
  margin: 30px auto 0;
  padding-top: 20px;
  border-top: 1px solid var(--rule-faint);
  max-width: 340px;
  text-align: left;
}

.nm-form {
  display: flex;
  align-items: baseline;
  gap: 14px;
}

.nm-form input {
  flex: 1;
  margin-bottom: 0;
  font-size: 14px;
}

.notify-me .nm-hint,
.notify-me .nm-error {
  font-family: var(--sans);
  font-size: 11px;
  line-height: 1.5;
  color: var(--faint);
  margin: 10px 0 0;
}

.notify-me .nm-error { color: var(--red); }

.notify-me .nm-done {
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--soft);
  margin: 0;
}

/* A failed send. Stated once, in the sender's own hand-off colour, next to the
   entry it belongs to. */
.entry .meta .undelivered { color: var(--red); }

#recipient-row .hint { margin-top: -8px; }

/* ---------- unsubscribe ---------- */

.unsub {
  max-width: 460px;
  margin: 0 auto;
  padding: 84px 26px 60px;
}

.unsub h1 {
  font-family: var(--serif);
  font-size: 26px;
  font-weight: normal;
  line-height: 1.35;
  margin: 0 0 18px;
}

.unsub p {
  font-family: var(--sans);
  font-size: 13.5px;
  line-height: 1.6;
  color: var(--soft);
  margin: 0 0 24px;
}

.unsub .note {
  font-size: 11px;
  color: var(--faint);
  margin-top: 26px;
}


/* ---------- why / first visit ---------- */

.why {
  max-width: 560px;
  margin: 0 auto;
  padding: 72px 26px 80px;
}

.why-kicker {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--blue);
  margin: 0 0 18px;
}

.why h1 {
  font-family: var(--serif);
  font-size: 28px;
  font-weight: normal;
  line-height: 1.25;
  margin: 0 0 22px;
}

.why p {
  font-family: var(--serif);
  font-size: 17px;
  line-height: 1.55;
  color: var(--ink);
  margin: 0 0 16px;
}

.why em { font-style: italic; }

.why-home {
  color: inherit;
  text-decoration: none;
}

.why-home:hover {
  text-decoration: underline;
  text-underline-offset: 3px;
}

.why-home:focus-visible { outline: 2px solid var(--blue); outline-offset: 3px; }

.why-go {
  display: inline-block;
  margin: 10px 0 36px;
  padding: 10px 18px;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--letter);
  background: var(--ink);
  border: 1px solid var(--ink);
}

.why-go:focus-visible {
  outline: 2px solid var(--blue);
  outline-offset: 3px;
}

.why-go:hover { filter: brightness(1.08); }

.why-sources {
  list-style: none;
  padding: 18px 0 0;
  margin: 0;
  border-top: 1px solid var(--rule);
}

.why-sources li {
  font-family: var(--sans);
  font-size: 12px;
  line-height: 1.5;
  color: var(--soft);
  margin: 0 0 12px;
}

.why-sources a { color: var(--blue); }

footer .why-link {
  display: inline-block;
  margin-top: 14px;
  color: inherit;
  text-decoration: none;
}

footer .why-link:hover {
  color: var(--soft);
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* ---------- letters ---------- */
/* The `stack` preference decides how Before and Received are presented. Plain is
   the default and adds nothing at all, so the board only changes for someone who
   went and asked for it. Today is the writing surface and is never touched here,
   and neither is #memory, which is already a pulled-out object on letter stock.

   Every selector below keeps the container in :where() so its specificity stays
   at the class level. That is what lets the narrow-screen block at the end of
   this section, and `.raised` after it, win on source order without an
   !important.

   Tilt and lift are carried as custom properties rather than written into each
   rule's `transform`. A card's angle comes from its position in the column, its
   lift comes from its state, and the two are set by different rules: composing
   them in one shorthand would mean restating every angle in every state at
   every width. */

:where(html[data-stack='tidy'], html[data-stack='pile'], html[data-stack='pinboard'])
  :where(#before, #received) .entry {
  position: relative;
  background: var(--letter);
  padding: 15px 18px 13px;
  box-shadow: var(--card-shadow);
  cursor: pointer;
  transform: rotate(var(--tilt, 0deg)) translateY(var(--lift, 0px));
  transition: transform 170ms ease, box-shadow 170ms ease;
}

/* Under a pointer the card comes up a little and its shadow deepens, so the one
   you are about to click has separated from its neighbours before you click it.
   Touch has no such state, and on touch this would stick to the last card
   tapped, so it is fenced off behind a real pointer. */
@media (hover: hover) {
  :where(html[data-stack='tidy'], html[data-stack='pile'], html[data-stack='pinboard'])
    :where(#before, #received) .entry:hover {
    box-shadow: var(--card-shadow-hover);
    --lift: -2px;
  }
}

/* Tidy: square-ish, just off true, nothing overlapping. */
:where(html[data-stack='tidy']) :where(#before, #received) .entry:nth-child(odd) {
  --tilt: -1deg;
}

:where(html[data-stack='tidy']) :where(#before, #received) .entry:nth-child(even) {
  --tilt: 0.9deg;
}

/* Pile: mail dropped on a table. The negative margin does the overlapping and
   the DOM order does the z-index, so the stack ascends down the column for free.
   Hover and focus lift a card out of the pile, which is what keeps its own
   controls clickable when the card below is sitting on top of them. */
:where(html[data-stack='pile']) :where(#before, #received) .entry {
  margin-bottom: -44px;
  /* The 44px the next card covers has to be empty. Without this the card below
     sits on top of Edit, Delete and Unsend, and a tap aimed at one of them hits
     the covering card instead: the controls look present and do nothing. */
  padding-bottom: 57px;
  /* The only mode whose top edge needs drawing, because that edge is lying on
     another card. One soft shadow falling downward draws nothing there, and the
     pile reads as a single long card with rules across it. */
  box-shadow: var(--pile-shadow);
}

:where(html[data-stack='pile']) :where(#before, #received) {
  padding-bottom: 44px;
}

:where(html[data-stack='pile']) :where(#before, #received) .entry:nth-child(odd) {
  --tilt: -0.7deg;
}

:where(html[data-stack='pile']) :where(#before, #received) .entry:nth-child(even) {
  --tilt: 0.6deg;
}

:where(html[data-stack='pile']) :where(#before, #received) .entry:hover,
:where(html[data-stack='pile']) :where(#before, #received) .entry:focus-within {
  z-index: 4;
}

@media (hover: hover) {
  :where(html[data-stack='pile']) :where(#before, #received) .entry:hover {
    box-shadow: var(--pile-shadow-hover);
    --lift: -3px;
  }
}

/* Pinboard: wider angles, an uneven left edge, and a strip of tape over the top. */
:where(html[data-stack='pinboard']) :where(#before, #received) .entry {
  margin-bottom: 26px;
}

:where(html[data-stack='pinboard']) :where(#before, #received) .entry:nth-child(3n + 1) {
  --tilt: -2.2deg;
  margin-left: 0;
}

:where(html[data-stack='pinboard']) :where(#before, #received) .entry:nth-child(3n + 2) {
  --tilt: 2.6deg;
  margin-left: 15px;
}

:where(html[data-stack='pinboard']) :where(#before, #received) .entry:nth-child(3n) {
  --tilt: -1.4deg;
  margin-left: 6px;
}

:where(html[data-stack='pinboard']) :where(#before, #received) .entry::before {
  content: '';
  position: absolute;
  top: -8px;
  left: 50%;
  width: 58px;
  height: 15px;
  transform: translateX(-50%) rotate(-1.6deg);
  /* The variable carries the theme, the opacity carries the translucency, so
     Keepsake's warmer red comes through with no second rule. */
  background: var(--red);
  opacity: 0.17;
}

/* Narrow screens get the same three modes at a smaller amplitude. They used to
   flatten to nothing, on the reasoning that a thin column of steeply tilted,
   overlapping cards reads as a rendering fault rather than as mail on a desk.
   That reasoning holds against the full desktop values; it does not hold against
   zero, which made every mode the plain column again and made the setting look
   broken. So: angles come down to about half, Pile's overlap comes down so more
   of each card shows in a narrow column than in a wide one, and Pinboard drops
   its uneven left indents, which are horizontal space a 375px column has none of
   to spend. The tape and the overlap stay, because they are what each mode is. */
@media (max-width: 860px) {
  :where(html[data-stack='tidy']) :where(#before, #received) .entry:nth-child(odd) {
    --tilt: -0.55deg;
  }

  :where(html[data-stack='tidy']) :where(#before, #received) .entry:nth-child(even) {
    --tilt: 0.5deg;
  }

  :where(html[data-stack='pile']) :where(#before, #received) .entry {
    margin-bottom: -26px;
    /* Unchanged clearance under the controls: 26px of card is covered, and the
       13px the plain card ends with is still empty below the last button. */
    padding-bottom: 39px;
  }

  :where(html[data-stack='pile']) :where(#before, #received) {
    padding-bottom: 26px;
  }

  :where(html[data-stack='pile']) :where(#before, #received) .entry:nth-child(odd) {
    --tilt: -0.4deg;
  }

  :where(html[data-stack='pile']) :where(#before, #received) .entry:nth-child(even) {
    --tilt: 0.35deg;
  }

  :where(html[data-stack='pinboard']) :where(#before, #received) .entry:nth-child(3n + 1) {
    --tilt: -1.1deg;
  }

  :where(html[data-stack='pinboard']) :where(#before, #received) .entry:nth-child(3n + 2) {
    --tilt: 1.3deg;
    margin-left: 0;
  }

  :where(html[data-stack='pinboard']) :where(#before, #received) .entry:nth-child(3n) {
    --tilt: -0.7deg;
    margin-left: 0;
  }
}

/* Raised. Written after the mode rules and at the same specificity, so it wins
   on order rather than on weight. Deliberately stronger than hover in all three
   of its parts, so resting, hovered and raised are three legible steps rather
   than two. */
:where(html[data-stack]) :where(#before, #received) .entry.raised {
  z-index: 6;
  box-shadow: var(--card-shadow-raised);
  --tilt: 0deg;
  --lift: -5px;
}

:where(html[data-stack='pinboard']) :where(#before, #received) .entry.raised::before {
  opacity: 0.3;
}

:where(#before, #received) .entry:focus-visible {
  outline: 2px solid var(--blue);
  outline-offset: 4px;
}

/* Reduced motion loses the movement between states, not the states themselves. */
@media (prefers-reduced-motion: reduce) {
  :where(#before, #received) .entry { transition: none; }
}
