/* ---- Guestbook Design Tokens ---- */
/*
 * Single source of truth for guestbook colors.
 * All guestbook components should reference these variables only —
 * never hardcode the hex values.
 *
 * When migrating to Next.js, this file moves as-is and is imported
 * once at the root (or scoped via CSS Modules).
 */

/* Caveat — handwritten font used by guestbook entry cards.
   @import must be the first non-comment rule in the stylesheet. */
@import url('https://fonts.googleapis.com/css2?family=Caveat:wght@400;500;600&display=swap');

:root {
  --gb-orange: #e8541a;
  --gb-cream: #f5e6c8;
  --gb-panel: #f7f2ea;
  --gb-slot: #1a1a1a;
  --gb-slot-glow: rgba(232, 84, 26, 0.4);
}

/* ================================================================
   GuestbookCard
   ================================================================
   Horizontal library-card that peeks from a mail slot at the bottom
   of the viewport. In the resting state only the top strip (pattern
   band + header) is visible above the slot; clicking the card lifts
   it fully above the slot so the visitor can type a name and sign
   directly on the card.

   State classes live on .gb-card-slot-wrap:
     (default)        — peeking from slot
     .is-open         — fully lifted, form interactive
     .has-signature   — controls Clear button visibility
*/

/* Fixed anchor at the bottom center of the viewport. Zero height —
   the slot + card position themselves against its bottom: 0 baseline.
   pointer-events: none so the wrap itself doesn't block clicks on
   the rest of the page; the card + clear button re-enable themselves. */
.gb-card-slot-wrap {
  position: fixed;
  bottom: 0;
  left: 50%;
  transform: translateX(-50%);
  width: 440px;
  max-width: calc(100vw - 24px);
  height: 0;
  pointer-events: none;
  z-index: 9000;
  font-family: 'Georgia', 'Times New Roman', serif;
}

/* Dismiss group — transparent wrapper around card + color picker.
   Does nothing visually; exists only so dismiss can translateY a
   single parent and move card + pickers as one unit. */
.gb-dismiss-group {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  pointer-events: none;
}

/* The dark horizontal slit. Spans slightly wider than the card so
   the card appears to emerge from a slot that fully contains its
   width. Stays visible in every state — this same element becomes
   the mail-slot drop target in a later prompt. */
.gb-slot {
  position: absolute;
  bottom: 0;
  left: 50%;
  transform: translateX(-50%);
  width: 460px;
  max-width: calc(100% + 20px);
  height: 6px;
  background: var(--gb-slot);
  border-radius: 3px;
  box-shadow:
    inset 0 1.5px 3px rgba(0, 0, 0, 0.9),
    inset 0 -0.5px 0 rgba(255, 255, 255, 0.06),
    0 1px 2px rgba(0, 0, 0, 0.15);
  z-index: 2;
  pointer-events: none;
}

/* The card face. Background + ink are driven by CSS custom properties
   set from JS (applyCardColor), which lets the color picker re-theme
   the whole card in one assignment. Rounded top corners, sits behind
   the slot so its bottom edge visually disappears into it.

   Resting translate hides most of the card below the slot, leaving
   ~130px visible — enough room for the pattern band + header CTA. */
.gb-card {
  position: absolute;
  bottom: -15px;
  left: 0;
  right: 0;
  width: 100%;
  /* Fixed, compact height — the signature area rearranges (label above
     the line) so everything fits within this box. Consistent 28px inner
     padding on every edge gives balanced breathing room. */
  height: 340px;
  padding: 28px;
  box-sizing: border-box;
  /* Flex column so the body can flex: 1 and push the signature to the
     bottom of the content area, matching a real library card where
     the signature line sits near the card's bottom edge. */
  display: flex;
  flex-direction: column;
  /* Clip everything to the card's rounded rectangle — nothing inside
     (header pattern, name input, canvas stroke, etc.) can bleed outside
     the border-radius. isolation: isolate creates a new stacking
     context so overflow: hidden respects the 14px rounded corners on
     every browser. */
  overflow: hidden;
  isolation: isolate;
  background: var(--gb-card-bg, var(--gb-orange));
  color: var(--gb-card-ink, var(--gb-cream));
  border-radius: 14px 14px 0 0;
  /* No overflow clipping on the card — the signature area needs to
     breathe freely without anything getting cut off at the bottom. */
  /* box-shadow is set inline from JS (applyCardColor) so it tracks
     the current card color reactively. The inline style uses the
     formula 0 20px 60px rgba(r,g,b,0.35) + 0 8px 20px rgba(r,g,b,0.2),
     derived from the active cardColor on every swatch / wheel change. */
  font-family: 'Georgia', 'Times New Roman', serif;
  /* Own our typography so host-page styles can't inflate our line
     boxes. index.html's body.minimal ships line-height: 2.4rem +
     letter-spacing: 1px, which — without this reset — fattens every
     row inside the card, pushes the signature area past the 340px
     overflow: hidden boundary, and clips the baseline rule. Setting
     these explicitly on the card makes the component render the
     same on any page. */
  line-height: 1.4;
  letter-spacing: normal;
  pointer-events: auto;
  cursor: pointer;
  z-index: 1;
  -webkit-tap-highlight-color: transparent;

  /* Peek — only the very top of the card is above the slot, showing
     the pattern band, GUEST CARD label, and the headline. Nothing
     from the body (name, date, signature) should be visible until
     the card is opened. Card height is 340, so translateY(260)
     leaves ~77px visible — just enough for the header + top padding. */
  transform: translateY(260px);
  transition:
    transform 320ms cubic-bezier(0.22, 1, 0.36, 1),
    bottom 320ms cubic-bezier(0.22, 1, 0.36, 1),
    background 260ms ease,
    color 260ms ease,
    box-shadow 320ms ease-out;
}

.gb-card:focus-visible {
  outline: 2px solid var(--gb-cream);
  outline-offset: 3px;
}

/* Hover in peek mode: lift ~45px more to invite interaction. Shadow
   stays in sync with the card color via the inline style set by
   applyCardColor. Still hides the signature area. */
.gb-card-slot-wrap:not(.is-open) .gb-card:hover {
  transform: translateY(215px);
}

/* Open: lift the full 360px card well above the slot so there's
   clear breathing room beneath it (room for the color picker), round
   all four corners, and switch the cursor back to default since the
   card is now a form. */
.gb-card-slot-wrap.is-open .gb-card {
  bottom: 85px;
  transform: translateY(-210px);
  border-radius: 14px;
  cursor: default;
  transition:
    transform 520ms cubic-bezier(0.34, 1.35, 0.64, 1),
    bottom 520ms cubic-bezier(0.34, 1.35, 0.64, 1),
    border-radius 320ms ease-out 80ms,
    background 260ms ease,
    color 260ms ease,
    box-shadow 520ms ease-out;
  /* box-shadow comes from the inline JS style so it tracks card color. */
}

/* Custom wheel mode — the picker grows taller, so lift the card
   further to make room underneath. */
.gb-card-slot-wrap.is-open.is-custom-color .gb-card {
  transform: translateY(-320px);
}

/* Card pattern — a full-card background texture rendered behind the
   content. Sits on top of the solid card color but below the header
   + body (z-index 1 here, header z-index 3, body z-index 2). Two
   absolutely-stacked child layers (.gb-card-pattern-layer) hold the
   current and next SVG pattern; JS toggles `.is-active` to crossfade
   between them over 300ms when the pattern key changes. */
.gb-card-pattern {
  position: absolute;
  inset: 0;
  pointer-events: none;
  border-radius: inherit;
  overflow: hidden;
  z-index: 1;
}

.gb-card-pattern-layer {
  position: absolute;
  inset: 0;
  opacity: 0;
  /* Pattern tiles all use `currentColor` — driven by a darker shade
     of the active card color, set in JS (applyCardColor). */
  color: var(--gb-card-pattern-color, #000);
  transition: opacity 300ms ease;
}

.gb-card-pattern-layer.is-active {
  /* 25–30% opacity keeps the texture present but quiet. */
  opacity: 0.28;
}

.gb-card-pattern-layer svg {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
}

/* Header — overlays the pattern band so it remains visible in the
   peek state and acts as the CTA. Non-interactive (clicks pass
   through to the card, which handles the open transition). Lives
   in normal flow inside the padded card, with an explicit bottom
   margin before the fields row. Inherits --gb-card-ink from the
   card (set inline by applyCardColor) so the text always contrasts
   the active card color. */
.gb-card-header {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 4px;
  pointer-events: none;
  z-index: 3;
  margin-bottom: 20px;
}

/* Content area — fields row + signature. Lives in normal flow
   inside the card's padding box so the card height naturally grows
   to contain everything. Non-interactive in peek mode (pointer-events:
   none) so the whole card surface captures clicks; re-enabled when
   the card is open. */
.gb-card-body {
  position: relative;
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
  pointer-events: none;
  z-index: 2;
}

.gb-card-slot-wrap.is-open .gb-card-body {
  pointer-events: auto;
}

.gb-card-label {
  font-size: 10px;
  letter-spacing: 0.32em;
  text-transform: uppercase;
  opacity: 0.72;
  font-weight: 400;
}

.gb-card-title {
  font-size: 17px;
  font-style: italic;
  font-weight: 500;
  letter-spacing: 0.005em;
  line-height: 1.25;
  max-width: 320px;
}

/* NAME + ISSUED row. Name takes the remaining width; date shrinks
   to its own content. Both share the same field label style. */
.gb-card-fields {
  display: flex;
  gap: 24px;
  align-items: flex-end;
  margin-bottom: 10px;
}

.gb-card-field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
}

.gb-card-field-name {
  flex: 1 1 auto;
  min-width: 0;
}

.gb-card-field-date {
  flex: 0 0 auto;
  text-align: right;
}

.gb-card-field-label {
  font-size: 9px;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  opacity: 0.7;
  font-weight: 400;
}

/* NAME row — wraps the input and confirm button behind a shared
   baseline. The underline lives on the row (not the input) so the
   check button reads as sitting *on* the baseline, not next to a
   dangling rule. */
.gb-card-name-row {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  min-width: 0;
  padding-bottom: 2px;
  border-bottom: 1px solid
    color-mix(in srgb, var(--gb-card-ink, var(--gb-cream)) 38%, transparent);
  transition: border-color 180ms ease;
}

.gb-card-name-row:focus-within {
  border-bottom-color: color-mix(
    in srgb,
    var(--gb-card-ink, var(--gb-cream)) 75%,
    transparent
  );
}

/* NAME input — handwritten Caveat, no borders of its own (the row
   owns the underline now). */
.gb-card-name {
  flex: 1 1 auto;
  min-width: 0;
  box-sizing: border-box;
  padding: 2px 0 2px;
  border: none;
  background: transparent;
  font-family: 'Caveat', 'Bradley Hand', cursive;
  font-size: 24px;
  line-height: 1.1;
  color: var(--gb-card-ink, var(--gb-cream));
  outline: none;
  transition: color 260ms ease;
}

.gb-card-name::placeholder {
  color: color-mix(in srgb, var(--gb-card-ink, var(--gb-cream)) 45%, transparent);
  font-style: italic;
}

/* Check button that locks the name. Always visible — empty names can
   be locked. Click locks the name field independently. */
.gb-card-name-confirm {
  flex-shrink: 0;
  width: 26px;
  height: 26px;
  padding: 0;
  border-radius: 50%;
  background: transparent;
  border: 1px solid
    color-mix(in srgb, var(--gb-card-ink, var(--gb-cream)) 38%, transparent);
  color: var(--gb-card-ink, var(--gb-cream));
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  opacity: 0.85;
  pointer-events: auto;
  transition: opacity 220ms ease, background 180ms ease, border-color 180ms ease;
}

.gb-card-name-confirm svg {
  width: 13px;
  height: 13px;
  display: block;
}

.gb-card-name-confirm:hover {
  background: color-mix(
    in srgb,
    var(--gb-card-ink, var(--gb-cream)) 14%,
    transparent
  );
  opacity: 1;
  border-color: color-mix(
    in srgb,
    var(--gb-card-ink, var(--gb-cream)) 65%,
    transparent
  );
}

.gb-card-name-confirm:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px
    color-mix(in srgb, var(--gb-card-ink, var(--gb-cream)) 70%, transparent);
  opacity: 1;
}

/* Confirmed view — the row collapses and a static printed name
   takes its place, with a tiny italic "edit" link that drops the
   field back into input mode. */
.gb-card-name-display {
  display: none;
  align-items: baseline;
  gap: 12px;
  width: 100%;
  min-width: 0;
  padding-bottom: 2px;
}

.gb-card-field-name.is-confirmed .gb-card-name-row {
  display: none;
}

.gb-card-field-name.is-confirmed .gb-card-name-display {
  display: flex;
}

.gb-card-name-printed {
  flex: 1 1 auto;
  min-width: 0;
  font-family: 'Caveat', 'Bradley Hand', cursive;
  font-size: 26px;
  line-height: 1.1;
  color: var(--gb-card-ink, var(--gb-cream));
  padding: 2px 0 2px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: color 260ms ease;
}

.gb-card-name-edit {
  flex-shrink: 0;
  background: none;
  border: none;
  padding: 2px 4px;
  font-family: 'Georgia', 'Times New Roman', serif;
  font-style: italic;
  font-size: 11px;
  letter-spacing: 0.02em;
  color: var(--gb-card-ink, var(--gb-cream));
  opacity: 0.55;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-color: color-mix(
    in srgb,
    var(--gb-card-ink, var(--gb-cream)) 40%,
    transparent
  );
  transition: opacity 180ms ease, color 260ms ease;
}

.gb-card-name-edit:hover,
.gb-card-name-edit:focus-visible {
  opacity: 0.95;
  outline: none;
}

/* ISSUED — plain serif, no border, just reads as typed on. */
.gb-card-date {
  font-size: 14px;
  font-family: 'Georgia', 'Times New Roman', serif;
  letter-spacing: 0.05em;
  padding: 2px 0 4px;
  white-space: nowrap;
}

/* MESSAGE field — full-width short note below the NAME/ISSUED row.
   Optional: visitor can skip it. Shares the "name/message" lock group
   with the NAME field — clicking the name ✓ locks both fields
   together; clicking either edit link unlocks both. There is no
   per-field confirm button on MESSAGE. */
.gb-card-field-message {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
  width: 100%;
  margin-bottom: 10px;
}

.gb-card-message-row {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  min-width: 0;
  padding-bottom: 2px;
  border-bottom: 1px solid
    color-mix(in srgb, var(--gb-card-ink, var(--gb-cream)) 38%, transparent);
  transition: border-color 180ms ease;
}

.gb-card-message-row:focus-within {
  border-bottom-color: color-mix(
    in srgb,
    var(--gb-card-ink, var(--gb-cream)) 75%,
    transparent
  );
}

.gb-card-message {
  flex: 1 1 auto;
  min-width: 0;
  box-sizing: border-box;
  padding: 2px 0 2px;
  border: none;
  background: transparent;
  font-family: 'Caveat', 'Bradley Hand', cursive;
  font-size: 22px;
  line-height: 1.1;
  color: var(--gb-card-ink, var(--gb-cream));
  outline: none;
  transition: color 260ms ease;
}

.gb-card-message::placeholder {
  color: color-mix(in srgb, var(--gb-card-ink, var(--gb-cream)) 45%, transparent);
  font-style: italic;
}

/* Counter — hidden by default, fades in once the typed value is
   within 10 characters of the 60-char limit. Faint serif, small. */
.gb-card-message-counter {
  flex-shrink: 0;
  font-family: 'Georgia', 'Times New Roman', serif;
  font-size: 10px;
  letter-spacing: 0.02em;
  color: var(--gb-card-ink, var(--gb-cream));
  opacity: 1;
  pointer-events: none;
  transition: opacity 180ms ease, color 260ms ease;
}

.gb-card-field-message.is-near-limit .gb-card-message-counter {
  opacity: 0.5;
}

/* Message ✓ — inline at right end of message row, mirrors the name ✓.
   Always visible (empty messages can be locked). */
.gb-card-message-confirm {
  flex-shrink: 0;
  width: 26px;
  height: 26px;
  padding: 0;
  border-radius: 50%;
  background: transparent;
  border: 1px solid
    color-mix(in srgb, var(--gb-card-ink, var(--gb-cream)) 38%, transparent);
  color: var(--gb-card-ink, var(--gb-cream));
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  opacity: 0.85;
  pointer-events: auto;
  transition: opacity 220ms ease, background 180ms ease, border-color 180ms ease;
}

.gb-card-message-confirm svg {
  width: 13px;
  height: 13px;
  display: block;
}

.gb-card-message-confirm:hover {
  background: color-mix(
    in srgb,
    var(--gb-card-ink, var(--gb-cream)) 14%,
    transparent
  );
  opacity: 1;
  border-color: color-mix(
    in srgb,
    var(--gb-card-ink, var(--gb-cream)) 65%,
    transparent
  );
}

.gb-card-message-confirm:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px
    color-mix(in srgb, var(--gb-card-ink, var(--gb-cream)) 70%, transparent);
  opacity: 1;
}

/* Confirmed view — row collapses, printed message + edit link take
   over. Mirrors the NAME field's confirmed state. */
.gb-card-message-display {
  display: none;
  align-items: baseline;
  gap: 12px;
  width: 100%;
  min-width: 0;
  padding-bottom: 2px;
}

.gb-card-field-message.is-confirmed .gb-card-message-row {
  display: none;
}

.gb-card-field-message.is-confirmed .gb-card-message-display {
  display: flex;
}

.gb-card-message-printed {
  flex: 1 1 auto;
  min-width: 0;
  font-family: 'Caveat', 'Bradley Hand', cursive;
  font-size: 22px;
  line-height: 1.1;
  color: var(--gb-card-ink, var(--gb-cream));
  padding: 2px 0 2px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: color 260ms ease;
}

/* Empty message placeholder in the confirmed view — muted em dash so
   the row still has a visible baseline alongside the "edit" link. */
.gb-card-message-printed:empty::before {
  content: '—';
  opacity: 0.4;
  font-style: italic;
}

.gb-card-message-edit {
  flex-shrink: 0;
  background: none;
  border: none;
  padding: 2px 4px;
  font-family: 'Georgia', 'Times New Roman', serif;
  font-style: italic;
  font-size: 11px;
  letter-spacing: 0.02em;
  color: var(--gb-card-ink, var(--gb-cream));
  opacity: 0.55;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-color: color-mix(
    in srgb,
    var(--gb-card-ink, var(--gb-cream)) 40%,
    transparent
  );
  transition: opacity 180ms ease, color 260ms ease;
}

.gb-card-message-edit:hover,
.gb-card-message-edit:focus-visible {
  opacity: 0.95;
  outline: none;
}

/* Signature area: SIGNATURE label on top, canvas (with its bottom
   border as the signature baseline) directly below it. This order
   keeps the label inside the card while leaving the drawable area
   above the line, like a real form field. margin-top: auto pushes
   the whole block to the bottom of the body flex container. */
.gb-card-signature {
  margin-top: auto;
  display: flex;
  flex-direction: column;
  /* Tight label → canvas gap so the label reads as a field heading. */
  gap: 6px;
}

.gb-card-canvas {
  display: block;
  width: 100%;
  /* Fixed 88px height — fits inside the 340px card after header
     (~40), header margin-bottom (20), fields (~48 + 10 margin),
     MESSAGE field (~48 + 10 margin), SIGNATURE label (11 + 6 gap),
     and 28px padding top + bottom. flex-shrink: 0 so the canvas
     keeps this height even under flex pressure. */
  height: 88px;
  flex-shrink: 0;
  background: transparent;
  border: none;
  border-bottom: 1px solid
    color-mix(in srgb, var(--gb-card-ink, var(--gb-cream)) 45%, transparent);
  cursor: crosshair;
  touch-action: none;
  -webkit-user-select: none;
  user-select: none;
  transition: border-color 260ms ease;
}

.gb-card-sig-label {
  font-size: 9px;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  opacity: 0.7;
  font-weight: 400;
}

/* Clear action — absolute inside the card, sits to the LEFT of the
   signature ✓ button so both actions share the upper-right area of
   the signature baseline. Rides along with the card's lift transform.
   Invisible until the card is open AND signed. The ~6px vertical
   nudge centers the Clear text against the taller ✓ circle. */
.gb-card-clear {
  position: absolute;
  right: 74px;
  bottom: 99px;
  background: none;
  border: none;
  padding: 2px 4px;
  font-family: 'Georgia', 'Times New Roman', serif;
  font-size: 11px;
  letter-spacing: 0.04em;
  color: var(--gb-card-ink, var(--gb-cream));
  opacity: 0;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-color: color-mix(
    in srgb,
    var(--gb-card-ink, var(--gb-cream)) 40%,
    transparent
  );
  transition: opacity 220ms ease, color 260ms ease;
  pointer-events: none;
  z-index: 4;
}

.gb-card-slot-wrap.is-open.has-signature .gb-card-clear {
  opacity: 0.7;
  pointer-events: auto;
}

.gb-card-clear:hover,
.gb-card-clear:focus-visible {
  opacity: 1;
  outline: none;
}

/* Signature ✓ — anchored at the right edge of the signature area,
   directly mirroring the name ✓ on the right side of the name row.
   The Clear action sits just to its left (see .gb-card-clear above).
   Minimal small circle with a checkmark glyph, ink-colored. Shown
   when the card is open; faded/disabled until there's at least one
   stroke; hidden in the locked state. */
.gb-card-sig-confirm {
  position: absolute;
  right: 28px;
  bottom: 93px;
  width: 26px;
  height: 26px;
  padding: 0;
  border-radius: 50%;
  background: transparent;
  border: 1px solid
    color-mix(in srgb, var(--gb-card-ink, var(--gb-cream)) 38%, transparent);
  color: var(--gb-card-ink, var(--gb-cream));
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  pointer-events: none;
  transition: opacity 220ms ease, background 180ms ease,
    border-color 180ms ease, color 260ms ease;
  z-index: 4;
}

.gb-card-sig-confirm svg {
  width: 13px;
  height: 13px;
  display: block;
}

/* Open + no strokes → visible but faded + disabled. */
.gb-card-slot-wrap.is-open .gb-card-sig-confirm {
  opacity: 0.28;
}

/* Open + has a signature → fully visible + clickable. */
.gb-card-slot-wrap.is-open.has-signature .gb-card-sig-confirm {
  opacity: 0.85;
  pointer-events: auto;
}

.gb-card-sig-confirm:hover {
  background: color-mix(
    in srgb,
    var(--gb-card-ink, var(--gb-cream)) 14%,
    transparent
  );
  opacity: 1;
  border-color: color-mix(
    in srgb,
    var(--gb-card-ink, var(--gb-cream)) 65%,
    transparent
  );
}

.gb-card-sig-confirm:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px
    color-mix(in srgb, var(--gb-card-ink, var(--gb-cream)) 70%, transparent);
  opacity: 1;
}

/* Locked state — ✓ and Clear both hide, canvas freezes. The edit
   link takes over the bottom-left spot. Extra-specific selectors
   beat the "is-open.has-signature" rules above. */
.gb-card-slot-wrap.is-open.is-sig-locked .gb-card-sig-confirm,
.gb-card-slot-wrap.is-open.has-signature.is-sig-locked .gb-card-sig-confirm {
  opacity: 0;
  pointer-events: none;
}

.gb-card-slot-wrap.is-open.has-signature.is-sig-locked .gb-card-clear {
  opacity: 0;
  pointer-events: none;
}

.gb-card-slot-wrap.is-sig-locked .gb-card-canvas {
  pointer-events: none;
  cursor: default;
}

/* Signature edit link — takes over the ✓'s slot on the right when
   the signature is locked. Same style as the name edit link so both
   "edit"s read as identical affordances. The ~6px vertical nudge
   centers the text against the ✓ circle it's replacing. */
.gb-card-sig-edit {
  position: absolute;
  right: 28px;
  bottom: 99px;
  background: none;
  border: none;
  padding: 2px 4px;
  font-family: 'Georgia', 'Times New Roman', serif;
  font-style: italic;
  font-size: 11px;
  letter-spacing: 0.02em;
  color: var(--gb-card-ink, var(--gb-cream));
  opacity: 0;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-color: color-mix(
    in srgb,
    var(--gb-card-ink, var(--gb-cream)) 40%,
    transparent
  );
  transition: opacity 220ms ease, color 260ms ease;
  pointer-events: none;
  z-index: 4;
}

.gb-card-slot-wrap.is-open.is-sig-locked .gb-card-sig-edit {
  opacity: 0.65;
  pointer-events: auto;
}

.gb-card-sig-edit:hover,
.gb-card-sig-edit:focus-visible {
  opacity: 0.95;
  outline: none;
}

/* Respect reduced-motion: trim the lift / lower transitions. */
@media (prefers-reduced-motion: reduce) {
  .gb-card,
  .gb-card-slot-wrap.is-open .gb-card,
  .gb-card-clear,
  .gb-card-sig-confirm,
  .gb-card-sig-edit {
    transition-duration: 220ms;
    transition-timing-function: ease-out;
  }
}

/* Mobile: shrink inner padding + field gap so the card still reads
   cleanly on narrow screens. */
@media (max-width: 480px) {
  .gb-card-slot-wrap {
    width: calc(100vw - 16px);
  }
  .gb-slot {
    width: 100%;
    max-width: calc(100% + 16px);
  }
  .gb-card {
    padding: 22px;
  }
  .gb-card-header {
    margin-bottom: 16px;
  }
  .gb-card-signature {
    margin-top: 16px;
  }
  .gb-card-fields {
    gap: 14px;
    margin-bottom: 8px;
  }
  .gb-card-field-message {
    margin-bottom: 8px;
  }
  .gb-card-title {
    font-size: 15px;
    max-width: 260px;
  }
  .gb-card-canvas {
    height: 74px;
  }
  .gb-card-name {
    font-size: 22px;
  }
  .gb-card-name-printed {
    font-size: 24px;
  }
  .gb-card-message {
    font-size: 20px;
  }
  .gb-card-message-printed {
    font-size: 22px;
  }
  .gb-card-clear {
    right: 58px;
    bottom: 95px;
  }
  .gb-card-sig-confirm {
    right: 22px;
    bottom: 89px;
  }
  .gb-card-sig-edit {
    right: 22px;
    bottom: 95px;
  }
}

/* ================================================================
   GuestbookCard — Color picker (Arc-inspired)
   ================================================================
   Sits below the card inside .gb-card-slot-wrap. Invisible in the
   peek state; fades in + slides up when the card opens. Two modes:
   a horizontally scrollable preset swatch row (default) and a
   hand-drawn HSL wheel + lightness slider ("custom"). */

.gb-card-colors {
  position: absolute;
  bottom: 18px;
  left: 50%;
  transform: translate(-50%, 16px);
  width: 100%;
  max-width: 440px;
  padding: 0 12px;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  opacity: 0;
  pointer-events: none;
  transition: opacity 320ms ease,
    transform 420ms cubic-bezier(0.22, 1, 0.36, 1);
  z-index: 2;
  font-family: 'Georgia', 'Times New Roman', serif;
}

.gb-card-slot-wrap.is-open .gb-card-colors {
  opacity: 1;
  transform: translate(-50%, 0);
  pointer-events: auto;
  transition: opacity 420ms ease 180ms,
    transform 520ms cubic-bezier(0.22, 1, 0.36, 1) 180ms;
}

.gb-card-colors-label {
  font-style: italic;
  font-size: 11px;
  color: #4a3f2e;
  opacity: 0.58;
  letter-spacing: 0.01em;
  margin-bottom: 2px;
}

/* Dark mode — use a light muted color so the label stays legible
   on dark backgrounds. Mirrors the portfolio's existing pattern:
   explicit [data-theme="dark"] wins, system dark falls back via
   :root:not([data-theme="light"]) inside prefers-color-scheme. */
[data-theme="dark"] .gb-card-colors-label {
  color: rgba(255, 255, 255, 0.5);
  opacity: 1;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .gb-card-colors-label {
    color: rgba(255, 255, 255, 0.5);
    opacity: 1;
  }
}

/* ---- Presets view (default) ---- */
.gb-card-colors-presets {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  max-width: 100%;
}

.gb-card-colors-arrow {
  flex-shrink: 0;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  border: 1px solid rgba(30, 20, 10, 0.14);
  background: rgba(255, 255, 255, 0.88);
  color: #3a2f1f;
  font-size: 15px;
  line-height: 1;
  padding: 0;
  cursor: pointer;
  display: grid;
  place-items: center;
  transition: background 160ms ease, transform 160ms ease,
    box-shadow 160ms ease;
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
}

.gb-card-colors-arrow:hover {
  background: #fff;
  transform: scale(1.06);
  box-shadow: 0 2px 6px rgba(30, 20, 10, 0.12);
}

.gb-card-colors-arrow:focus-visible {
  outline: 2px solid var(--gb-orange);
  outline-offset: 2px;
}

.gb-card-colors-scroll {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  gap: 10px;
  overflow-x: auto;
  padding: 8px 24px;
  scrollbar-width: none;
  scroll-behavior: smooth;
  align-items: center;
  justify-content: flex-start;
}
.gb-card-colors-scroll::-webkit-scrollbar {
  display: none;
}

/* ---- Individual swatch ---- */
.gb-card-swatch {
  flex-shrink: 0;
  width: 32px;
  height: 32px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: var(--swatch-color, #e8541a);
  cursor: pointer;
  box-shadow: 0 1px 3px rgba(30, 20, 10, 0.18),
    inset 0 0 0 1px rgba(255, 255, 255, 0.25);
  transition: transform 240ms cubic-bezier(0.34, 1.35, 0.64, 1),
    box-shadow 220ms ease;
  -webkit-tap-highlight-color: transparent;
}

.gb-card-swatch:hover {
  transform: scale(1.08);
}

.gb-card-swatch:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px #1a1a1a, 0 1px 3px rgba(30, 20, 10, 0.2);
}

.gb-card-swatch.is-selected {
  /* 38/32 = 1.1875 — matches the spec "slightly larger (38px)". */
  transform: scale(1.1875);
  box-shadow: 0 0 0 2px rgba(26, 26, 26, 0.9),
    0 2px 6px rgba(30, 20, 10, 0.22);
}

/* ---- Custom mode link + back link ---- */
.gb-card-colors-custom-link,
.gb-card-colors-back-link {
  background: none;
  border: none;
  padding: 4px 8px;
  font-family: 'Georgia', 'Times New Roman', serif;
  font-style: italic;
  font-size: 12px;
  color: #3a2f1f;
  opacity: 0.58;
  cursor: pointer;
  transition: opacity 160ms ease, transform 160ms ease;
}

/* Dark mode — light muted color so the "Custom →" / "← Back" links
   remain legible on dark backgrounds. Same detection pattern as the
   picker label above. */
[data-theme="dark"] .gb-card-colors-custom-link,
[data-theme="dark"] .gb-card-colors-back-link {
  color: rgba(255, 255, 255, 0.5);
  opacity: 1;
}
[data-theme="dark"] .gb-card-colors-custom-link:hover,
[data-theme="dark"] .gb-card-colors-back-link:hover {
  color: rgba(255, 255, 255, 0.85);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .gb-card-colors-custom-link,
  :root:not([data-theme="light"]) .gb-card-colors-back-link {
    color: rgba(255, 255, 255, 0.5);
    opacity: 1;
  }
  :root:not([data-theme="light"]) .gb-card-colors-custom-link:hover,
  :root:not([data-theme="light"]) .gb-card-colors-back-link:hover {
    color: rgba(255, 255, 255, 0.85);
  }
}

.gb-card-colors-custom-link:hover,
.gb-card-colors-back-link:hover {
  opacity: 1;
  transform: translateX(2px);
}

.gb-card-colors-back-link:hover {
  transform: translateX(-2px);
}

.gb-card-colors-custom-link:focus-visible,
.gb-card-colors-back-link:focus-visible {
  outline: 2px solid var(--gb-orange);
  outline-offset: 2px;
  border-radius: 3px;
}

/* ---- Mode switching ---- */
.gb-card-colors .gb-card-colors-wheel-view {
  display: none;
}
.gb-card-colors.is-custom .gb-card-colors-presets,
.gb-card-colors.is-custom .gb-card-colors-custom-link {
  display: none;
}
.gb-card-colors.is-custom .gb-card-colors-wheel-view {
  display: flex;
}

/* ---- Wheel view ---- */
.gb-card-colors-wheel-view {
  flex-direction: column;
  align-items: center;
  gap: 10px;
}

.gb-card-wheel-row {
  display: flex;
  gap: 14px;
  align-items: stretch;
}

.gb-card-wheel-wrap {
  position: relative;
  width: 140px;
  height: 140px;
}

.gb-card-wheel-canvas {
  display: block;
  width: 140px;
  height: 140px;
  cursor: crosshair;
  touch-action: none;
  border-radius: 50%;
  box-shadow: 0 2px 10px rgba(30, 20, 10, 0.18),
    inset 0 0 0 1px rgba(255, 255, 255, 0.3);
}

.gb-card-wheel-marker {
  position: absolute;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  border: 2px solid #fff;
  background: transparent;
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.55),
    0 2px 4px rgba(0, 0, 0, 0.24);
  pointer-events: none;
  transform: translate(-50%, -50%);
}

.gb-card-lightness {
  position: relative;
  width: 14px;
  height: 140px;
  cursor: ns-resize;
  touch-action: none;
  border-radius: 7px;
  box-shadow: 0 2px 8px rgba(30, 20, 10, 0.18),
    inset 0 0 0 1px rgba(255, 255, 255, 0.28);
  overflow: hidden;
}

.gb-card-lightness-canvas {
  display: block;
  width: 14px;
  height: 140px;
}

.gb-card-lightness-thumb {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 20px;
  height: 6px;
  border-radius: 3px;
  background: #fff;
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.55),
    0 2px 4px rgba(0, 0, 0, 0.24);
  pointer-events: none;
  transform: translate(-50%, -50%);
}

/* ================================================================
   Pattern picker
   ================================================================
   Lives inside .gb-card-colors as its last child so it flows
   visually below the color swatches (and below the wheel view in
   custom mode) and inherits the container's open-state fade-in.
   5 square tiles, each with a tiny live SVG preview and a faint
   text label underneath. */

.gb-card-patterns {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  margin-top: 8px;
  width: 100%;
}

.gb-card-patterns-label {
  font-style: italic;
  font-size: 11px;
  color: #4a3f2e;
  opacity: 0.58;
  letter-spacing: 0.01em;
}

[data-theme="dark"] .gb-card-patterns-label {
  color: rgba(255, 255, 255, 0.5);
  opacity: 1;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .gb-card-patterns-label {
    color: rgba(255, 255, 255, 0.5);
    opacity: 1;
  }
}

.gb-card-patterns-row {
  display: flex;
  align-items: flex-start;
  justify-content: center;
  gap: 12px;
}

.gb-card-pattern-cell {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  /* Fix each cell to the SELECTED tile size so neighbors don't
     shift when the selection scales up. */
  width: 44px;
}

.gb-card-pattern-tile {
  appearance: none;
  -webkit-appearance: none;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 2px solid transparent;
  border-radius: 6px;
  background: var(--gb-card-bg, #f5e6c8);
  color: var(--gb-card-pattern-color, #4a3f2e);
  cursor: pointer;
  display: block;
  overflow: hidden;
  position: relative;
  box-sizing: border-box;
  transition: box-shadow 220ms ease, border-color 220ms ease, background 260ms ease;
}

.gb-card-pattern-tile svg {
  display: block;
  width: 100%;
  height: 100%;
  /* Tile previews render the pattern at full strength so the motif
     is readable at 40px. */
  opacity: 0.72;
}

.gb-card-pattern-tile:hover {
  border-color: rgba(74, 63, 46, 0.45);
}

.gb-card-pattern-tile:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px rgba(74, 63, 46, 0.55);
}

.gb-card-pattern-tile.is-selected {
  border-color: rgba(74, 63, 46, 0.9);
  box-shadow: 0 2px 6px rgba(74, 63, 46, 0.18);
}

.gb-card-pattern-cell-label {
  font-family: 'Georgia', 'Times New Roman', serif;
  font-size: 9px;
  letter-spacing: 0.04em;
  color: #4a3f2e;
  opacity: 0.55;
  white-space: nowrap;
}

[data-theme="dark"] .gb-card-pattern-tile {
  border-color: rgba(255, 255, 255, 0.22);
}
[data-theme="dark"] .gb-card-pattern-tile:hover {
  border-color: rgba(255, 255, 255, 0.45);
}
[data-theme="dark"] .gb-card-pattern-tile:focus-visible {
  box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.5);
}
[data-theme="dark"] .gb-card-pattern-tile.is-selected {
  border-color: rgba(255, 255, 255, 0.85);
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.85),
    0 2px 6px rgba(0, 0, 0, 0.32);
}
[data-theme="dark"] .gb-card-pattern-cell-label {
  color: rgba(255, 255, 255, 0.6);
  opacity: 1;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .gb-card-pattern-tile {
    border-color: rgba(255, 255, 255, 0.22);
  }
  :root:not([data-theme="light"]) .gb-card-pattern-tile:hover {
    border-color: rgba(255, 255, 255, 0.45);
  }
  :root:not([data-theme="light"]) .gb-card-pattern-tile:focus-visible {
    box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.5);
  }
  :root:not([data-theme="light"]) .gb-card-pattern-tile.is-selected {
    border-color: rgba(255, 255, 255, 0.85);
    box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.85),
      0 2px 6px rgba(0, 0, 0, 0.32);
  }
  :root:not([data-theme="light"]) .gb-card-pattern-cell-label {
    color: rgba(255, 255, 255, 0.6);
    opacity: 1;
  }
}

@media (prefers-reduced-motion: reduce) {
  .gb-card-colors,
  .gb-card-swatch,
  .gb-card-colors-custom-link,
  .gb-card-colors-back-link,
  .gb-card-colors-arrow,
  .gb-card-pattern-tile,
  .gb-card-pattern-layer {
    transition-duration: 140ms;
  }
}

@media (max-width: 480px) {
  .gb-card-colors {
    max-width: 100%;
    padding: 0 8px;
  }
  .gb-card-wheel-wrap,
  .gb-card-wheel-canvas {
    width: 118px;
    height: 118px;
  }
  .gb-card-lightness,
  .gb-card-lightness-canvas {
    height: 118px;
  }
}

/* ================================================================
   GuestbookPanel
   ================================================================ */

/* Root pins a non-interactive surface over the viewport until opened.
   The panel + backdrop both live inside so focus handling and z-index
   are scoped together. */
.gb-panel-root {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 8500;
}

/* 20% black wash over the rest of the portfolio. */
.gb-panel-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0);
  transition: background 420ms ease-out;
  pointer-events: none;
}

.gb-panel-backdrop.is-open {
  background: rgba(0, 0, 0, 0.2);
  pointer-events: auto;
}

/* The panel surface. Its bottom edge sits 3px above the viewport
   bottom so it meets the slot (which lives at bottom: 0) halfway —
   visually the slot becomes the panel's bottom edge, tying them
   into one continuous physical object. */
.gb-panel {
  position: absolute;
  bottom: 3px;
  left: 50%;
  width: 520px;
  max-width: 100vw;
  height: calc(100vh - 40px);
  max-height: 100vh;
  background: var(--gb-panel);
  border-radius: 20px 20px 0 0;
  box-shadow:
    0 -24px 60px rgba(30, 20, 10, 0.22),
    0 -10px 24px rgba(30, 20, 10, 0.14);
  display: flex;
  flex-direction: column;
  transform: translate(-50%, calc(100% + 20px));
  transition: transform 460ms cubic-bezier(0.22, 1, 0.36, 1);
  pointer-events: auto;
  overflow: hidden;
  z-index: 8600;
  /* The slot wrap sits at z-index 9000 (outside this root), so the
     slot will render on top of the panel's bottom edge. */
}

.gb-panel.is-open {
  transform: translate(-50%, 0);
}

.gb-panel:focus {
  outline: none;
}

.gb-panel-header {
  position: relative;
  flex-shrink: 0;
  height: 60px;
}

.gb-panel-close {
  position: absolute;
  top: 12px;
  right: 20px;
  background: none;
  border: none;
  font-family: 'Georgia', 'Times New Roman', serif;
  font-size: 34px;
  line-height: 1;
  color: #1a1a1a;
  opacity: 0.55;
  cursor: pointer;
  padding: 4px 10px;
  transition: opacity 180ms ease, transform 180ms ease;
  z-index: 10;
  font-weight: 300;
}

.gb-panel-close:hover {
  opacity: 1;
  transform: scale(1.08);
}

.gb-panel-close:focus-visible {
  opacity: 1;
  outline: 2px solid #1a1a1a;
  outline-offset: 2px;
  border-radius: 2px;
}

.gb-panel-scroll {
  flex: 1 1 auto;
  overflow-y: auto;
  overflow-x: hidden;
  padding: 0 24px 16px;
  position: relative;
  scrollbar-width: thin;
  scrollbar-color: rgba(0, 0, 0, 0.12) transparent;
}

.gb-panel-scroll::-webkit-scrollbar {
  width: 6px;
}
.gb-panel-scroll::-webkit-scrollbar-thumb {
  background: rgba(0, 0, 0, 0.12);
  border-radius: 3px;
}

/* Bottom section of the panel: hosts the form and reserves cushion
   below it so the form content clears the persistent mail slot. */
.gb-panel-form-wrap {
  flex-shrink: 0;
  padding: 0 24px 56px;
  border-top: 1px solid rgba(0, 0, 0, 0.05);
  background: var(--gb-panel);
}

@media (max-width: 560px) {
  .gb-panel {
    width: 100vw;
    max-width: 100vw;
    height: 100vh;
    max-height: 100vh;
    border-radius: 0;
    bottom: 0;
    transform: translate(-50%, 100%);
  }
  .gb-panel.is-open {
    transform: translate(-50%, 0);
  }
}

@media (prefers-reduced-motion: reduce) {
  .gb-panel,
  .gb-panel-backdrop {
    transition-duration: 220ms;
  }
}

/* ================================================================
   GuestbookEntries
   ================================================================ */

/* Scatter canvas. Entries are absolutely positioned inside; container
   height is set inline by JS based on entry count / column layout. */
.gb-entries {
  position: relative;
  width: 100%;
  min-height: 240px;
}

/* Each entry is a small handwritten note card, slightly rotated.
   Background + ink come from the entry's stored cardColor (set via
   custom properties by GuestbookEntries). Entries without a color
   fall back to cream + dark ink for parity with the old default. */
.gb-entry {
  position: absolute;
  width: 160px;
  padding: 14px 16px 12px;
  background: var(--gb-entry-bg, var(--gb-cream));
  border-radius: 3px;
  font-family: 'Caveat', 'Bradley Hand', 'Segoe Print', cursive;
  color: var(--gb-entry-ink, #2a2418);
  transform-origin: center center;
  transform: rotate(var(--gb-entry-rot, 0deg));
  box-shadow:
    0 1px 2px rgba(50, 40, 20, 0.08),
    0 4px 12px rgba(50, 40, 20, 0.08),
    0 14px 28px rgba(50, 40, 20, 0.05);
  transition:
    transform 560ms cubic-bezier(0.22, 1, 0.36, 1),
    opacity 440ms ease-out;
  will-change: transform, opacity;
  user-select: none;
}

/* Landing state — used on mount + when a new entry is added.
   Combines the seeded rotation with a scale(0.9) so the card
   appears to drop in place and settle. */
.gb-entry.is-landing {
  transform: rotate(var(--gb-entry-rot, 0deg)) scale(0.9);
  opacity: 0;
}

.gb-entry-stamp {
  font-size: 52px;
  line-height: 1;
  text-align: center;
  margin-bottom: 6px;
  -webkit-user-select: none;
  user-select: none;
}

.gb-entry-draw {
  display: block;
  width: 100%;
  height: 88px;
  object-fit: contain;
  background: #ffffff;
  border-radius: 2px;
  margin-bottom: 10px;
  border: 1px solid rgba(0, 0, 0, 0.05);
  image-rendering: auto;
}

.gb-entry-name {
  font-size: 24px;
  font-weight: 600;
  line-height: 1.1;
  text-align: center;
  letter-spacing: 0.005em;
}

.gb-entry-date {
  font-size: 14px;
  font-weight: 400;
  text-align: center;
  color: var(--gb-entry-ink, #978a73);
  margin-top: 3px;
  font-style: italic;
  opacity: 0.55;
  letter-spacing: 0.02em;
}

@media (prefers-reduced-motion: reduce) {
  .gb-entry {
    transition-duration: 220ms;
  }
}

/* ================================================================
   GuestbookForm
   ================================================================ */

.gb-form {
  padding: 18px 0 4px;
  font-family: 'Georgia', 'Times New Roman', serif;
}

/* ---- Name field ---- */
.gb-form-name-wrap {
  margin-bottom: 16px;
}

.gb-form-name {
  width: 100%;
  box-sizing: border-box;
  padding: 6px 2px 8px;
  border: none;
  border-bottom: 1px solid rgba(26, 26, 26, 0.2);
  background: transparent;
  font-family: 'Caveat', 'Bradley Hand', cursive;
  font-size: 24px;
  line-height: 1.1;
  color: #1a1a1a;
  outline: none;
  transition: border-color 180ms ease;
}

.gb-form-name::placeholder {
  color: rgba(26, 26, 26, 0.35);
  font-style: italic;
}

.gb-form-name:focus {
  border-bottom-color: rgba(26, 26, 26, 0.45);
}

/* ---- Tabs ---- */
.gb-form-tabs {
  display: flex;
  gap: 20px;
  margin-bottom: 12px;
  align-items: flex-end;
}

.gb-form-tab {
  background: none;
  border: none;
  padding: 4px 2px 8px;
  font-family: 'Georgia', 'Times New Roman', serif;
  font-size: 13px;
  letter-spacing: 0.04em;
  color: #1a1a1a;
  cursor: pointer;
  opacity: 0.48;
  position: relative;
  transition: opacity 180ms ease;
}

.gb-form-tab:hover {
  opacity: 0.8;
}

.gb-form-tab.is-active {
  opacity: 1;
}

.gb-form-tab.is-active::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 2px;
  background: var(--gb-orange);
  border-radius: 1px;
  animation: gb-form-tab-in 240ms cubic-bezier(0.22, 1, 0.36, 1);
}

.gb-form-tab:focus-visible {
  opacity: 1;
  outline: none;
}
.gb-form-tab:focus-visible::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 2px;
  background: var(--gb-orange);
  border-radius: 1px;
}

@keyframes gb-form-tab-in {
  from { transform: scaleX(0.3); opacity: 0; }
  to   { transform: scaleX(1);   opacity: 1; }
}

/* ---- Tab panels ---- */
.gb-form-panels {
  position: relative;
  min-height: 150px;
}

.gb-form-panel {
  display: none;
}

.gb-form-panel.is-active {
  display: block;
}

/* ---- Sign: canvas ---- */
.gb-form-canvas {
  display: block;
  width: 100%;
  height: 120px;
  background: rgba(255, 255, 255, 0.55);
  border: 1px solid rgba(26, 26, 26, 0.1);
  border-radius: 4px;
  cursor: crosshair;
  touch-action: none;
  -webkit-user-select: none;
  user-select: none;
}

.gb-form-clear {
  display: block;
  margin-left: auto;
  margin-top: 6px;
  background: none;
  border: none;
  padding: 2px 2px 0;
  font-family: 'Georgia', 'Times New Roman', serif;
  font-size: 12px;
  color: #1a1a1a;
  opacity: 0.5;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-color: rgba(26, 26, 26, 0.3);
  transition: opacity 150ms ease;
}

.gb-form-clear:hover,
.gb-form-clear:focus-visible {
  opacity: 1;
  outline: none;
}

/* ---- Stamp: 6 emoji grid ---- */
.gb-form-stamps {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  justify-items: center;
  align-items: center;
  padding: 16px 0 10px;
  min-height: 120px;
  box-sizing: border-box;
}

/* Each stamp is a circular hit area. The selected highlight is a
   soft orange circle drawn via ::before behind the emoji. */
.gb-form-stamp {
  position: relative;
  width: 48px;
  height: 48px;
  padding: 0;
  border: none;
  background: none;
  cursor: pointer;
  display: grid;
  place-items: center;
  border-radius: 50%;
  isolation: isolate;
  transition: transform 180ms cubic-bezier(0.22, 1, 0.36, 1);
}

.gb-form-stamp::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: rgba(232, 84, 26, 0.15);
  transform: scale(0);
  transition: transform 260ms cubic-bezier(0.34, 1.56, 0.64, 1);
  z-index: -1;
}

.gb-form-stamp:hover {
  transform: scale(1.08);
}

.gb-form-stamp.is-selected::before {
  transform: scale(1);
}

.gb-form-stamp:focus-visible {
  outline: none;
}
.gb-form-stamp:focus-visible::before {
  transform: scale(1);
  background: rgba(232, 84, 26, 0.22);
}

.gb-form-stamp-emoji {
  font-size: 26px;
  line-height: 1;
  display: block;
  -webkit-user-select: none;
  user-select: none;
  /* Keep the emoji from being pushed by ::before's scaling */
  position: relative;
  z-index: 1;
}

@media (prefers-reduced-motion: reduce) {
  .gb-form-tab.is-active::after,
  .gb-form-stamp,
  .gb-form-stamp::before {
    transition-duration: 120ms;
    animation-duration: 120ms;
  }
}

/* ---- Dismiss animation (tap-outside / Escape close) ----
   JS animates translateY on .gb-dismiss-group so card + pickers
   slide down together as one unit. Pickers use display:none in
   closed state — JS manages show/hide on open/close. */

.gb-card-slot-wrap.is-dismissing .gb-card-colors {
  opacity: 1 !important;
  pointer-events: none;
}

/* ================================================================
   Send flow — strict ready sequence, drag pickup, drop animation,
   and the "Sent." payoff. See GuestbookCard.js for the timing.
   ================================================================ */

/* ---- Step 1: picker fades out ---- */
.gb-card-slot-wrap.is-picker-out .gb-card-colors {
  opacity: 0;
  pointer-events: none;
  transition: opacity 250ms ease-out;
}

/* ---- Step 2: card breath pulse ----
   A one-shot scale pulse on top of the open-state translate. The
   keyframe hardcodes translateY(-210px) — `maybeStartReadyFlow`
   guarantees the card is not in custom-color mode by the time this
   runs.

   Fill mode is deliberately `none` (the default) so that once the
   animation completes, the element returns to its normal cascade —
   otherwise the 100% keyframe would stay pinned and outrank any
   inline `transform` we set during a drag. */
@keyframes gb-card-breath-pulse {
  0%   { transform: translateY(-210px) scale(1); }
  50%  { transform: translateY(-210px) scale(1.02); }
  100% { transform: translateY(-210px) scale(1); }
}

.gb-card-slot-wrap.is-pulsed .gb-card {
  animation: gb-card-breath-pulse 300ms ease-out;
}

/* ---- Step 3: "Drop to send" scroll-hint + ready cursor ---- */
.gb-card-slot-wrap.is-ready .gb-card {
  cursor: grab;
}
.gb-card-slot-wrap.is-ready .gb-card:active {
  cursor: grabbing;
}

/* Minimal scroll-hint indicator below the card. No background, no
   pill, no border — just a small muted label and a thin bouncing
   chevron. Matches the "scroll to explore" style elsewhere on the
   portfolio. */
.gb-send-hint {
  position: absolute;
  left: 50%;
  bottom: 64px;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  font-family: 'Georgia', 'Times New Roman', serif;
  font-style: italic;
  font-size: 11px;
  letter-spacing: 0.04em;
  color: rgba(26, 20, 10, 0.42);
  opacity: 0;
  pointer-events: none;
  z-index: 11;
  transition: opacity 200ms ease-out;
  white-space: nowrap;
  user-select: none;
}

.gb-send-hint-label {
  display: block;
  line-height: 1;
}

.gb-send-hint-arrow {
  display: block;
  width: 14px;
  height: 10px;
  color: rgba(26, 20, 10, 0.4);
  animation: gb-send-hint-bounce 1.8s ease-in-out infinite;
}

@keyframes gb-send-hint-bounce {
  0%, 100% { transform: translateY(0);   opacity: 0.45; }
  50%      { transform: translateY(4px); opacity: 0.9; }
}

.gb-card-slot-wrap.is-ready .gb-send-hint {
  opacity: 1;
}

/* Hide the hint instantly the moment a drag starts or the send
   animation begins. */
.gb-card-slot-wrap.is-dragging .gb-send-hint,
.gb-card-slot-wrap.is-sending .gb-send-hint,
.gb-card-slot-wrap.is-sent .gb-send-hint,
.gb-card-slot-wrap.is-post-actions .gb-send-hint {
  opacity: 0;
  transition: opacity 80ms ease-out;
}

.gb-card-slot-wrap.is-dragging .gb-send-hint .gb-send-hint-arrow,
.gb-card-slot-wrap.is-sending .gb-send-hint .gb-send-hint-arrow {
  animation: none;
}

/* ---- Drag state ---- */
.gb-card-slot-wrap.is-dragging .gb-card {
  cursor: grabbing;
  /* Inline transforms on .gb-card own positioning during a drag —
     kill the class-level transition so it tracks the pointer with
     no smoothing. */
  transition: none !important;
}

/* Slot widens and its orange glow intensifies while dragging. */
.gb-card-slot-wrap.is-dragging .gb-slot {
  width: 500px;
  max-width: calc(100% + 40px);
  box-shadow:
    inset 0 1.5px 3px rgba(0, 0, 0, 0.95),
    inset 0 -0.5px 0 rgba(255, 255, 255, 0.08),
    0 0 36px rgba(232, 84, 26, 0.55),
    0 0 10px rgba(232, 84, 26, 0.35);
  transition: width 240ms cubic-bezier(0.22, 1, 0.36, 1),
    max-width 240ms cubic-bezier(0.22, 1, 0.36, 1),
    box-shadow 260ms ease-out;
}

/* Fall-back glow transition when the drag ends so the slot settles
   back to its idle width gracefully. */
.gb-slot {
  transition: width 220ms ease, max-width 220ms ease,
    box-shadow 260ms ease;
}

/* "Right here" micro-copy above the slot — only visible during drag.
   Wrapped in a dark pill so the cream text stays legible regardless
   of the host page background (the hint floats above the slot, not
   over it, so it can't rely on the slot's dark surface for contrast). */
.gb-slot-hint {
  position: absolute;
  left: 50%;
  bottom: 22px;
  transform: translate(-50%, 6px);
  padding: 4px 10px;
  border-radius: 999px;
  background: rgba(26, 26, 26, 0.88);
  font-family: 'Georgia', 'Times New Roman', serif;
  font-style: italic;
  font-size: 11px;
  letter-spacing: 0.02em;
  line-height: 1.2;
  color: #f5e6c8;
  opacity: 0;
  pointer-events: none;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
  transition: opacity 200ms ease, transform 260ms ease;
  z-index: 11;
  white-space: nowrap;
}

.gb-card-slot-wrap.is-dragging .gb-slot-hint {
  opacity: 0.85;
  transform: translate(-50%, 0);
}

/* ---- Send animation support ----
   The card's transform is driven inline from JS during .is-sending.
   We just need to silence any lingering class-level transitions and
   lock z-index so it slides behind the slot, not over it. */
.gb-card-slot-wrap.is-sending .gb-card {
  cursor: default;
  z-index: 1;
}

.gb-card-slot-wrap.is-sending .gb-card-colors {
  opacity: 0;
  pointer-events: none;
}

/* ---- Slot orange pulse (one-shot on drop) ---- */
@keyframes gb-slot-pulse {
  0% {
    box-shadow:
      inset 0 1.5px 3px rgba(0, 0, 0, 0.95),
      inset 0 -0.5px 0 rgba(255, 255, 255, 0.06),
      0 0 0 rgba(232, 84, 26, 0);
  }
  35% {
    box-shadow:
      inset 0 1.5px 3px rgba(0, 0, 0, 0.95),
      inset 0 -0.5px 0 rgba(255, 255, 255, 0.06),
      0 0 46px rgba(232, 84, 26, 0.85),
      0 0 14px rgba(232, 84, 26, 0.55);
  }
  100% {
    box-shadow:
      inset 0 1.5px 3px rgba(0, 0, 0, 0.9),
      inset 0 -0.5px 0 rgba(255, 255, 255, 0.06),
      0 1px 2px rgba(0, 0, 0, 0.15);
  }
}

.gb-card-slot-wrap.is-slot-pulse .gb-slot {
  animation: gb-slot-pulse 600ms ease-out both;
}

/* ---- "Sent. Thanks for stopping by." ---- */
.gb-sent-message {
  position: absolute;
  left: 50%;
  bottom: 54px;
  transform: translateX(-50%);
  font-family: 'Georgia', 'Times New Roman', serif;
  font-style: italic;
  font-size: 13px;
  letter-spacing: 0.01em;
  color: rgba(26, 20, 10, 0.55);
  opacity: 0;
  pointer-events: none;
  white-space: nowrap;
  transition: opacity 400ms ease;
  z-index: 11;
}

.gb-card-slot-wrap.is-sent .gb-sent-message {
  opacity: 1;
}

.gb-card-slot-wrap.is-sent.is-sent-out .gb-sent-message {
  opacity: 0;
}

/* ---- Post-send actions: Edit card · Download card ----
   Sits in the same space as the sent message. Minimal text-link
   aesthetic: same muted serif, no buttons, no borders, a thin
   middle-dot separator between them. Fades in 300ms after the
   sent message fades out. */
.gb-post-actions {
  position: absolute;
  left: 50%;
  bottom: 54px;
  transform: translateX(-50%);
  display: flex;
  align-items: baseline;
  gap: 14px;
  font-family: 'Georgia', 'Times New Roman', serif;
  font-style: italic;
  font-size: 12px;
  letter-spacing: 0.02em;
  opacity: 0;
  pointer-events: none;
  white-space: nowrap;
  transition: opacity 300ms ease;
  z-index: 11;
}

.gb-card-slot-wrap.is-post-actions .gb-post-actions {
  opacity: 1;
  pointer-events: auto;
}

.gb-post-action {
  background: none;
  border: none;
  padding: 2px 2px;
  margin: 0;
  font: inherit;
  font-style: italic;
  color: rgba(26, 20, 10, 0.55);
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-color: rgba(26, 20, 10, 0.25);
  transition: color 180ms ease, text-decoration-color 180ms ease;
}

.gb-post-action:hover,
.gb-post-action:focus-visible {
  color: rgba(26, 20, 10, 0.9);
  text-decoration-color: rgba(26, 20, 10, 0.55);
  outline: none;
}

.gb-post-sep {
  color: rgba(26, 20, 10, 0.3);
  font-size: 14px;
  line-height: 1;
  user-select: none;
}

/* ================================================================
   Dark mode — page-chrome elements
   ================================================================
   The slot, "Right here" drag hint, sent message, and post-send
   action links all live OUTSIDE the card on the page background, so
   they need their own contrast treatment when the host page is dark.
   The card face itself is unaffected (its ink is driven by the
   selected card color). Same detection pattern as the picker label
   overrides above: explicit [data-theme="dark"] wins, system dark
   falls back via :root:not([data-theme="light"]). */

/* Slot — in light mode the slot reads as a dark slit carved into a
   light page. On a dark page that trick fails (#1a1a1a ≈ #111), so
   we lift the fill to a mid-gray that contrasts with the page and
   add a bright 1px rim so the slot's edges read crisply. Same
   treatment applied to the dragging state so the rim persists while
   the slot widens and picks up its orange glow. */
[data-theme="dark"] .gb-slot {
  background: #2a2a2a;
  box-shadow:
    inset 0 1.5px 3px rgba(0, 0, 0, 0.95),
    inset 0 -0.5px 0 rgba(255, 255, 255, 0.12),
    0 0 0 1px rgba(255, 255, 255, 0.4),
    0 2px 8px rgba(0, 0, 0, 0.6);
}

[data-theme="dark"] .gb-card-slot-wrap.is-dragging .gb-slot {
  box-shadow:
    inset 0 1.5px 3px rgba(0, 0, 0, 0.95),
    inset 0 -0.5px 0 rgba(255, 255, 255, 0.14),
    0 0 0 1px rgba(255, 255, 255, 0.5),
    0 0 36px rgba(232, 84, 26, 0.6),
    0 0 10px rgba(232, 84, 26, 0.4);
}

/* "Drop to send" scroll-hint — dark-ink cream replacement so the
   label and bouncing chevron read against a dark page. */
[data-theme="dark"] .gb-send-hint {
  color: rgba(245, 230, 200, 0.6);
}

[data-theme="dark"] .gb-send-hint .gb-send-hint-arrow {
  color: rgba(245, 230, 200, 0.55);
}

/* "Right here" drag hint — the light-mode dark pill vanishes on a
   dark page. Invert to a cream pill with dark text so it reads as a
   bright floating label regardless of page theme. */
[data-theme="dark"] .gb-slot-hint {
  background: rgba(245, 230, 200, 0.92);
  color: #1a1a1a;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.45);
}

/* Sent message, post-send action links, and the · separator all
   use dark ink tones in light mode. Swap to warm cream tones at the
   same opacities so the hierarchy is preserved on dark pages. */
[data-theme="dark"] .gb-sent-message {
  color: rgba(245, 230, 200, 0.7);
}

[data-theme="dark"] .gb-post-action {
  color: rgba(245, 230, 200, 0.7);
  text-decoration-color: rgba(245, 230, 200, 0.3);
}

[data-theme="dark"] .gb-post-action:hover,
[data-theme="dark"] .gb-post-action:focus-visible {
  color: rgba(245, 230, 200, 0.95);
  text-decoration-color: rgba(245, 230, 200, 0.6);
}

[data-theme="dark"] .gb-post-sep {
  color: rgba(245, 230, 200, 0.35);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .gb-slot {
    background: #2a2a2a;
    box-shadow:
      inset 0 1.5px 3px rgba(0, 0, 0, 0.95),
      inset 0 -0.5px 0 rgba(255, 255, 255, 0.12),
      0 0 0 1px rgba(255, 255, 255, 0.4),
      0 2px 8px rgba(0, 0, 0, 0.6);
  }
  :root:not([data-theme="light"]) .gb-card-slot-wrap.is-dragging .gb-slot {
    box-shadow:
      inset 0 1.5px 3px rgba(0, 0, 0, 0.95),
      inset 0 -0.5px 0 rgba(255, 255, 255, 0.14),
      0 0 0 1px rgba(255, 255, 255, 0.5),
      0 0 36px rgba(232, 84, 26, 0.6),
      0 0 10px rgba(232, 84, 26, 0.4);
  }
  :root:not([data-theme="light"]) .gb-send-hint {
    color: rgba(245, 230, 200, 0.6);
  }
  :root:not([data-theme="light"]) .gb-send-hint .gb-send-hint-arrow {
    color: rgba(245, 230, 200, 0.55);
  }
  :root:not([data-theme="light"]) .gb-slot-hint {
    background: rgba(245, 230, 200, 0.92);
    color: #1a1a1a;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.45);
  }
  :root:not([data-theme="light"]) .gb-sent-message {
    color: rgba(245, 230, 200, 0.7);
  }
  :root:not([data-theme="light"]) .gb-post-action {
    color: rgba(245, 230, 200, 0.7);
    text-decoration-color: rgba(245, 230, 200, 0.3);
  }
  :root:not([data-theme="light"]) .gb-post-action:hover,
  :root:not([data-theme="light"]) .gb-post-action:focus-visible {
    color: rgba(245, 230, 200, 0.95);
    text-decoration-color: rgba(245, 230, 200, 0.6);
  }
  :root:not([data-theme="light"]) .gb-post-sep {
    color: rgba(245, 230, 200, 0.35);
  }
}

/* ---- Reduced motion: collapse every non-essential animation ---- */
@media (prefers-reduced-motion: reduce) {
  .gb-card-slot-wrap.is-pulsed .gb-card,
  .gb-send-hint-arrow,
  .gb-card-slot-wrap.is-slot-pulse .gb-slot {
    animation: none;
  }
  .gb-send-hint,
  .gb-sent-message,
  .gb-post-actions,
  .gb-card-colors {
    transition-duration: 150ms;
  }
}

/* ---- Mobile tuning for the send flow ---- */
@media (max-width: 480px) {
  .gb-send-hint {
    bottom: 58px;
    font-size: 10.5px;
  }
  .gb-sent-message,
  .gb-post-actions {
    bottom: 48px;
    font-size: 12px;
  }
  .gb-card-slot-wrap.is-dragging .gb-slot {
    width: 100%;
    max-width: calc(100% + 24px);
  }
  /* Mobile open-state lift is still -210px (see .gb-card-slot-wrap
     .is-open .gb-card above), so the pulse keyframe works as-is. */
}

@media (max-width: 560px) {
  .gb-form-stamp {
    width: 52px;
    height: 52px;
  }
  .gb-form-stamp-emoji {
    font-size: 28px;
  }
}

/* ---- Japanese font size overrides ---- */
html[data-lang="ja"] .gb-card-name,
html[data-lang="ja"] .gb-card-name::placeholder {
  font-size: 16px;
}
html[data-lang="ja"] .gb-card-message,
html[data-lang="ja"] .gb-card-message::placeholder {
  font-size: 16px;
}
