@font-face {
  font-family: 'Clear Sans';
  src: url('../assets/fonts/ClearSans-Regular.ttf') format('truetype');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Clear Sans';
  src: url('../assets/fonts/ClearSans-Bold.ttf') format('truetype');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Clear Sans';
  src: url('../assets/fonts/ClearSans-Italic.ttf') format('truetype');
  font-weight: 400;
  font-style: italic;
  font-display: swap;
}

@font-face {
  font-family: 'Clear Sans';
  src: url('../assets/fonts/ClearSans-BoldItalic.ttf') format('truetype');
  font-weight: 700;
  font-style: italic;
  font-display: swap;
}

:root {
  --red: #c9021d;
  --red-mid: #a80418;
  --ink: #1c1a17;
}

* { box-sizing: border-box; }

button, textarea, input, select {
  font-family: inherit;
}

html, body {
  height: 100%;
  margin: 0;
}

/* Deliberately NOT setting any overflow on html: doing so turns html into
   its own explicit scroll container instead of the normal browser default
   (body's overflow propagating to the viewport) — which on mobile WebKit
   can make position:fixed children (like .background) scroll away with
   the page instead of staying pinned, since "fixed to the viewport" and
   "fixed relative to html's own scroll box" stop being the same thing.
   Horizontal overflow (the reason this was here before) is instead
   prevented at the source: .tabs/.brand/.tab-btn.key-btn size themselves
   off --vw (a clientWidth-based, scrollbar-corrected unit set in app.js)
   rather than raw vw, which is what actually caused the overflow. */

body {
  min-height: 100vh;
  font-family: 'Clear Sans', 'Helvetica Neue', Arial, sans-serif;
  color: var(--red);
}

.background {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  /* Deliberately NOT inset:0/bottom:0: that ties this element's height to
     the *dynamic* visual viewport, which mobile browsers continuously
     resize as the address bar animates away during a scroll gesture —
     with background-size:cover, that live resize reads as the image
     visibly "zooming" while scrolling. 100lvh (large viewport height) is
     the size with browser UI fully retracted, and — unlike 100dvh — it
     stays constant through that whole animation instead of tracking it,
     so the image never resizes at all. The plain 100vh above it is only
     a fallback for browsers without lvh support; modern ones use the
     line below it. */
  height: 100vh;
  height: 100lvh;
  z-index: -2;
  /* background-attachment: fixed (no longer used below) is notoriously
     unreliable on mobile Safari — the image can still scroll with the
     page despite it, a completely separate issue from the element's own
     position. Since this whole element is already position:fixed and
     never scrolls internally, plain default attachment (scroll) achieves
     the same "pinned to the viewport" result without that bug. */
  background:
    linear-gradient(to bottom,
      rgba(8, 12, 24, 0.35) 0%,
      rgba(8, 12, 24, 0.05) 35%,
      rgba(8, 12, 24, 0.25) 100%),
    url('../assets/background.jpg') center bottom / cover no-repeat;
}

.stage {
  position: relative;
  z-index: 1;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 28px 20px 60px;
}

/* ---------- Brand (top-left) ---------- */
.brand {
  position: fixed;
  top: 24px;
  left: 20px;
  z-index: 5;
  /* Safety cap so the tagline (long, unconstrained text) can't stretch this
     box wide before JS measures the logo and pins the tagline's width to
     it. Without this, shrink-to-fit sizing would size .brand off the
     tagline's own preferred width instead of the logo's. Scales down on
     narrow viewports (together with .tabs below) so the two fixed,
     opposite-corner boxes never collide in the middle.
     Uses --vw (a scrollbar-corrected 1% of viewport width, set from
     app.js) instead of raw vw — see the html { scrollbar-gutter } comment
     above for why. */
  max-width: clamp(150px, calc(45 * var(--vw, 1vw)), 340px);
}

.logo {
  display: inline-block;
  font-size: 1.8rem;
  font-weight: 400;
  text-transform: uppercase;
  color: #fff;
  margin: 0;
  text-shadow: 0 2px 6px rgba(0,0,0,0.25);
}

.tagline {
  /* width is set from JS to match the rendered width of .logo */
  margin: 10px 0 0;
  height: fit-content;
  font-size: 13px;
  line-height: 1.5;
  text-align: justify;
  color: #fff;
  text-shadow: 0 1px 4px rgba(0,0,0,0.5);
}

/* Mobile-only "info" toggle + close button for .tagline, and the
   mobile-only refresh button that sits in the tabs row (desktop keeps its
   own #refresh-button beside the envelope instead) — see the mobile
   layout section near the bottom of this file. All hidden on desktop. */
.tagline-toggle,
.tagline-close,
.refresh-btn-mobile {
  display: none;
}

/* ---------- Tabs (top-right) ---------- */
.tabs {
  position: fixed;
  top: 24px;
  right: 20px;
  z-index: 5;
  display: flex;
  gap: 10px;
  /* guarantees the box's own right edge can never push past the viewport's
     right edge (and cause horizontal scroll) no matter how the key images
     are sized */
  max-width: calc(100 * var(--vw, 1vw) - 40px);
}

/* ---------- Heart toggle buttons (assets/heart-1-*.png, heart-2-*.png) ----------
   Each button swaps between its own red (active) and pink (inactive) image
   in app.js's setMode() — exactly one heart is red at a time. Sized with
   clamp() so it shrinks on narrow viewports instead of colliding with
   .brand on the opposite corner. */
.tab-btn.key-btn {
  border: none;
  background: none;
  padding: 0;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  height: clamp(60px, calc(16 * var(--vw, 1vw)), 112px);
  transition: transform 0.12s ease;
}

.tab-btn.key-btn img {
  display: block;
  height: 100%;
  width: auto;
}

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

.tab-btn.key-btn:active {
  transform: translateY(1px);
  transition: transform 0.05s ease;
}

/* ---------- Envelope ---------- */
.envelope-wrap {
  width: 100%;
  display: flex;
  justify-content: center;
  /* .stage centers this vertically as a baseline; app.js's centerEnvelope()
     overrides margin-top with an exact value so the envelope+letter
     composition (which can extend above the envelope when the letter
     slides out further) stays centered as a whole, not just the envelope. */
  margin: 0 0 30px;
}

.envelope {
  position: relative;
  width: min(86vw, 420px);
  /* envelope-back.png / envelope-front.png / paper.png are all the same
     square canvas (1563x1563) — keep this 1/1 as long as that's true. */
  aspect-ratio: 1 / 1;
  /* Two stacked shadows so the whole envelope+letter reads as floating
     above the photo instead of pasted flat onto it: a tight, darker one
     for contact definition right at the edges, and a larger, softer one
     for ambient depth. drop-shadow (not box-shadow) so it follows the
     envelope/paper's actual silhouette, not just their bounding boxes. */
  filter:
    drop-shadow(0 6px 10px rgba(0, 0, 0, 0.5))
    drop-shadow(0 32px 48px rgba(0, 0, 0, 0.45));
}

.flap-back,
.pocket-front {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  pointer-events: none;
}

.flap-back {
  z-index: 1;
}

/* Sits to the right of the envelope, vertically centered on it — left:100%
   is relative to #envelope (its positioned ancestor), so this tracks the
   envelope's own responsive width automatically. Used by the refresh
   button in read mode (write-controls has its own row below the envelope
   instead — see .write-controls further down). */
.envelope-side-control {
  position: absolute;
  left: 100%;
  top: 50%;
  transform: translateY(-50%);
  margin-left: 28px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  white-space: nowrap;
}

.refresh-btn {
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

body.mode-write .refresh-btn {
  display: none;
}

/* A white circular "chip" behind the icon, matching the visual weight of
   the solid-colored heart buttons — a bare outline icon on the photo
   background read as much fainter than the hearts. box-sizing:border-box
   (global) keeps width/height as the total chip size, with padding
   carving out room for the background to show around the icon. */
.refresh-btn img {
  display: block;
  width: 64px;
  height: 64px;
  padding: 12px;
  background: rgba(255, 255, 255, 0.9);
  border-radius: 50%;
  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.35);
  transition: transform 0.3s ease;
}

.refresh-btn:hover img {
  transform: rotate(-35deg);
}

.refresh-btn:active img {
  transform: rotate(-110deg) scale(0.9);
  transition: transform 0.15s ease;
}

.refresh-label {
  font-size: 13px;
  font-weight: 700;
  color: #fff;
  text-shadow: 0 1px 4px rgba(0, 0, 0, 0.5);
}

/* Its own row below the envelope (not the side-control spot the refresh
   button uses) — width:100% of #envelope (its positioned ancestor) makes
   the frame exactly as wide as the letter, with the char count and submit
   button pushed to its two ends. */
.write-controls {
  position: absolute;
  top: 100%;
  left: 0;
  width: 100%;
  margin-top: 28px;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 20px;
}

.write-controls[hidden] {
  display: none;
}

.pocket-front {
  z-index: 3;
}

/* Clips the paper's slide to exactly the envelope's box on the bottom/sides,
   but extends well above it so a letter that slides out further (see
   --reveal-shift below) is never clipped early. */
.paper-mask {
  position: absolute;
  inset: -220% 0 0 0;
  overflow: hidden;
  z-index: 2;
  pointer-events: none;
}

/* Same canvas as the envelope images, so it sits flush at rest — the
   registration the artwork was drawn to. The slide starts pushed down
   (mostly hidden behind envelope-front.png) and settles at translateY(0),
   or higher via --reveal-shift (set from JS) when the letter is longer
   than the always-visible window and needs to slide out further to be
   fully readable. */
.paper {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  aspect-ratio: 1 / 1;
  /* Without this, .paper-scroll's height (below) can ask for more room
     than fits in here — e.g. on a small mobile envelope with a near-max
     reveal shift — and since aspect-ratio is only a *preferred* size for
     an auto-height box, the overflowing child would otherwise stretch
     .paper itself taller than the envelope, which breaks the "paper is a
     fixed size that only ever moves via transform" assumption the margin
     clearance above the envelope (see .envelope-wrap) depends on. Any
     excess now falls back to .paper-scroll's own overflow-y: auto instead
     of growing .paper. */
  overflow: hidden;
  pointer-events: auto;
  background-image: url('../assets/letter/paper.png');
  background-size: 100% 100%;
  background-repeat: no-repeat;
  padding: 80px;
  --reveal-shift: 0px;
  transform: translateY(58%);
  opacity: 0;
  transition: transform 1.15s cubic-bezier(0.16, 0.85, 0.3, 1), opacity 0.7s ease-out;
}

.paper.is-revealed {
  transform: translateY(var(--reveal-shift));
  opacity: 1;
}

.paper.is-cycling {
  transform: translate(16px, 26px);
  opacity: 0;
  transition: transform 0.5s ease-in-out, opacity 0.4s ease-in-out;
}

/* The always-visible window before the fold covers the rest of the paper.
   Text taller than this slides the whole paper out further via JS (see
   --reveal-shift and MAX_REVEAL_SHIFT in app.js), which also grows this
   box's height to match. overflow-y is just a safety net for the rare
   letter long enough to exceed even the maximum slide-out. */
.paper-scroll {
  height: 50%;
  overflow-y: auto;
}

.paper-inner {
  min-height: 60px;
}

.letter-content {
  font-size: 14px;
  line-height: 1.5;
  color: var(--ink);
  white-space: pre-wrap;
  word-break: break-word;
}

.letter-textarea {
  display: none;
  width: 100%;
  min-height: 220px;
  border: none;
  outline: none;
  resize: none;
  background: transparent;
  font-size: 14px;
  line-height: 1.5;
  color: var(--ink);
}

.letter-textarea::placeholder {
  color: rgba(28, 26, 23, 0.4);
}

.letter-date {
  margin-top: 14px;
  font-size: 13px;
  font-style: italic;
  color: var(--red-mid);
  text-align: right;
}

/* ---------- Mode switching ---------- */
body.mode-write .letter-content { display: none; }
body.mode-write .letter-textarea { display: block; }
body.mode-write .letter-date { display: none; }

body.mode-read .letter-textarea { display: none; }

/* ---------- Controls ---------- */
.controls {
  width: 100%;
  max-width: 640px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
}

.pill-btn {
  font-size: 0.95rem;
  padding: 12px 28px;
  border-radius: 999px;
  border: none;
  background: var(--red);
  color: #fff;
  cursor: pointer;
  box-shadow: 0 6px 16px rgba(0,0,0,0.3);
  transition: transform 0.15s ease;
}

.pill-btn:hover { transform: translateY(-1px); }
.pill-btn:disabled { opacity: 0.6; cursor: default; transform: none; }

.pill-outline {
  background: transparent;
  border: 1.5px solid var(--red);
  box-shadow: none;
}

.char-count-row {
  font-size: 0.85rem;
  color: #fff;
  text-shadow: 0 1px 4px rgba(0,0,0,0.4);
}

.write-message {
  max-width: 480px;
  text-align: center;
  font-style: italic;
  font-size: 0.9rem;
  color: #fff;
  text-shadow: 0 1px 4px rgba(0,0,0,0.4);
  min-height: 1.2em;
}

.write-message .msg-box {
  padding: 12px 22px;
  border-radius: 12px;
  background: rgba(255,255,255,0.12);
  border: 1px solid rgba(255,255,255,0.3);
}

@media (max-width: 480px) {
  .logo { font-size: 1.5rem; }
}

/* ---------- Footer ---------- */
/* On desktop this wrapper doesn't generate a box at all, so its two
   children keep their own independent fixed-corner positioning exactly as
   before. It only becomes a real (flex) container on mobile — see the
   @media (max-width: 700px) block near the end of this file. */
.site-footer {
  display: contents;
}

.footer-credit {
  position: fixed;
  right: 20px;
  bottom: 16px;
  z-index: 5;
  font-size: 13px;
  color: #fff;
  text-shadow: 0 1px 4px rgba(0,0,0,0.5);
}

.footer-link {
  color: inherit;
  text-decoration: none;
}

.footer-link:hover {
  text-decoration: underline;
}

.imprint-wrap {
  position: fixed;
  left: 20px;
  bottom: 16px;
  z-index: 5;
  font-size: 13px;
  color: #fff;
  text-shadow: 0 1px 4px rgba(0,0,0,0.5);
}

.imprint-toggle {
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  color: inherit;
  text-shadow: inherit;
  cursor: pointer;
}

.imprint-toggle:hover {
  text-decoration: underline;
}

.imprint-panel {
  position: absolute;
  left: 0;
  bottom: 100%;
  margin-bottom: 10px;
  padding: 20px 28px 4px 0;
  width: max-content;
  max-width: 60vw;
  font-size: 0.8rem;
  line-height: 1.5;
  color: #fff;
  text-shadow: 0 1px 4px rgba(0,0,0,0.6);
}

.imprint-line {
  margin: 0 0 12px;
}

.imprint-close {
  position: absolute;
  top: 0;
  right: 8px;
  background: none;
  border: none;
  color: inherit;
  text-shadow: inherit;
  font-size: 1.2rem;
  line-height: 1;
  cursor: pointer;
  padding: 4px;
}

/* ---------- Mobile layout ----------
   Desktop pins .brand/.tabs to opposite top corners and centers the
   envelope independent of scroll (see app.js's pinEnvelopePosition). On
   narrow viewports there isn't room for that without things overlapping,
   so everything instead becomes normal in-flow content, stacked in the
   order it already appears in the HTML: brand, tabs, envelope, then the
   footer (imprint + credit) — which .stage's min-height:100vh pushes
   below one full screen's worth of scroll either way. */
@media (max-width: 700px) {
  .brand {
    position: static;
    max-width: none;
    padding: 24px 20px 0;
  }

  /* display:block (not inline-block) so it drops to its own line below
     the headline, instead of sitting next to it. */
  .tagline-toggle {
    display: block;
    margin-top: 8px;
    background: none;
    border: none;
    padding: 0;
    font-size: 14px;
    color: #fff;
    text-shadow: 0 1px 4px rgba(0, 0, 0, 0.5);
    text-decoration: none;
    cursor: pointer;
  }

  /* Animated open/close instead of a plain display:none/block toggle —
     display can't be transitioned, so this stays permanently display:block
     and animates max-height instead (0 = fully collapsed, since
     box-sizing:border-box + overflow:hidden means padding collapses to
     nothing with it too, no separate padding animation needed).
     Sequenced in two steps, matching "push down, then fade in": max-height
     runs first, and opacity's transition-delay (equal to max-height's
     duration) holds it at 0 until that finishes, so the fade-in only
     starts once .tabs/the envelope have already finished sliding down to
     make room. ease-in-out on both — no bounce/spring easing. */
  .tagline {
    position: relative;
    max-height: 0;
    overflow: hidden;
    opacity: 0;
    margin-top: 10px;
    /* 0 by default (closed); .is-open below sets 40px, the gap between
       the info box's bottom edge and .tabs's own top edge while open.
       Transitioned alongside max-height so the extra space opens up
       smoothly instead of snapping in at the end. */
    margin-bottom: 0;
    background: rgba(255, 255, 255, 0.85);
    padding: 12px 34px 12px 14px;
    border-radius: 10px;
    color: #000;
    text-shadow: none;
    transition: max-height 0.45s ease-in-out, margin-bottom 0.45s ease-in-out, opacity 0.3s ease-in-out 0.45s;
  }

  .tagline.is-open {
    max-height: 220px;
    margin-bottom: 40px;
    opacity: 1;
  }

  .tagline-close {
    display: block;
    position: absolute;
    top: 4px;
    right: 8px;
    background: none;
    border: none;
    color: #000;
    font-size: 1.2rem;
    line-height: 1;
    cursor: pointer;
    padding: 4px;
  }

  .tabs {
    position: static;
    width: 100%;
    max-width: none;
    justify-content: center;
    align-items: center;
    gap: 14px;
    margin: 0;
    padding: 16px 20px 0;
  }

  /* The envelope-side refresh button is replaced by #refresh-button-mobile
     below, which sits in the tabs row instead — see the HTML comment on
     that button. */
  #refresh-button {
    display: none;
  }

  .refresh-btn-mobile {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 4px;
  }

  /* Bigger than the old bare-icon size (44px) now that it has a
     background chip (see the shared .refresh-btn img rule) to fill out —
     matches the enlarged hearts below in visual weight. */
  .refresh-btn-mobile img {
    width: 60px;
    height: 60px;
    padding: 11px;
  }

  .refresh-btn-mobile:hover img,
  .refresh-btn-mobile:active img {
    transform: rotate(-35deg);
  }

  /* Hearts read as small at the old clamp() — bigger minimum and scaling
     factor here (mobile only; desktop keeps the smaller clamp above). */
  .tab-btn.key-btn {
    height: clamp(80px, calc(20 * var(--vw, 1vw)), 130px);
  }

  /* Same size as the letter text (.letter-content/.letter-textarea are
     14px) for the refresh label, char count, and submit button. */
  .refresh-label,
  .char-count-row,
  .pill-btn {
    font-size: 14px;
  }

  .stage {
    justify-content: flex-start;
  }

  /* This is just the pre-JS fallback (and a floor while JS is loading) —
     app.js's pinEnvelopeBelowButtons() measures the buttons row's actual
     position and the current letter's actual slide-out amount, and
     overrides this with an exact inline margin-top so the letter's top
     can never rise above the buttons, however long it is or wherever the
     buttons row currently sits (e.g. pushed down by the open info box). */
  .envelope-wrap {
    margin-top: 40px;
  }

  /* display:contents on desktop (default, outside this media query) makes
     this wrapper invisible to layout, so .imprint-wrap/.footer-credit's
     own fixed corner positioning is unaffected there. Only inside this
     mobile block does it become a real flex container. */
  .site-footer {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    justify-content: flex-start;
    gap: 25px;
    margin-top: 40px;
    padding: 0 20px 30px;
  }

  .imprint-wrap,
  .footer-credit {
    position: relative;
    inset: auto;
  }
}
