/* =========================================================================
   Happy Birthday, Katie
   A pressed-botanical keepsake: a closed card that folds open onto a canvas
   everyone signs. Warm paper, deep ink, pink script.
   ========================================================================= */

:root {
  /* Pinks, with a plum ink and a gold that is only ever a pen colour. */
  --paper: #fdf3f5;
  --table: #f0cdd8;
  --card: #fffbfc;
  --card-edge: #f6e2e8;

  --ink: #4a2b38;
  --ink-soft: #8a6472;

  --gold: #c08a3e;
  --rose: #d05c86;
  --blush: #f7d6e2;
  --plum: #8e3d63;
  --sky: #5b7f9c;

  /* Empty spots sit on bare board, which is nearly white, so the pen colours
     above are too light to carry text there. These are the darkened versions
     that clear WCAG AA against the board's lightest patch (#fffafb): 4.5:1 for
     the labels, 3:1 for the dashed boxes. tools/contrast.mjs checks them. */
  --rose-deep: #b03462;
  --spot-ink: #7d5566;
  --spot-line: #9a7d89;
  --held-ink: #8a5a12;
  --held-wash: rgba(246, 227, 180, 0.3);

  /* The cover's own paper, a shade warmer than the rest. The bows carry their
     own pinks, in js/bows.js. */
  --cover-paper: #fdf6ec;

  --edge: rgba(74, 43, 56, 0.15);
  --edge-soft: rgba(74, 43, 56, 0.09);

  --font-display: 'Fraunces', 'Iowan Old Style', Georgia, serif;
  --font-body: 'Karla', 'Avenir Next', 'Segoe UI', sans-serif;
  --font-hand: 'Caveat', 'Bradley Hand', cursive;
  /* The hand-lettering: genuinely drawn, and variable so it can bounce. */
  --font-cute: 'Shantell Sans', 'Comic Sans MS', cursive;
  /* The name, inside and out. */
  --font-script: 'Send Flowers', 'Snell Roundhand', cursive;

  /* Script and handwriting faces set their word space far too tight — in the
     loopier ones the gap between two words is barely wider than the gap
     between two letters, and a line of them reads as one long word. Every rule
     below that uses one opens the word space back up. WCAG 1.4.12 takes 0.16em
     as the point at which text has to stay readable, so that is the floor here
     and the display scripts go well past it. */
  --word-space: 0.2em;
  --word-space-display: 0.3em;

  --ease: cubic-bezier(0.2, 0.7, 0.2, 1);
  --ease-fold: cubic-bezier(0.36, 0.06, 0.14, 1);
}

* {
  box-sizing: border-box;
}

/* A class that sets `display` outranks the UA rule for [hidden], which would
   quietly leave hidden panels on screen. Make the attribute mean what it says. */
[hidden] {
  display: none !important;
}

html {
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  min-height: 100svh;
  font-family: var(--font-body);
  font-size: 1.0625rem;
  line-height: 1.65;
  color: var(--ink);
  background-color: var(--paper);
  overflow: hidden;
  overscroll-behavior: none;
}

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

.glitter {
  position: fixed;
  inset: 0;
  z-index: 55;
  pointer-events: none;
  /* A canvas has an intrinsic size from its width/height attributes, which are
     in device pixels. Without this it lays out at 2x on a retina screen and the
     burst lands off in a corner. */
  width: 100%;
  height: 100%;
}

.grain {
  position: fixed;
  inset: 0;
  z-index: 60;
  pointer-events: none;
  opacity: 0.55;
  mix-blend-mode: multiply;
  /* Two layers: tight fibre, then a slow mottle. Real paper has both, and one
     alone reads as either digital noise or a dirty screen. */
  background-image:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='f'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23f)' opacity='0.28'/%3E%3C/svg%3E"),
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='520' height='520'%3E%3Cfilter id='m'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.012' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='520' height='520' filter='url(%23m)' opacity='0.16'/%3E%3C/svg%3E");
  background-size: 160px 160px, 520px 520px;
}

/* =========================================================================
   Opening — the closed card, and the fold

   Layering matters here: the table is behind the board, and the cover is in
   front of it. So the board is on screen from the first paint, merely clipped
   to the card's window, and the fold reveals the real canvas underneath.
   ========================================================================= */

.table {
  position: fixed;
  inset: 0;
  z-index: 38;
  background:
    radial-gradient(120% 90% at 50% 8%, #fbe4ec 0%, rgba(251, 228, 236, 0) 60%),
    linear-gradient(168deg, #f4d5e0 0%, #e8bccd 100%);
  transition: opacity 0.8s var(--ease);
}

.table.is-gone {
  opacity: 0;
}

.opening {
  position: fixed;
  inset: 0;
  z-index: 41;
  display: grid;
  place-items: center;
  /* Only the card and the cue take clicks; everything else is a view of the
     board underneath. */
  pointer-events: none;
  transition: opacity 0.7s var(--ease);
}

.opening.is-gone {
  opacity: 0;
}

.stage {
  pointer-events: auto;
  /* Deliberately no will-change: letting the browser re-rasterise through the
     move keeps the canvas underneath legible rather than smeared. */
  transition: transform var(--ease-dolly, cubic-bezier(0.42, 0, 0.3, 1));
  transition-property: transform;
  transition-duration: 1500ms;
}

/* The camera moves in: the card, its cover and the canvas showing through it
   all scale together, until the window alone fills the screen. */
.opening.is-diving .stage {
  transform: scale(var(--dolly, 3));
}

.scene {
  perspective: 1900px;
  perspective-origin: 50% 45%;
}

/* A 6:4 card. Centred in the viewport, which is what lets the window simply
   grow outward without the artwork inside appearing to drift. */
.card {
  position: relative;
  width: min(84vw, 560px, 58vh * 1.5);
  aspect-ratio: 3 / 2;
  transform-style: preserve-3d;
}

/* The window the canvas shows through. The board is moved in here while the
   card is shut, so the two scale as one. */
.card__window {
  position: absolute;
  inset: 0;
  overflow: hidden;
  border-radius: 5px;
  background: var(--paper);
  box-shadow:
    0 1px 2px rgba(74, 43, 56, 0.18),
    0 26px 48px -26px rgba(120, 60, 84, 0.75);
}

/* Inside the card, the board fills the window instead of the viewport — and
   sits under the cover, which its base z-index would otherwise outrank. */
.board--inset {
  position: absolute;
  inset: 0;
  z-index: 0;
}

.board--inset .hud,
.board--inset .board__hint {
  display: none;
}

/* The hinge carries both faces and swings on the left edge. It must paint
   above the board showing through the window beneath it. */
.card__hinge {
  position: absolute;
  inset: 0;
  z-index: 2;
  transform-style: preserve-3d;
  transform-origin: left center;
  transition: transform 1.15s var(--ease-fold);
  will-change: transform;
}

.opening.is-open .card__hinge {
  transform: rotateY(-158deg);
}

.card__face {
  position: absolute;
  inset: 0;
  border-radius: 5px;
  overflow: hidden;
  backface-visibility: hidden;
  background: var(--cover-paper);
}

/* The card stock itself: fibre and a slow mottle, printed under the artwork. */
.card__face::before {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0.5;
  mix-blend-mode: multiply;
  background-image:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='150' height='150'%3E%3Cfilter id='c'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.95' numOctaves='4' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='150' height='150' filter='url(%23c)' opacity='0.3'/%3E%3C/svg%3E"),
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='480' height='480'%3E%3Cfilter id='d'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.014' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='480' height='480' filter='url(%23d)' opacity='0.2'/%3E%3C/svg%3E");
  background-size: 150px 150px, 480px 480px;
  z-index: 2;
  box-shadow: 0 1px 2px rgba(74, 43, 56, 0.16), 0 18px 34px -20px rgba(120, 60, 84, 0.55);
}

/* A sheen that sweeps as the cover turns, so the paper catches light. */
.card__face--front::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(100deg, rgba(255, 255, 255, 0) 30%, rgba(255, 255, 255, 0.55) 50%, rgba(255, 255, 255, 0) 70%);
  opacity: 0;
  transition: opacity 1.15s var(--ease-fold);
}

.opening.is-open .card__face--front::after {
  opacity: 1;
}

.card__face--back {
  transform: rotateY(180deg);
  background: linear-gradient(200deg, #fff7f9 0%, #f8e4ea 100%);
}

.cover__inner-left {
  position: absolute;
  inset: 0;
  /* Faint ruled lines, like the blank left page of a card. */
  background-image: repeating-linear-gradient(
    180deg,
    transparent 0 26px,
    rgba(74, 43, 56, 0.05) 26px 27px
  );
  mask-image: linear-gradient(90deg, transparent, #000 22%, #000 78%, transparent);
}

.cover {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
}

.cover > * {
  grid-area: 1 / 1;
}

.cover__art {
  width: 100%;
  height: 100%;
  display: block;
}

.cover__art--image {
  object-fit: cover;
}

.cover__text {
  position: relative;
  text-align: center;
  padding: 5% 7%;
  border-radius: 50%;
  /* The artwork leaves the middle clear, so this is only a breath of the paper
     colour to settle the lettering onto — anything stronger reads as a plate
     laid over the drawing. */
  background: radial-gradient(
    58% 52% at 50% 50%,
    rgba(253, 246, 236, 0.72) 0%,
    rgba(253, 246, 236, 0.5) 60%,
    rgba(253, 246, 236, 0) 100%
  );
}

/* One hand-lettered character (see js/lettering.js). Inline-block so it can be
   tilted at all; the tilt, lift and size come in as custom properties. */
.ltr {
  display: inline-block;
  transform: translateY(var(--y, 0)) rotate(var(--r, 0deg)) scale(var(--s, 1));
}

.cover__name {
  margin: 0;
  font-family: var(--font-script);
  font-weight: 400;
  font-size: clamp(3.6rem, 14.5vw, 6rem);
  word-spacing: var(--word-space-display);
  line-height: 0.92;
  color: var(--rose);
}

.cover__saying {
  margin: 0.5em auto 0;
  /* Balanced, so the last word never strands on a line of its own. */
  text-wrap: balance;
  max-width: 17ch;
  font-family: var(--font-cute);
  font-variation-settings: 'BNCE' 55, 'INFM' 34;
  word-spacing: var(--word-space);
  font-weight: 600;
  font-size: clamp(0.8rem, 2.7vw, 1.02rem);
  line-height: 1.3;
  /* Plum, not rose: rose on this cream is 3.5:1, which carries the name at
     6rem but not a line of body-sized text. Plum is 6.5:1. */
  color: var(--plum);
}

.opening__cue {
  position: absolute;
  left: 50%;
  bottom: max(1.75rem, env(safe-area-inset-bottom));
  transform: translateX(-50%);
  pointer-events: auto;
  border: 0;
  background: none;
  padding: 0.5rem 1rem;
  cursor: pointer;
  font-family: var(--font-body);
  font-size: 0.72rem;
  letter-spacing: 0.32em;
  text-transform: uppercase;
  color: rgba(74, 43, 56, 0.6);
  transition: color 0.3s var(--ease);
}

.opening__cue:hover {
  color: var(--ink);
}

/* The label breathes, not the button — an animated hit target is a moving
   target, for a finger and for a test runner alike. */
.opening__cue-label {
  display: inline-block;
  animation: breathe 3.4s ease-in-out 1.4s infinite;
}

.opening.is-open .opening__cue {
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.3s linear;
}

.opening.is-open .opening__cue-label {
  animation: none;
}

@keyframes breathe {
  0%,
  100% {
    transform: translateY(0);
  }
  50% {
    transform: translateY(-3px);
  }
}

/* =========================================================================
   Board — the pan/zoom canvas
   ========================================================================= */

.board {
  position: fixed;
  inset: 0;
  z-index: 1;
  overflow: hidden;
  touch-action: none;
  cursor: grab;
  background:
    radial-gradient(120% 80% at 50% 0%, #fffafb 0%, rgba(255, 250, 251, 0) 55%),
    radial-gradient(80% 60% at 12% 100%, rgba(208, 92, 134, 0.09), rgba(208, 92, 134, 0) 60%),
    radial-gradient(80% 60% at 90% 90%, rgba(192, 138, 62, 0.07), rgba(192, 138, 62, 0) 60%),
    var(--paper);
  transition: opacity 0.8s var(--ease);
}



.board.is-dragging {
  cursor: grabbing;
}

.board__world {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 0;
  height: 0;
  transform-origin: 0 0;
  will-change: transform;
}

/* --- the greeting at the middle ----------------------------------------- */

/* Printed on the same sheet as everything else — no panel, no shadow. On a
   real card the greeting is ink on the paper, not a thing resting on it. */
.centre {
  position: absolute;
  display: grid;
  place-items: center;
  text-align: center;
}

.centre > * {
  grid-area: 1 / 1;
}

.centre__text {
  position: relative;
  width: 60%;
  padding: 0 1rem;
  /* Sit a touch low in the ring: the bow tied at the top eats into the opening
     from above, and the sign-off is the widest line down at the bottom. */
  transform: translateY(1.5%);
}

.garland {
  width: 100%;
  justify-self: center;
  overflow: visible;
  pointer-events: none;
}

/* A supplied garland (config.garland). Square, and transparent, so it sits on
   the card the same way the drawn one does. */
.garland--image {
  height: auto;
}


.centre__title {
  margin: 0;
  line-height: 0.94;
  font-weight: 400;
}

.centre__greeting {
  display: block;
  font-family: var(--font-hand);
  font-weight: 600;
  font-size: 44px;
  word-spacing: var(--word-space);
  line-height: 1;
  color: var(--ink-soft);
}

.centre__name {
  display: block;
  font-family: var(--font-script);
  font-weight: 400;
  font-size: 112px;
  word-spacing: var(--word-space-display);
  line-height: 0.9;
  margin-top: 0.04em;
  color: var(--rose);
}

.centre__date {
  margin: 22px 0 0;
  font-size: 14px;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

.centre__date span {
  color: var(--rose);
}

.centre__note {
  margin: 18px auto 0;
  max-width: 24ch;
  font-family: var(--font-display);
  font-variation-settings: 'SOFT' 50, 'opsz' 30;
  font-size: 20px;
  line-height: 1.6;
  color: #3d3138;
}

.centre__from {
  margin: 14px 0 0;
  font-family: var(--font-hand);
  font-size: 21px;
  word-spacing: var(--word-space);
  /* Small, and it shrinks further as the board zooms out — so plum, for the
     same reason the cover's saying is plum. */
  color: var(--plum);
}

/* --- a signed tile ------------------------------------------------------- */

/* Written straight onto the card: no panel, no border, no shadow. */
.tile {
  position: absolute;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 16px;
  border-radius: 6px;
  transform: rotate(var(--tilt, 0deg));
  transition: transform 0.3s var(--ease), background-color 0.3s var(--ease);
  cursor: pointer;
  overflow: hidden;
  animation: place 0.6s var(--ease) both;
}

.tile:hover {
  /* The faintest wash, just enough to show it is tappable. */
  background: rgba(255, 255, 255, 0.5);
  z-index: 3;
}

@keyframes place {
  from {
    opacity: 0;
    transform: translateY(16px) rotate(0deg) scale(0.96);
  }
  to {
    opacity: 1;
    transform: rotate(var(--tilt, 0deg));
  }
}

.tile__ink {
  display: block;
  flex: 0 1 auto;
  min-height: 0;
  width: 100%;
  max-height: 150px;
}

.tile__ink path {
  fill: none;
  stroke: var(--pen, var(--ink));
  stroke-width: 0.012;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.tile__by {
  flex: none;
  margin: 10px 0 0;
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: 8px;
  flex-wrap: wrap;
}

.tile__name {
  font-family: var(--font-body);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.06em;
  color: var(--ink-soft);
}

/* A photo tucked into the corner of a signature, like one stuck on with a
   corner mount. Sized against the tile so it holds its proportion at any zoom. */
.tile__snap {
  position: absolute;
  top: 6%;
  right: 6%;
  width: 26%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  border: 3px solid #fffdfd;
  border-radius: 2px;
  box-shadow: 0 4px 10px -4px rgba(74, 43, 56, 0.55);
  transform: rotate(2.5deg);
}

/* A note or a voice message is a small mark beside the name; the thing itself
   is in the detail view. */
.tile__mark {
  font-size: 13px;
  line-height: 1;
  color: var(--rose);
}

.pen-ink {
  --pen: var(--ink);
}
.pen-rose {
  --pen: var(--rose);
}
.pen-plum {
  --pen: var(--plum);
}
.pen-gold {
  --pen: var(--gold);
}
.pen-sky {
  --pen: var(--sky);
}

/* --- an empty spot ------------------------------------------------------- */

/* A spot is drawn in world units and then scaled by the board's zoom, so a
   fixed 13px label renders at 3px once the whole card is fitted on a phone.
   Everything here is sized against --vs (the live view scale, set by
   js/board.js) so it lands at a constant size *on screen* however far out the
   camera is, and is clamped at both ends so it never outgrows its own box. */
.spot {
  --spot-text: clamp(22px, calc(13px / var(--vs, 0.5)), 64px);
  --spot-edge: clamp(2px, calc(1.5px / var(--vs, 0.5)), 8px);

  position: absolute;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.3em;
  padding: 6%;
  overflow: hidden;
  border-radius: 3px;
  border: var(--spot-edge) dashed var(--spot-line);
  background: transparent;
  color: var(--spot-ink);
  font-family: var(--font-body);
  font-size: var(--spot-text);
  font-weight: 600;
  line-height: 1.1;
  letter-spacing: 0.08em;
  text-align: center;
  text-transform: uppercase;
  cursor: pointer;
  transition: background-color 0.25s var(--ease), border-color 0.25s var(--ease),
    color 0.25s var(--ease), transform 0.25s var(--ease);
}

.spot:hover {
  background: rgba(255, 255, 255, 0.55);
  border-color: var(--rose-deep);
  color: var(--rose-deep);
  transform: scale(1.02);
}

.spot__plus {
  font-size: 1.5em;
  font-weight: 400;
  line-height: 1;
}

.spot--taken {
  cursor: not-allowed;
  border-style: solid;
  border-color: var(--held-ink);
  background: var(--held-wash);
  color: var(--held-ink);
}

.spot--taken:hover {
  transform: none;
  background: var(--held-wash);
  border-color: var(--held-ink);
  color: var(--held-ink);
}

.spot--mine {
  border-style: solid;
  border-color: var(--rose-deep);
  background: rgba(208, 92, 134, 0.12);
  color: var(--rose-deep);
}

/* Windows high-contrast and forced-colours modes throw the palette away, so
   say what these are in terms the mode still honours. */
@media (forced-colors: active) {
  .spot {
    border-color: ButtonBorder;
    color: ButtonText;
  }

  .spot--taken {
    color: GrayText;
    border-color: GrayText;
  }
}

/* =========================================================================
   HUD
   ========================================================================= */

.hud {
  animation: chromeIn 0.5s var(--ease) both;
  position: fixed;
  left: 50%;
  bottom: max(1rem, env(safe-area-inset-bottom));
  transform: translateX(-50%);
  z-index: 20;
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.55rem 0.6rem 0.55rem 1.1rem;
  border-radius: 999px;
  background: rgba(255, 251, 252, 0.93);
  backdrop-filter: blur(10px);
  border: 1px solid var(--edge-soft);
  box-shadow: 0 18px 40px -22px rgba(74, 43, 56, 0.8);
  max-width: calc(100vw - 1.5rem);
}

.hud__tally {
  margin: 0;
  font-family: var(--font-hand);
  font-size: 1.15rem;
  word-spacing: var(--word-space);
  color: var(--ink-soft);
  white-space: nowrap;
}

.hud__zoom {
  display: flex;
  gap: 0.2rem;
}

.hud__btn {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  border: 1px solid var(--edge-soft);
  background: rgba(255, 255, 255, 0.7);
  color: var(--ink-soft);
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
  transition: background-color 0.2s var(--ease), color 0.2s var(--ease);
}

.hud__btn:hover {
  background: #fff;
  color: var(--ink);
}

.hud__cta {
  padding: 0.72rem 1.2rem;
  font-size: 0.68rem;
  white-space: nowrap;
}

@media (max-width: 34rem) {
  .hud__tally {
    display: none;
  }

  .hud {
    padding-left: 0.6rem;
  }
}

.board__hint {
  animation: chromeIn 0.5s var(--ease) 0.1s both;
  position: fixed;
  left: 50%;
  top: max(1rem, env(safe-area-inset-top));
  transform: translateX(-50%);
  z-index: 20;
  margin: 0;
  padding: 0.45rem 1rem;
  border-radius: 999px;
  background: rgba(255, 251, 252, 0.84);
  backdrop-filter: blur(8px);
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  color: var(--ink-soft);
  white-space: nowrap;
  max-width: calc(100vw - 2rem);
  overflow: hidden;
  text-overflow: ellipsis;
  transition: opacity 0.6s var(--ease);
}

.board__hint.is-gone {
  opacity: 0;
}

/* Mockup mode says so, and keeps saying so. */
.board__hint--mockup {
  background: var(--rose);
  color: #fff;
  font-weight: 600;
}

.board__hint--mockup.is-gone {
  opacity: 0.85;
}

/* =========================================================================
   Buttons
   ========================================================================= */

.btn {
  font-family: var(--font-body);
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  padding: 1rem 1.9rem;
  border-radius: 999px;
  border: 1px solid transparent;
  cursor: pointer;
  transition: transform 0.25s var(--ease), box-shadow 0.25s var(--ease),
    background-color 0.25s var(--ease), color 0.25s var(--ease);
}

.btn:active {
  transform: translateY(1px);
}

.btn--primary {
  color: #fff6f9;
  background: linear-gradient(160deg, #6b3350 0%, #4a2b38 100%);
  box-shadow: 0 12px 26px -14px rgba(74, 43, 56, 0.9), inset 0 1px 0 rgba(255, 255, 255, 0.14);
}

.btn--primary:hover {
  transform: translateY(-2px);
}

.btn--primary:disabled {
  opacity: 0.55;
  cursor: progress;
  transform: none;
}

.btn--ghost {
  color: var(--ink-soft);
  background: transparent;
  border-color: var(--edge);
}

.btn--ghost:hover {
  color: var(--ink);
  border-color: rgba(74, 43, 56, 0.32);
}

:where(button, a, input, textarea, [tabindex]):focus-visible {
  outline: 2px solid var(--rose);
  outline-offset: 3px;
}

/* =========================================================================
   Compose sheet
   ========================================================================= */

.sheet {
  width: min(34rem, 100vw);
  max-height: 92svh;
  padding: 0;
  border: 0;
  margin: auto auto 0;
  background: var(--card);
  border-radius: 18px 18px 0 0;
  box-shadow: 0 -20px 60px -20px rgba(74, 43, 56, 0.5);
  color: var(--ink);
  overflow: hidden auto;
  overscroll-behavior: contain;
}

@media (min-width: 40rem) {
  .sheet {
    margin: auto;
    border-radius: 6px;
  }
}

.sheet::backdrop,
.lightbox::backdrop {
  background: rgba(70, 34, 48, 0.62);
  backdrop-filter: blur(3px);
}

.sheet[open] {
  animation: sheetIn 0.42s var(--ease);
}

@keyframes chromeIn {
  from {
    opacity: 0;
    transform: translate(-50%, 8px);
  }
  to {
    opacity: 1;
    transform: translate(-50%, 0);
  }
}

@keyframes sheetIn {
  from {
    opacity: 0;
    transform: translateY(24px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

.sheet__inner {
  padding: 1.25rem clamp(1.25rem, 5vw, 2.25rem) clamp(1.5rem, 5vw, 2.25rem);
}

.sheet__grip {
  width: 40px;
  height: 4px;
  border-radius: 999px;
  background: var(--edge);
  margin: 0 auto 1.1rem;
}

@media (min-width: 40rem) {
  .sheet__grip {
    display: none;
  }
}

.sheet__head {
  margin-bottom: 1.6rem;
}

.sheet__title {
  margin: 0;
  font-family: var(--font-display);
  font-variation-settings: 'SOFT' 50, 'WONK' 1, 'opsz' 48;
  font-weight: 500;
  font-size: 1.7rem;
}

.sheet__sub {
  margin: 0.3rem 0 0;
  font-size: 0.9rem;
  color: var(--ink-soft);
}

.field {
  margin-bottom: 1.35rem;
}

.field__row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
}

.field > label,
.field__label,
.field__row label {
  display: block;
  margin-bottom: 0.45rem;
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

/* Said under the two fields that don't go on the card. Signers should know
   who a photo or a recording is actually for before they leave one. */
.field__hint {
  margin: -0.2rem 0 0.55rem;
  font-size: 0.8rem;
  line-height: 1.5;
  color: var(--plum);
}

.opt {
  font-weight: 400;
  letter-spacing: 0.08em;
  text-transform: none;
  color: var(--ink-soft);
}

input[type='text'],
textarea {
  width: 100%;
  font-family: var(--font-body);
  font-size: 1rem;
  color: var(--ink);
  background: rgba(255, 255, 255, 0.6);
  border: 1px solid var(--edge);
  border-radius: 4px;
  padding: 0.75rem 0.85rem;
  transition: border-color 0.2s var(--ease), background-color 0.2s var(--ease);
}

input[type='text']:focus,
textarea:focus {
  background: #fff;
  border-color: rgba(74, 43, 56, 0.4);
}

textarea {
  resize: vertical;
  min-height: 6rem;
  line-height: 1.6;
}

.linkbtn {
  border: 0;
  background: none;
  padding: 0;
  cursor: pointer;
  font-family: var(--font-body);
  font-size: 0.72rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-soft);
  text-decoration: underline;
  text-underline-offset: 3px;
}

.linkbtn:hover {
  color: var(--rose-deep);
}

/* --- signature pad ------------------------------------------------------- */

.pad-wrap {
  position: relative;
  background: rgba(255, 255, 255, 0.68);
  border: 1px solid var(--edge);
  border-radius: 4px;
  overflow: hidden;
}

.pad {
  display: block;
  width: 100%;
  /* Room to actually write a line or draw something, not just initial it. */
  height: clamp(190px, 46vw, 260px);
  touch-action: none;
  cursor: crosshair;
}

.pad.is-erasing {
  cursor: cell;
}

.pad__baseline {
  position: absolute;
  left: 8%;
  right: 8%;
  bottom: 22%;
  height: 1px;
  background: repeating-linear-gradient(90deg, var(--edge) 0 6px, transparent 6px 12px);
  pointer-events: none;
}

.pad__hint {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  font-family: var(--font-hand);
  font-size: 1.15rem;
  word-spacing: var(--word-space);
  color: var(--ink-soft);
  pointer-events: none;
  transition: opacity 0.25s var(--ease), visibility 0.25s;
}

/* Fades rather than snapping, so it can't use [hidden] — but visibility still
   takes it out of hit-testing and out of the accessibility tree. */
.pad__hint.is-gone {
  opacity: 0;
  visibility: hidden;
}

.padbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-top: 0.8rem;
  flex-wrap: wrap;
}

.inks {
  display: flex;
  gap: 0.55rem;
  margin: 0;
  padding: 0;
  border: 0;
}

.tools {
  display: flex;
  gap: 0.35rem;
}

.tool {
  width: 38px;
  height: 38px;
  display: grid;
  place-items: center;
  border-radius: 10px;
  border: 1px solid var(--edge);
  background: rgba(255, 255, 255, 0.65);
  color: var(--ink-soft);
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
  transition: background-color 0.2s var(--ease), color 0.2s var(--ease),
    border-color 0.2s var(--ease);
}

.tool:hover {
  color: var(--ink);
  border-color: rgba(74, 43, 56, 0.3);
  background: #fff;
}

.tool.is-on {
  background: var(--rose);
  border-color: var(--rose);
  color: #fff;
}

.tool:disabled {
  opacity: 0.4;
  cursor: default;
}

.ink input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

.ink > span:first-of-type {
  display: block;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: var(--swatch);
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.9), 0 0 0 2px var(--edge-soft);
  cursor: pointer;
  transition: transform 0.2s var(--ease), box-shadow 0.2s var(--ease);
}

.ink:hover > span:first-of-type {
  transform: scale(1.1);
}

.ink input:checked + span {
  box-shadow: 0 0 0 2px var(--card), 0 0 0 3px var(--swatch);
  transform: scale(1.1);
}

.ink input:focus-visible + span {
  outline: 2px solid var(--rose);
  outline-offset: 3px;
}

.ink--ink {
  --swatch: #4a2b38;
}
.ink--rose {
  --swatch: #d05c86;
}
.ink--plum {
  --swatch: #8e3d63;
}
.ink--gold {
  --swatch: #c08a3e;
}
.ink--sky {
  --swatch: #5b7f9c;
}

/* --- the optional extras ------------------------------------------------- */

/* A photo, a voice message and a note. Set apart from the name and the
   signature, because none of them is needed to sign. */
.extras {
  margin: 0 0 0.35rem;
  padding: 1.25rem 0 0.15rem;
  border: 0;
  border-top: 1px solid var(--edge-soft);
}

/* A legend is laid into the fieldset's own border, which leaves a stub of rule
   running off beside it. Floating it takes it out of that slot and puts it back
   in the flow, so the rule above stays unbroken. */
.extras__legend {
  float: left;
  width: 100%;
  padding: 0;
  font-family: var(--font-display);
  font-variation-settings: 'SOFT' 50, 'opsz' 24;
  font-size: 1.05rem;
  color: var(--ink);
}

.extras__sub {
  clear: both;
  margin: 0.15rem 0 1.2rem;
  font-size: 0.85rem;
  color: var(--ink-soft);
}

/* A file input is unstylable and ugly, so the input itself is hidden inside
   its own label: clicking the face still opens the picker natively, and the
   input keeps the focus ring and the keyboard behaviour it was born with. */
.pickbtn {
  display: inline-block;
}

.pickbtn__face {
  display: inline-block;
  padding: 0.7rem 1.2rem;
  border-radius: 999px;
  border: 1px solid var(--edge);
  background: rgba(255, 255, 255, 0.65);
  color: var(--ink-soft);
  font-family: var(--font-body);
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  cursor: pointer;
  transition: background-color 0.2s var(--ease), color 0.2s var(--ease),
    border-color 0.2s var(--ease);
}

.pickbtn:hover .pickbtn__face {
  color: var(--ink);
  border-color: rgba(74, 43, 56, 0.32);
  background: #fff;
}

/* The input is off-screen, so its own outline would be too. */
.pickbtn input:focus-visible + .pickbtn__face {
  outline: 2px solid var(--rose);
  outline-offset: 3px;
}

/* What has been chosen so far — the photo, or the recording to play back. */
.picked {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  margin-top: 0.9rem;
  padding: 0.7rem;
  border-radius: 6px;
  background: rgba(255, 255, 255, 0.6);
  border: 1px solid var(--edge-soft);
  flex-wrap: wrap;
}

.picked__thumb {
  width: 74px;
  height: 74px;
  flex: none;
  object-fit: cover;
  border-radius: 4px;
  background: rgba(74, 43, 56, 0.06);
}

.picked__side {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.35rem;
}

.picked__what {
  margin: 0;
  font-size: 0.78rem;
  color: var(--ink-soft);
}

.picked__play {
  flex: 1 1 12rem;
  min-width: 0;
  height: 40px;
}

/* --- the recorder -------------------------------------------------------- */

.rec {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  flex-wrap: wrap;
}

.rec__btn {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  padding: 0.7rem 1.2rem;
  border-radius: 999px;
  border: 1px solid var(--edge);
  background: rgba(255, 255, 255, 0.65);
  color: var(--ink-soft);
  font-family: var(--font-body);
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  cursor: pointer;
  transition: background-color 0.2s var(--ease), color 0.2s var(--ease),
    border-color 0.2s var(--ease);
}

.rec__btn:hover {
  color: var(--ink);
  border-color: rgba(74, 43, 56, 0.32);
  background: #fff;
}

.rec__dot {
  width: 11px;
  height: 11px;
  border-radius: 50%;
  background: var(--rose-deep);
}

.rec__btn.is-recording {
  border-color: var(--rose-deep);
  color: var(--rose-deep);
}

.rec__btn.is-recording .rec__dot {
  animation: pulse 1.1s var(--ease) infinite;
}

@keyframes pulse {
  0%,
  100% {
    opacity: 1;
    transform: scale(1);
  }
  50% {
    opacity: 0.35;
    transform: scale(0.78);
  }
}

.rec__state {
  margin: 0;
  font-size: 0.82rem;
  color: var(--ink-soft);
}

/* --- how much of a note is left ------------------------------------------ */

.count {
  margin: 0.4rem 0 0;
  font-size: 0.72rem;
  letter-spacing: 0.06em;
  color: var(--ink-soft);
  text-align: right;
}

.hp {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.formerror {
  margin: 0 0 1rem;
  padding: 0.7rem 0.9rem;
  font-size: 0.88rem;
  color: #8e2e50;
  background: rgba(208, 92, 134, 0.1);
  border-left: 3px solid var(--rose);
  border-radius: 3px;
}

.sheet__actions {
  display: flex;
  gap: 0.75rem;
  justify-content: flex-end;
  align-items: center;
  margin-top: 1.75rem;
}

@media (max-width: 30rem) {
  .sheet__actions {
    flex-direction: column-reverse;
    align-items: stretch;
  }

  .sheet__actions .btn {
    width: 100%;
  }
}

/* =========================================================================
   Detail lightbox
   ========================================================================= */

.lightbox {
  width: min(36rem, calc(100vw - 2rem));
  max-height: 90svh;
  padding: 0;
  border: 0;
  border-radius: 6px;
  background: var(--card);
  color: var(--ink);
  overflow: visible;
}

.lightbox[open] {
  animation: sheetIn 0.35s var(--ease);
}

.lightbox__inner {
  position: relative;
  max-height: 90svh;
  overflow: hidden auto;
  padding: clamp(1.5rem, 5vw, 2.5rem);
}

.lightbox__close {
  position: absolute;
  top: 0.6rem;
  right: 0.7rem;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  border: 0;
  background: rgba(74, 43, 56, 0.06);
  color: var(--ink-soft);
  font-size: 1.3rem;
  line-height: 1;
  cursor: pointer;
}

.lightbox__close:hover {
  background: rgba(74, 43, 56, 0.12);
  color: var(--ink);
}

.detail__ink {
  display: block;
  width: min(100%, 15rem);
  margin-bottom: 0.5rem;
}

.detail__ink path {
  fill: none;
  stroke: var(--pen, var(--ink));
  stroke-width: 0.012;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.detail__by {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  flex-wrap: wrap;
  padding-top: 0.85rem;
  border-top: 1px solid var(--edge-soft);
}

.detail__name {
  font-family: var(--font-hand);
  font-size: 1.6rem;
  word-spacing: var(--word-space);
  color: var(--pen, var(--ink));
}

.detail__photo {
  display: block;
  width: 100%;
  height: auto;
  max-height: 46svh;
  object-fit: contain;
  object-position: center;
  margin-bottom: 1.1rem;
  border-radius: 4px;
  background: rgba(74, 43, 56, 0.05);
}

.detail__note {
  margin: 0.2rem 0 1.1rem;
  font-family: var(--font-display);
  font-variation-settings: 'SOFT' 50, 'opsz' 24;
  font-size: 1.05rem;
  line-height: 1.65;
  white-space: pre-line;
  color: var(--ink);
}

/*
 * What a guest is told instead of being shown. Quieter than a note — it is a
 * caption about the card, not something anybody wrote.
 */
.detail__held {
  margin: 0.2rem 0 1.1rem;
  font-size: 0.82rem;
  letter-spacing: 0.04em;
  font-style: italic;
  color: var(--plum);
  opacity: 0.8;
}

.detail__audio {
  display: block;
  width: 100%;
  margin-bottom: 1.1rem;
}

.detail__when {
  margin-left: auto;
  font-size: 0.7rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

/* =========================================================================
   Toast
   ========================================================================= */

.toast {
  position: fixed;
  left: 50%;
  bottom: calc(max(1rem, env(safe-area-inset-bottom)) + 4.5rem);
  transform: translate(-50%, 130%);
  z-index: 50;
  max-width: min(26rem, calc(100vw - 2rem));
  padding: 0.85rem 1.35rem;
  border-radius: 999px;
  background: #4a2b38;
  color: #fff6f9;
  font-family: var(--font-hand);
  font-size: 1.15rem;
  word-spacing: var(--word-space);
  text-align: center;
  box-shadow: 0 18px 40px -18px rgba(74, 43, 56, 0.9);
  transition: transform 0.45s var(--ease);
}

.toast.is-up {
  transform: translate(-50%, 0);
}

/* =========================================================================
   Reduced motion — keep the design, drop the movement.
   ========================================================================= */

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
}
