/* The landing: one viewport, a phone frame over the sky video, the pitch as
   a text thread inside it. Nothing scrolls unless the window is shorter than
   header, 480px phone and footer stacked.

   /how and /login link pip.v4.css and are untouched by this file.

   Versioned in the filename on purpose: /assets/* is cached immutable for a
   year, so a change means pip.v9.css plus the link in index.html and the path
   in test/landing.test.js.

   No @import and system stack only, which is what makes the phone internals
   read as the real Messages. */

:root {
  --ink:      #0c0b0b;   /* the page behind the sky */
  --bubble:   #e9e9eb;   /* theirs */
  --blue:     #0a7cff;   /* mine, and every control inside the phone */
  --sheet:    rgba(250, 250, 250, .94);
  --hair:     rgba(0, 0, 0, .09);
  --chrome:   rgba(60, 60, 67, .75);
}

* { box-sizing: border-box; }

html, body { margin: 0; padding: 0; }

/* No scrollbar-gutter reservation: the page is one viewport and does not
   scroll, so a permanent empty gutter would just be a stripe of dark. */
html {
  background: var(--ink);
  -webkit-text-size-adjust: 100%;
}

body {
  background: var(--ink);
  color: rgba(255, 255, 255, .72);
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Helvetica Neue', sans-serif;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

a { color: rgba(255, 255, 255, .72); text-decoration: none; }
a:hover { color: #fff; }

/* Outside the phone the ring is white, because the only backdrop out here is
   a dark sky. Inside the phone it is the Messages blue on white. */
:focus-visible { outline: 2px solid #fff; outline-offset: 3px; border-radius: 4px; }
.screen :focus-visible,
#handoff :focus-visible { outline: 2px solid var(--blue); outline-offset: 2px; }

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

/* --- the page column --------------------------------------------------- */
/* Header, stage, footer in a column. The height is definite rather than a
   minimum, because the phone sizes itself as a percentage of the stage and a
   percentage against an auto height resolves to auto: the frame would grow to
   fit its own content and push the footer off screen. The stage takes the
   slack, so the frame can never grow into the chrome, and all three are in
   normal flow rather than positioned, so overlap cannot happen either. */
#page {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  height: 100vh;
  height: 100dvh;
}

.top, .foot { flex: none; }

/* --- the sky ---------------------------------------------------------- */
/* Decoration, and nothing but: every word on this page is legible with the
   video removed, which is what lets reduced motion drop it. */
.sky {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  overflow: hidden;
}

.sky > * { position: absolute; }

.sky-fallback {
  inset: 0;
  background: linear-gradient(180deg, #cfd6de 0%, #d9d3c8 46%, #c3b7a6 100%);
}

.sky-drift {
  inset: -12%;
  background:
    radial-gradient(48% 26% at 22% 32%, rgba(255, 255, 255, .72), rgba(255, 255, 255, 0) 70%),
    radial-gradient(38% 22% at 68% 24%, rgba(255, 255, 255, .55), rgba(255, 255, 255, 0) 72%),
    radial-gradient(56% 30% at 80% 62%, rgba(255, 252, 246, .5), rgba(255, 255, 255, 0) 70%),
    radial-gradient(44% 24% at 38% 74%, rgba(240, 232, 220, .55), rgba(255, 255, 255, 0) 74%);
  animation: pipDrift 68s linear infinite alternate;
}

@keyframes pipDrift {
  from { transform: translate3d(-3%, 1%, 0) scale(1.04); }
  to   { transform: translate3d(4%, -2%, 0) scale(1.12); }
}

/* The poster is the first frame of the same clip, so the swap when the video
   is ready is invisible. It also stands in permanently when the video errors
   or when motion is reduced and the src is never promoted. */
#sky-video {
  inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
}

/* Lighter than the design file's .34 to .5: that was tuned against a paler
   reference clip, and on this sunset timelapse it crushed the clouds into a
   black rectangle. The scrims below carry the header and footer, so the
   middle of the picture can stay a sky. */
.sky-shade {
  inset: 0;
  background: linear-gradient(180deg, rgba(12, 11, 11, .14), rgba(12, 11, 11, .28));
}

/* A bright cloud can drift under the header or the footer at any moment, and
   white 12px type on a white cloud is gone. These two hold the contrast under
   the chrome without darkening the middle of the picture. */
.sky-scrim-top,
.sky-scrim-bottom {
  left: 0; right: 0;
  height: 140px;
}
.sky-scrim-top {
  top: 0;
  background: linear-gradient(180deg, rgba(12, 11, 11, .55), rgba(12, 11, 11, 0));
}
.sky-scrim-bottom {
  bottom: 0;
  background: linear-gradient(0deg, rgba(12, 11, 11, .55), rgba(12, 11, 11, 0));
}

/* --- header ----------------------------------------------------------- */
.top {
  position: relative;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: nowrap;
  gap: 12px;
  padding: 16px 28px;
  padding-top: calc(16px + env(safe-area-inset-top));
  padding-left: calc(28px + env(safe-area-inset-left));
  padding-right: calc(28px + env(safe-area-inset-right));
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  flex: none;
  min-height: 40px;
  color: rgba(255, 255, 255, .92);
}
.brand:hover { color: #fff; }

.brand img {
  width: 24px; height: 24px;
  border-radius: 50%;
  object-fit: cover;
}

.brand span {
  font-size: 17px;
  font-weight: 500;
  letter-spacing: -.01em;
}

/* The door for people who already text Pip. It sits on the sky, opposite the
   mark, so it never competes with the thread inside the phone. */
.top-login {
  margin-left: auto;
  flex: none;
  min-height: 40px;
  padding: 8px 16px;
  border-radius: 999px;
  border: 1px solid rgba(255, 255, 255, .34);
  background: rgba(255, 255, 255, .14);
  color: #fff;
  font-size: 15px;
  font-weight: 600;
  letter-spacing: -.01em;
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
}
.top-login:hover { background: rgba(255, 255, 255, .26); color: #fff; }

/* --- the stage and the frame ------------------------------------------ */
/* flex: 1 1 auto so the stage takes the slack. Per CSS Flexbox 9.8 a flexed
   item ends up with a definite main size, which is what the phone's percentage
   height resolves against.

   min-height is the phone's own floor, not 0. At 0 the stage would shrink
   under a short viewport while the frame kept its 480px minimum, and the frame
   would spill over the footer with the document none the wiser. At 480 the
   stage overflows #page instead, so the page grows and scrolls, and the footer
   moves down with it because it is still in flow. */
.stage {
  position: relative;
  z-index: 1;
  flex: 1 1 auto;
  /* The phone's 480px floor plus 4px above and below, which is what clears
     the bezel's 3px ring from the header and the footer. */
  min-height: 488px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 4px 12px;
}

/* No transform: scale. Scaling blurs the text, breaks iOS focus scrolling and
   still leaves the frame overlapping the chrome at short heights. The frame
   is responsive instead and its contents stay at native size.

   The height is the stage's, capped at the frame's natural size. Below the
   480px floor the stage overflows #page and the document scrolls; the footer
   is still in flow after the stage, so it moves down rather than overlapping. */
.phone {
  position: relative;
  width: min(392px, 100%);
  height: min(100%, 846px);
  min-height: 480px;
  flex: none;
  background: #0a0a0b;
  border-radius: 58px;
  padding: 11px;
  box-shadow:
    0 0 0 2px #38383b,
    0 0 0 3px #0a0a0b,
    0 50px 90px -24px rgba(0, 0, 0, .85);
}

.notch {
  position: absolute;
  top: 22px; left: 50%;
  transform: translateX(-50%);
  width: 116px; height: 34px;
  background: #000;
  border-radius: 20px;
  /* Above the menu (9) and the about pane (8): .screen makes no stacking
     context of its own, so an opaque pane inside it would otherwise paint
     over the notch and the phone would lose its notch while "about" is open. */
  z-index: 10;
}

.screen {
  position: relative;
  height: 100%;
  background: #fff;
  border-radius: 47px;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  font-family: -apple-system, 'SF Pro Text', 'Helvetica Neue', sans-serif;
  color: #000;
}

/* --- the thread header ------------------------------------------------ */
.thread-head {
  position: relative;
  flex: none;
  padding: 54px 12px 8px;
  background: rgba(246, 246, 246, .94);
  backdrop-filter: saturate(180%) blur(20px);
  -webkit-backdrop-filter: saturate(180%) blur(20px);
  border-bottom: 1px solid rgba(0, 0, 0, .13);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
}

.head-back {
  position: absolute;
  left: 12px; bottom: 26px;
  color: var(--blue);
  font-size: 26px;
  line-height: 1;
  font-weight: 300;
}

.head-more {
  position: absolute;
  right: 12px; bottom: 22px;
  width: 34px; height: 34px;
  border: none;
  border-radius: 50%;
  background: rgba(120, 120, 128, .16);
  color: var(--blue);
  font-size: 15px;
  letter-spacing: 1px;
  font-family: inherit;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
}
.head-more:hover { background: rgba(120, 120, 128, .28); }

.head-avatar {
  width: 44px; height: 44px;
  border-radius: 50%;
  object-fit: cover;
}

.head-name { font-size: 11px; color: #000; letter-spacing: -.1px; }
.head-name span { color: rgba(60, 60, 67, .5); }

/* --- the thread ------------------------------------------------------- */
.thread {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding: 14px 12px 10px;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 3px;
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.thread::-webkit-scrollbar { display: none; }

.stamp {
  text-align: center;
  font-size: 11px;
  color: rgba(60, 60, 67, .75);
  margin-bottom: 10px;
}
.stamp b { color: rgba(60, 60, 67, .85); }

.bubble {
  max-width: 76%;
  font-size: 17px;
  line-height: 1.29;
  padding: 8px 14px;
  border-radius: 19px;
  margin-top: 2px;
  animation: pipIn .26s cubic-bezier(.2, .9, .3, 1) both;
  overflow-wrap: anywhere;
}
.bubble.theirs { align-self: flex-start; background: var(--bubble); color: #000; }
.bubble.mine   { align-self: flex-end;   background: var(--blue);   color: #fff; }

@keyframes pipIn {
  from { opacity: 0; transform: translateY(8px) scale(.96); }
  to   { opacity: 1; transform: none; }
}

.typing {
  align-self: flex-start;
  background: var(--bubble);
  border-radius: 19px;
  padding: 12px 15px;
  margin-top: 2px;
  display: flex;
  gap: 5px;
  align-items: center;
}
.typing i {
  width: 8px; height: 8px;
  border-radius: 50%;
  background: #8e8e93;
  animation: pipDot 1.2s infinite;
}
.typing i:nth-child(2) { animation-delay: .18s; }
.typing i:nth-child(3) { animation-delay: .36s; }

@keyframes pipDot {
  0%, 60%, 100% { opacity: .32; transform: translateY(0); }
  30%           { opacity: 1;   transform: translateY(-2px); }
}

/* --- chips ------------------------------------------------------------ */
.chips {
  align-self: flex-end;
  max-width: 92%;
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 8px;
  margin: 10px 0 2px;
}

.chip {
  min-height: 40px;
  padding: 9px 15px;
  border-radius: 999px;
  border: 1.5px solid var(--blue);
  background: #fff;
  color: var(--blue);
  font-size: 16px;
  font-family: inherit;
  cursor: pointer;
  transition: background .12s, transform .12s;
}
.chip:hover { background: rgba(10, 124, 255, .08); }
.chip:active { transform: scale(.96); }
.chip.on { background: var(--blue); color: #fff; }
.chip.on:hover { background: var(--blue); }
/* Quiet, not gone: the row must not shrink under a thumb once the cap is reached. */
.chip:disabled { opacity: .45; cursor: default; }
.chip:disabled:hover { background: #fff; }
.chip:disabled:active { transform: none; }

.hint {
  align-self: flex-end;
  margin: 6px 2px 0;
  font-size: 12px;
  color: var(--chrome);
  display: flex;
  align-items: center;
  gap: 5px;
}
.hint span { font-size: 14px; }

.go {
  align-self: flex-end;
  margin-top: 8px;
  min-height: 42px;
  padding: 10px 18px;
  border: none;
  border-radius: 999px;
  background: var(--blue);
  color: #fff;
  font-size: 16px;
  font-weight: 500;
  font-family: inherit;
  cursor: pointer;
  animation: pipIn .2s both;
  transition: transform .12s;
}
.go:active { transform: scale(.96); }

/* --- the claim ------------------------------------------------------- */
/* One button. Ink rather than the thread's blue: white on #0a7cff is 3.5:1,
   under the 4.5:1 that 15px type needs, and ink keeps the one real control
   from reading as another bubble. The device decides what it does: a composer
   on phones and Macs, the QR dialog elsewhere. */
.claim {
  align-self: stretch;
  display: flex;
  justify-content: center;
  margin: 12px 0 4px;
  animation: pipIn .24s both;
}

.btn-claim {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  max-width: 300px;
  min-height: 50px;
  padding: 14px 24px;
  border-radius: 999px;
  background: var(--ink);
  color: #fff;
  font-size: 15px;
  font-weight: 700;
  letter-spacing: .08em;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  box-shadow: 0 8px 22px rgba(12, 11, 11, .22);
  transition: transform .12s, background .12s, box-shadow .12s;
}
.btn-claim:hover { background: #2b2620; color: #fff; }
.btn-claim:active { transform: scale(.97); box-shadow: 0 4px 12px rgba(12, 11, 11, .18); }

/* --- the count -------------------------------------------------------- */
/* Real or absent. There is no fallback number anywhere on this page: an
   invented count sits on the one line that exists to be trusted. */
.count {
  /* Its own row between the thread and the composer, not a child of the
     thread: the thread is a live region and this line has to stay last. A
     button, because it opens the board of what those people said. */
  flex: none;
  display: block;
  width: 100%;
  margin: 0;
  padding: 6px 12px 8px;
  border: none;
  background: none;
  font-family: inherit;
  font-size: 12px;
  color: rgba(60, 60, 67, .75);
  text-align: center;
  line-height: 1.4;
  cursor: pointer;
}
.count:hover { color: #000; }
.count .heart { display: inline-block; font-size: 11px; transition: transform .2s; }
.count:hover .heart { transform: scale(1.25); }

/* --- the composer ----------------------------------------------------- */
.composer {
  flex: none;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 10px 14px;
  background: #fff;
  border-top: 1px solid rgba(0, 0, 0, .07);
}

.plus {
  width: 34px; height: 34px;
  flex: none;
  border-radius: 50%;
  background: rgba(120, 120, 128, .16);
  color: #6b6b70;
  font-size: 21px;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
}

.field {
  flex: 1;
  min-width: 0;
  height: 36px;
  border: 1px solid #e2e2e6;
  border-radius: 999px;
  padding: 0 14px;
  font-size: 17px;
  color: rgba(60, 60, 67, .32);
  display: flex;
  align-items: center;
  background: #f7f7f8;
}

/* 17px, not smaller: anything under 16px makes iOS Safari zoom the whole
   frame on focus, and the frame is the page. */
#draft {
  flex: 1;
  min-width: 0;
  height: 36px;
  border: 2px solid var(--blue);
  border-radius: 999px;
  padding: 0 13px;
  font-size: 17px;
  font-family: inherit;
  color: #000;
  background: #fff;
  outline: none;
  box-shadow: 0 0 0 4px rgba(10, 124, 255, .14);
}
#draft::placeholder { color: rgba(60, 60, 67, .4); }

/* --- the action sheet ------------------------------------------------- */
.menu {
  position: absolute;
  inset: 0;
  z-index: 9;
  border-radius: 47px;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
}

.menu-scrim {
  position: absolute;
  inset: 0;
  border: none;
  padding: 0;
  background: rgba(0, 0, 0, .24);
  backdrop-filter: blur(1px);
  -webkit-backdrop-filter: blur(1px);
  cursor: default;
}

.menu-panes {
  position: relative;
  padding: 0 8px 10px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  animation: pipSheet .24s cubic-bezier(.2, .9, .3, 1) both;
}

@keyframes pipSheet {
  from { transform: translateY(100%); }
  to   { transform: none; }
}

.menu-card {
  background: var(--sheet);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  border-radius: 14px;
  overflow: hidden;
}

.menu-item {
  display: block;
  width: 100%;
  min-height: 57px;
  padding: 17px 14px;
  border: none;
  background: none;
  color: var(--blue);
  font-size: 20px;
  font-family: inherit;
  text-align: center;
  cursor: pointer;
}
.menu-item:hover { background: rgba(120, 120, 128, .1); color: var(--blue); }
/* The divider belongs to an item that actually has a visible item above it.
   `+` alone would give "about pip" a top border while the discord item is
   still hidden, because a display:none sibling still matches a combinator. */
.menu-card > .menu-item:not([hidden]) ~ .menu-item { border-top: 1px solid var(--hair); }

.menu-solo {
  border-radius: 14px;
  background: var(--sheet);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  font-weight: 600;
}
.menu-solo:hover { background: #fff; }

.menu-cancel {
  background: rgba(255, 255, 255, .96);
  font-weight: 600;
}
.menu-cancel:hover { background: #fff; }

/* --- the about pane --------------------------------------------------- */
.about {
  position: absolute;
  inset: 0;
  z-index: 8;
  background: #fff;
  display: flex;
  flex-direction: column;
  border-radius: 47px;
  overflow: hidden;
  animation: pipIn .22s both;
}

.about-head {
  position: relative;
  flex: none;
  padding: 54px 12px 10px;
  background: rgba(246, 246, 246, .96);
  backdrop-filter: saturate(180%) blur(20px);
  -webkit-backdrop-filter: saturate(180%) blur(20px);
  border-bottom: 1px solid rgba(0, 0, 0, .13);
  display: flex;
  align-items: flex-end;
  justify-content: center;
}

.about-back {
  position: absolute;
  left: 8px; bottom: 6px;
  border: none;
  background: none;
  color: var(--blue);
  font-size: 17px;
  font-family: inherit;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 2px;
  padding: 4px 6px;
}
.about-back span { font-size: 24px; line-height: 1; font-weight: 300; }

.about-title { font-size: 13px; font-weight: 600; color: #000; }

.about-thread {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 16px 12px;
  display: flex;
  flex-direction: column;
  gap: 3px;
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.about-thread::-webkit-scrollbar { display: none; }
.about-thread .bubble.theirs { max-width: 80%; }
/* The two questions sit where a reply would, until they are sent. */
.about-asks { margin-top: 4px; }

/* --- footer ----------------------------------------------------------- */
.foot {
  position: relative;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: nowrap;
  gap: 12px 28px;
  padding: 14px 28px;
  padding-bottom: calc(14px + env(safe-area-inset-bottom));
  padding-left: calc(28px + env(safe-area-inset-left));
  padding-right: calc(28px + env(safe-area-inset-right));
  font-size: 12px;
  letter-spacing: .04em;
}

.foot a {
  display: inline-flex;
  align-items: center;
  min-height: 40px;
}

#login-link { color: rgba(255, 255, 255, .62); }
#login-link:hover { color: #fff; }

/* No `order` anywhere in the footer. Source order is reading order: login,
   then the credit, then socials, then the legal row. */
.credit { display: flex; align-items: center; }

/* Antler ship one wordmark, in their own dark ink. The page under it is a
   night sky, so it is knocked out to white rather than recoloured. */
#antler {
  filter: brightness(0) invert(1);
  opacity: .75;
  display: block;
}

.legal { display: flex; gap: 18px; }

/* One cluster of circles. Discord is filled white so it reads first; the
   others stay outlines until a hover. */
.socials {
  display: flex;
  align-items: center;
  gap: 8px;
}
.socials a {
  width: 36px;
  height: 36px;
  min-height: 36px;
  justify-content: center;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, .32);
  color: rgba(255, 255, 255, .82);
  text-decoration: none;
}
.socials a:hover { background: rgba(255, 255, 255, .16); color: #fff; }
.socials svg { width: 16px; height: 16px; display: block; }
.socials .footer-discord {
  background: #fff;
  border-color: #fff;
  color: #14120f;
}
.socials .footer-discord:hover { background: #fff; color: #000; }

/* One group on the right: icons, then the wordmark, then the legal words.
   Never display:contents, which let the circles fall out of the row and
   off the bottom of a phone. */
.foot-row {
  display: flex;
  align-items: center;
  gap: 22px;
  flex: none;
}

/* --- the desktop handoff dialog --------------------------------------- */
/* Native <dialog> supplies the focus trap, Escape and the top layer.
   Closed-state display:none is the user-agent default; we repeat it so a
   browser without the element cannot leak the card inline. */
#handoff {
  width: 100vw; height: 100%;
  max-width: none; max-height: none;
  margin: 0; padding: 20px;
  border: none;
  background: transparent;
  color: #14120f;
}
#handoff:not([open]) { display: none; }

/* The dialog only opens on computers that cannot text, but a short window
   there still overflows #page, and a wheel over the backdrop would scroll the
   thread behind it. Pinning the body holds it, and lockPage writes the
   negative top that keeps the visitor where they were. */
body.handoff-open {
  position: fixed;
  left: 0; right: 0;
  width: 100%;
  overflow: hidden;
}

#handoff[open] {
  display: flex;
  align-items: center;
  justify-content: center;
  position: fixed;
  inset: 0;
  z-index: 50;
}
#handoff::backdrop {
  background: rgba(12, 11, 11, .55);
  backdrop-filter: blur(5px);
  -webkit-backdrop-filter: blur(5px);
}

.handoff-card {
  width: 100%;
  max-width: 400px;
  background: #fff;
  border-radius: 24px;
  box-shadow: 0 40px 90px -20px rgba(0, 0, 0, .6);
  padding: clamp(22px, 4vh, 32px);
  text-align: center;
  max-height: calc(100dvh - 40px);
  overflow-y: auto;
  animation: pipIn .22s both;
}

.handoff-mark { width: 46px; height: auto; margin: 0 auto 12px; display: block; }
.handoff-card h2 {
  font-size: 24px; line-height: 1.15; font-weight: 600;
  margin: 0 0 8px; color: #000;
}
.handoff-lede { font-size: 14px; color: rgba(60, 60, 67, .8); margin: 0 0 18px; line-height: 1.45; }
.handoff-qr { width: min(240px, 30vh); margin: 0 auto 16px; }
.handoff-qr svg { width: 100%; height: auto; display: block; }
.handoff-number {
  font-size: 18px; font-weight: 700; color: #000;
  font-variant-numeric: tabular-nums;
  margin: 0 0 10px; user-select: all;
}
.handoff-body {
  font-size: 14px; color: rgba(60, 60, 67, .8);
  margin: 0 0 18px; user-select: all; line-height: 1.45;
}
.handoff-actions { display: flex; gap: 8px; justify-content: center; flex-wrap: wrap; }
.handoff-actions .btn {
  min-height: 44px;
  padding: 12px 18px;
  font-size: 13px;
  font-family: inherit;
  font-weight: 600;
  letter-spacing: .04em;
  border: none;
  border-radius: 999px;
  background: var(--blue);
  color: #fff;
  cursor: pointer;
}
.handoff-actions .btn:hover { background: #0968d6; }
#handoff-status {
  font-size: 13px; color: rgba(60, 60, 67, .8);
  margin: 12px 0 0; min-height: 1.2em;
}
.handoff-quiet {
  background: none; border: none; font-family: inherit;
  font-size: 13px; color: rgba(60, 60, 67, .75);
  cursor: pointer; margin-top: 14px;
  min-height: 40px; white-space: nowrap;
}
.handoff-quiet:hover { color: #000; }

/* --- the board ---------------------------------------------------------- */
/* Prints on a table. The dialog fills the viewport; the table is drawn in
   CSS (a dark walnut: grain from two repeating gradients at slightly different
   angles, a warm light from above, a vignette at the edges) so there is no
   texture file to load. Every card is placed by percentage in a 16:10 board
   and rotated a few degrees, the way things land when they are put down and
   not arranged. */
#love {
  width: 100vw; height: 100%;
  max-width: none; max-height: none;
  margin: 0; padding: 0;
  border: none;
  background: transparent;
  color: #f3ede4;
}
#love:not([open]) { display: none; }
#love[open] {
  display: block;
  position: fixed;
  inset: 0;
  z-index: 50;
}
#love::backdrop { background: #0c0b0b; }

.love-table {
  position: relative;
  width: 100%;
  height: 100%;
  overflow: auto;
  padding: clamp(20px, 4vh, 44px) clamp(16px, 4vw, 48px) 40px;
  background:
    radial-gradient(ellipse 80% 55% at 50% 0%, rgba(255, 214, 170, .18), transparent 70%),
    repeating-linear-gradient(91deg, rgba(0, 0, 0, .10) 0 2px, transparent 2px 11px),
    repeating-linear-gradient(89deg, rgba(255, 255, 255, .028) 0 1px, transparent 1px 17px),
    repeating-linear-gradient(90.6deg, rgba(0, 0, 0, .06) 0 5px, transparent 5px 41px),
    linear-gradient(180deg, #3d2a1d 0%, #2c1d13 55%, #22160e 100%);
  box-shadow: inset 0 0 160px 40px rgba(0, 0, 0, .55);
  animation: loveTable .7s ease-out both;
}
@keyframes loveTable { from { opacity: 0; } }

.love-close {
  position: fixed;
  top: 14px; right: 16px;
  width: 44px; height: 44px;
  border: none; border-radius: 50%;
  background: rgba(255, 255, 255, .10);
  color: #fff;
  font-size: 26px; line-height: 1;
  cursor: pointer;
  z-index: 20;
}
.love-close:hover { background: rgba(255, 255, 255, .18); }

.love-title {
  margin: 0;
  padding: 0 56px;
  font-size: clamp(22px, 3.2vw, 34px);
  font-weight: 700;
  letter-spacing: -.01em;
  text-align: center;
  animation: loveRise .8s cubic-bezier(.2, .8, .2, 1) .15s both;
}
.love-sub {
  margin: 8px 0 0;
  text-align: center;
  font-size: 14px;
  color: rgba(243, 237, 228, .62);
  animation: loveRise .8s cubic-bezier(.2, .8, .2, 1) .28s both;
}
@keyframes loveRise { from { opacity: 0; transform: translateY(12px); } }

.love-board {
  position: relative;
  list-style: none;
  margin: clamp(18px, 3vh, 32px) auto 0;
  padding: 0;
  width: min(100%, 1180px);
  aspect-ratio: 16 / 10;
}

.love-card {
  position: absolute;
  left: var(--x);
  top: var(--y);
  width: var(--w);
  transform: rotate(var(--r));
  /* Each one lands a beat after the last: dropped from a hand's height,
     a few degrees off its resting angle, the shadow wide while it is in the
     air and tight once it is down. */
  /* backwards, not both: once landed the keyframe must let go, or the lift
     on tap would be overridden by it. The last frame equals rest, so nothing
     jumps at the handover. */
  animation: loveLand .9s cubic-bezier(.16, .84, .24, 1) backwards;
  animation-delay: calc(.35s + var(--i) * .11s);
  transition: transform .45s cubic-bezier(.2, .8, .2, 1), z-index 0s;
  z-index: 1;
}
@keyframes loveLand {
  0%   { opacity: 0; transform: translateY(-56px) rotate(calc(var(--r) - 9deg)) scale(1.12); }
  55%  { opacity: 1; }
  100% { opacity: 1; transform: translateY(0) rotate(var(--r)) scale(1); }
}
.love-pick {
  display: block;
  width: 100%;
  margin: 0;
  padding: 6px;
  border: none;
  border-radius: 6px;
  background: #fbf8f2;
  cursor: zoom-in;
  box-shadow: 0 18px 40px -14px rgba(0, 0, 0, .7), 0 2px 6px rgba(0, 0, 0, .35);
  transition: box-shadow .45s cubic-bezier(.2, .8, .2, 1), transform .45s cubic-bezier(.2, .8, .2, 1);
  animation: loveShadow .9s cubic-bezier(.16, .84, .24, 1) backwards;
  animation-delay: inherit;
}
@keyframes loveShadow {
  0%   { box-shadow: 0 70px 90px -30px rgba(0, 0, 0, .75), 0 2px 6px rgba(0, 0, 0, 0); }
  100% { box-shadow: 0 18px 40px -14px rgba(0, 0, 0, .7), 0 2px 6px rgba(0, 0, 0, .35); }
}
.love-pick img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 3px;
}
/* Hover only where hover exists, or a tapped print stays raised on a phone. */
@media (hover: hover) {
  .love-card:hover { z-index: 5; }
  .love-card:hover .love-pick { transform: translateY(-4px) scale(1.02); box-shadow: 0 28px 50px -16px rgba(0, 0, 0, .75), 0 3px 8px rgba(0, 0, 0, .35); }
}
/* Lifted: square, larger (tall prints more so, they start small), and nudged
   by --px/--py where a print at an edge would otherwise grow off the board or
   under the title. */
.love-card.up { z-index: 10; transform: rotate(0deg) scale(var(--s, 1.6)) translate(var(--px, 0), var(--py, 0)); }
.love-card.up .love-pick { cursor: zoom-out; box-shadow: 0 40px 80px -20px rgba(0, 0, 0, .8), 0 4px 12px rgba(0, 0, 0, .4); }

/* Under 720px the table is a column: the prints stack, still a little off
   square and a little overlapping, and the page scrolls through them. */
@media (max-width: 720px) {
  .love-board { aspect-ratio: auto; display: flex; flex-direction: column; align-items: center; gap: 6px; padding-bottom: 20px; }
  .love-card { position: static; width: min(92%, 420px); margin-top: -10px; }
  .love-card.tall { width: min(58%, 260px); }
  .love-card:nth-child(even) { align-self: flex-end; margin-right: 4%; }
  .love-card:nth-child(odd) { align-self: flex-start; margin-left: 4%; }
  /* On a phone a print is too small to read where it lies, so the lift is a
     lightbox: the print fills the width over a dimmed table, which also hides
     the column closing up behind it. */
  .love-card.up {
    position: fixed;
    left: 50%; top: 50%;
    width: min(96vw, 560px);
    margin: 0;
    transform: translate(-50%, -50%) rotate(0deg);
  }
  /* The dim sits on the table, not the card: a fixed box inside a transformed
     card is contained by the card. */
  #love.lit .love-table::after { content: ''; position: fixed; inset: 0; background: rgba(0, 0, 0, .65); z-index: 8; }
  .love-card.up .love-pick img { max-height: 84vh; width: auto; max-width: 100%; margin: 0 auto; }
}

/* --- narrow ----------------------------------------------------------- */
/* Under 420px the 58px bezel radius starts biting into the first and last
   lines of the thread, so the corners come in and the notch shrinks with
   them. The head loses the room the notch was reserving too. */
@media (max-width: 420px) {
  .phone { border-radius: 44px; padding: 9px; }
  .screen, .menu, .about { border-radius: 34px; }
  .notch { width: 96px; height: 28px; top: 18px; }
  .thread-head { padding-top: 44px; }
  .about-head { padding-top: 44px; }
  .stage { padding: 4px 8px; }
}

/* Two centred rows: the login line on its own, then the legal links and the
   wordmark side by side. Three rows cost a 390x664 phone the hundred pixels
   that decided whether the page scrolled at all, so the chrome is trimmed to
   the minimum here and a 480px frame fits every current phone in portrait.
   .foot-row is display: contents everywhere else, so the wide layout still
   sees three direct children spaced across the footer. The header keeps the
   brand hard left: it is one item and it has nowhere to wrap to. */
@media (max-width: 600px) {
  .top {
    padding: 8px 18px 4px;
    padding-top: calc(8px + env(safe-area-inset-top));
    padding-left: calc(18px + env(safe-area-inset-left));
    padding-right: calc(18px + env(safe-area-inset-right));
  }
  .foot {
    flex-direction: column;
    justify-content: center;
    gap: 2px;
    padding: 4px 18px 8px;
    padding-bottom: calc(8px + env(safe-area-inset-bottom));
    padding-left: calc(18px + env(safe-area-inset-left));
    padding-right: calc(18px + env(safe-area-inset-right));
  }
  .foot a { min-height: 36px; }
  .foot-row { flex-wrap: wrap; justify-content: center; gap: 2px 14px; width: 100%; }
  .socials { flex: 1 0 100%; justify-content: center; order: -1; }
  .foot .legal { gap: 14px; }
  .foot .socials { gap: 8px; }
}

/* Under 640px (a landscape phone, or a 720p laptop once the browser has taken
   its share) the 488px stage plus header and footer no longer fit, and the
   page scrolls, which is the one case where that is allowed. Trim the chrome
   so the scroll is as short as it can be. */
@media (max-height: 640px) {
  .top { padding-top: calc(10px + env(safe-area-inset-top)); padding-bottom: 10px; }
  .foot { padding-top: 10px; padding-bottom: calc(10px + env(safe-area-inset-bottom)); }
}

/* --- reduced motion --------------------------------------------------- */
/* The sky video goes and the poster carries the picture. Everything that
   moves on its own stops; nothing here is load bearing, and the thread still
   plays out on its timers because that is the content, not decoration. */
@media (prefers-reduced-motion: reduce) {
  /* The video element stays: the script withholds its src, so with nothing
     to play it is only the poster, a still frame that moves no more than the
     gradient behind it. Hiding it here would take the picture away too. */
  .sky-drift { animation: none; }
  .bubble, .claim, .go, .about, .menu-panes { animation: none; }
  .typing i { animation: none; opacity: .5; }
  .chip, .go, .btn-claim { transition: none; }
  .handoff-card { animation: none; }
  .love-table, .love-title, .love-sub, .love-card, .love-pick { animation: none; }
  .love-card, .love-pick, .count .heart { transition: none; }
}

/* The hidden attribute has to win. Every display rule above is a plain class
   rule, so it outranks the user-agent [hidden] { display: none } and an
   element carrying both would paint anyway. #claim, #proof-text, #draft,
   #menu, #about and #menu-discord are all exactly that case. */
[hidden] { display: none !important; }
