:root {
  /* Cell size from the app: EmojiCell.idealWidth = 128 + 8 padding * 2 = 144. */
  --cell: 144px;
  --gap: 16px;          /* app grid spacing */
  --row-gap: 16px;
  --radius: 12px;       /* app cell corner radius */
  --speed: 22;          /* marquee px per second (unitless — read in JS) */
  --fade-depth: 12vh;   /* how far the top and bottom edge fades reach in */
  --edge-overscan: 0px; /* set in JS: how far the screen extends past the viewport */

  --bg: #0e0e13;
  --card: #1a1a22;
  --card-2: #232330;
  --text: #f4f4f7;
  --text-dim: #9a9aa8;
  --accent: #8b7cff;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  min-height: 100%;
  background: var(--bg);
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  /* Rows scroll themselves horizontally; the page only ever scrolls down. */
  overflow-x: hidden;
}

/* Vertical stack of marquee rows. Fills the viewport when the emojis are few,
   and grows past it — scrolling the page — once there are more than fit. */
.wall {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--row-gap);
  min-height: 100vh;
  /* viewport-fit=cover with a translucent status bar, so the wall runs under
     the notch — the top has to clear it. The plain padding stays first as the
     fallback where env() isn't understood. */
  padding: var(--row-gap) 0;
  padding-top: calc(var(--row-gap) + env(safe-area-inset-top, 0px));
}

/* The bottom only needs the inset on the Home Screen. In Safari the toolbar
   already occupies that band, so adding it there stacks padding on space the
   browser has reserved — a dead gap under the wall. */
@media (display-mode: standalone) {
  .wall { padding-bottom: calc(var(--row-gap) + env(safe-area-inset-bottom, 0px)); }
}

.row {
  overflow: hidden;
  flex: none;
}

/* Off-screen rows keep their layout but stop animating — with a long wall
   most rows are parked, and running every marquee is wasted work. */
.row.parked .track { animation-play-state: paused; }


.track {
  display: flex;
  gap: var(--gap);
  width: max-content;
  will-change: transform;
  animation: marquee linear infinite;
}

/* Content is rendered twice; -50% lands exactly on the seam → seamless loop. */
@keyframes marquee {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}

/* Odd rows scroll the other way. */
.row.reverse .track { animation-direction: reverse; }

.cell {
  position: relative;
  width: var(--cell);
  height: var(--cell);
  flex: none;
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--card);
  cursor: pointer;
}

/* Name reveals on hover; scrolling keeps going. Names run long (up to ~90
   characters), so the label grows upward to fit instead of clamping to two
   lines — bounded by the cell, which a name has never needed all of. */
.cell .label {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  max-height: 100%;
  padding: 20px 8px 7px;
  font-size: 11px;
  font-weight: 600;
  line-height: 1.25;
  text-align: center;
  text-wrap: balance;
  overflow-wrap: break-word;
  color: var(--text);
  /* Fade sits over the top padding, so every line keeps the same backing
     however many lines the name runs to. */
  background: linear-gradient(to top,
    rgba(0,0,0,0.9), rgba(0,0,0,0.9) calc(100% - 20px), transparent);
  opacity: 0;
  transform: translateY(4px);
  transition: opacity 0.15s ease, transform 0.15s ease;
  pointer-events: none;
  overflow: hidden;
}

.cell:hover .label {
  opacity: 1;
  transform: translateY(0);
}

/* Brief pop when tapped/copied. */
.cell.copied { animation: pop 0.35s ease; }
@keyframes pop {
  40% { transform: scale(0.92); }
  100% { transform: scale(1); }
}

.cell img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  padding: 8px;                 /* app cell padding */
  opacity: 0;
  transition: opacity 0.4s ease;
}

.cell.loaded img { opacity: 1; }
.cell.loaded .skeleton { opacity: 0; }

/* Skeleton: shimmering card with the emoji name until the image arrives. */
.skeleton {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 10px;
  background: linear-gradient(100deg, var(--card) 30%, var(--card-2) 50%, var(--card) 70%);
  background-size: 220% 100%;
  animation: shimmer 1.6s ease-in-out infinite;
  transition: opacity 0.3s ease;
}

@keyframes shimmer {
  from { background-position: 180% 0; }
  to   { background-position: -80% 0; }
}

.skeleton .name {
  font-size: 12px;
  line-height: 1.3;
  font-weight: 600;
  text-align: center;
  color: var(--text-dim);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.cell.error .skeleton { animation: none; opacity: 1; }

/* Fade the wall out at the screen edges.
   Three strips rather than one full-screen gradient. Sizing a single overlay
   to the screen means picking a viewport unit, and neither works: the small
   viewport stops at the browser toolbar, leaving the band behind it unfaded,
   while the large viewport runs past the toolbar so the point where the
   gradient turns solid falls below the visible edge — the fade then never
   completes on screen. Anchoring a strip to each edge sidesteps the choice:
   `bottom: 0` tracks the visible bottom as the toolbar shows and hides. */

/* Left and right. Nothing here interacts with the toolbar, so one overlay
   across the viewport is fine. */
html::after {
  content: "";
  position: fixed;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(to right,
    var(--bg), transparent 8%, transparent 92%, var(--bg));
}

/* Top and bottom. Each holds full colour across its safe-area inset — under
   the status bar or the home indicator the wall should be solid, not merely
   dimmed — and fades over --fade-depth beyond it. */
body::before,
body::after {
  content: "";
  position: fixed;
  left: 0;
  right: 0;
  pointer-events: none;
}

/* Each strip is anchored to its viewport edge and then pushed *outward* by
   --edge-overscan, measured in JS. In Safari the safe-area insets are 0 and
   the viewport is shorter than the screen, yet page content still shows
   through the translucent browser chrome above and below it — outside the box
   a fixed element is laid out against, so a strip starting at top:0 never
   reached it. Extending outward is safe in a way sizing to the large viewport
   was not: the fade still completes at the visible edge, and everything past
   it is solid, so overshooting costs nothing. */
body::before {
  top: calc(-1 * var(--edge-overscan));
  height: calc(var(--edge-overscan) + var(--fade-depth) + env(safe-area-inset-top, 0px));
  background: linear-gradient(to bottom,
    var(--bg) calc(var(--edge-overscan) + env(safe-area-inset-top, 0px)),
    transparent 100%);
}

body::after {
  bottom: calc(-1 * var(--edge-overscan));
  height: calc(var(--edge-overscan) + var(--fade-depth) + env(safe-area-inset-bottom, 0px));
  background: linear-gradient(to top,
    var(--bg) calc(var(--edge-overscan) + env(safe-area-inset-bottom, 0px)),
    transparent 100%);
}

/* ── ?debug ────────────────────────────────────────────────────────────
   Paints the three edge layers flat so their real extent is visible on a
   device, where env() actually resolves and the browser toolbar exists.
   Screenshot it on the phone: where each band stops relative to the status
   bar, the toolbar and the home indicator is the whole diagnosis. */

.debug-edges body::before { background: rgba(255, 70, 70, 0.55); }
.debug-edges body::after  { background: rgba(70, 150, 255, 0.55); }
.debug-edges::after       { background: rgba(80, 255, 140, 0.22); }

/* Hairlines at the exact edges of the viewport box a fixed element is laid
   out against — if these sit off-screen, that box is not what's visible. */
.debug-edges body::before { border-bottom: 1px solid #ff0; }
.debug-edges body::after  { border-top: 1px solid #ff0; }

/* Markers that locate the layout viewport on screen. If the magenta line sits
   below the status bar, fixed elements start there and the band above is out
   of reach; if the lime block is invisible, nothing paints outside the
   viewport at all and the fade can never cover that band by overlay. */
.debug-mark {
  position: fixed;
  left: 0;
  right: 0;
  z-index: 99;
  pointer-events: none;
}
.debug-mark-top    { top: 0;           height: 4px;  background: #f0f; }
.debug-mark-bottom { bottom: 0;        height: 4px;  background: #0ff; }
.debug-mark-above  { top: -70px;       height: 60px; background: #0f0; }
.debug-mark-below  { bottom: -70px;    height: 60px; background: #f80; }

.debug-panel {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 100;
  margin: 0;
  padding: 12px 14px;
  border-radius: 12px;
  background: rgba(0, 0, 0, 0.86);
  color: #b6ffb6;
  font: 500 11px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace;
  white-space: pre;
  pointer-events: none;
}

.status {
  position: fixed;
  inset: 0;
  margin: auto;
  height: fit-content;
  width: fit-content;
  max-width: 80%;
  padding: 14px 20px;
  border-radius: 12px;
  background: var(--card);
  color: var(--text-dim);
  font-size: 14px;
  text-align: center;
}
.status[hidden] { display: none; }

/* iCloud account, top right. Hidden entirely until CloudKit is configured. */
/* Mirrors .home in the opposite corner — same size and insets so the two
   line up. Above the search bar, since the panel drops down over it. */
.account {
  position: fixed;
  top: calc(22px + env(safe-area-inset-top));
  right: calc(22px + env(safe-area-inset-right));
  z-index: 32;
}
.account[hidden] { display: none; }

.account-button {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 46px;
  height: 46px;
  padding: 0;
  border: none;
  border-radius: 50%;
  cursor: pointer;
  color: var(--text-dim);
  background: rgba(18, 18, 26, 0.72);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  box-shadow:
    inset 0 0 0 1px rgba(139, 124, 255, 0.35),
    0 8px 24px rgba(0, 0, 0, 0.45);
  transition: color 0.15s ease, box-shadow 0.2s ease;
}

.account-button:hover,
.account-button[aria-expanded="true"] {
  color: var(--text);
  box-shadow:
    inset 0 0 0 1.5px rgba(139, 124, 255, 0.8),
    0 0 24px rgba(139, 124, 255, 0.35),
    0 8px 24px rgba(0, 0, 0, 0.45);
}

.account-icon { width: 24px; height: 24px; }

/* Signed in: initials replace the glyph, accent ring stays lit. */
.account.signed-in .account-button {
  color: var(--text);
  box-shadow:
    inset 0 0 0 2px rgba(139, 124, 255, 0.9),
    0 0 20px rgba(139, 124, 255, 0.3),
    0 8px 24px rgba(0, 0, 0, 0.45);
}
.account-initials {
  font-size: 15px;
  font-weight: 700;
  letter-spacing: 0.02em;
}
.account-initials[hidden] { display: none; }
.account.signed-in .account-icon { display: none; }

.account-panel {
  position: absolute;
  top: calc(100% + 10px);
  right: 0;
  width: max(240px, 62vw);
  max-width: 300px;
  padding: 16px;
  border-radius: 18px;
  background: rgba(18, 18, 26, 0.92);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  box-shadow:
    inset 0 0 0 1px rgba(139, 124, 255, 0.3),
    0 18px 50px rgba(0, 0, 0, 0.6);
}
.account-panel[hidden] { display: none; }

.account-status {
  margin: 0 0 14px;
  font-size: 13px;
  line-height: 1.45;
  color: var(--text-dim);
}

/* Scope the wall to the signed-in user's own published emojis. */
.account-mine {
  display: block;
  width: 100%;
  margin: 0 0 12px;
  padding: 10px 14px;
  border: none;
  border-radius: 12px;
  cursor: pointer;
  font-family: inherit;
  font-size: 14px;
  font-weight: 600;
  color: var(--text);
  background: rgba(139, 124, 255, 0.18);
  box-shadow: inset 0 0 0 1px rgba(139, 124, 255, 0.4);
  transition: background 0.15s ease, box-shadow 0.15s ease, opacity 0.15s ease;
}
.account-mine[hidden] { display: none; }
.account-mine:hover { background: rgba(139, 124, 255, 0.3); }
.account-mine:disabled { opacity: 0.5; cursor: default; }

/* Active scope: the button reads as on, and the icon keeps a filled ring so
   it's clear from the wall that this isn't the whole catalogue. */
.account-mine[aria-pressed="true"] {
  background: rgba(139, 124, 255, 0.85);
  color: #0e0e13;
  box-shadow: inset 0 0 0 1px rgba(139, 124, 255, 0.95);
}
.account.scoped .account-button {
  color: #0e0e13;
  background: rgba(139, 124, 255, 0.9);
  box-shadow:
    inset 0 0 0 2px rgba(139, 124, 255, 1),
    0 0 24px rgba(139, 124, 255, 0.5),
    0 8px 24px rgba(0, 0, 0, 0.45);
}

/* Apple renders its own button in here; give it room and centre it. */
#apple-sign-in-button,
#apple-sign-out-button {
  display: flex;
  justify-content: center;
}
#apple-sign-in-button:empty,
#apple-sign-out-button:empty { display: none; }

/* Big centered search bar with a glowing outline. */
.search-bar {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 30;
  display: flex;
  align-items: center;
  gap: 12px;
  width: min(560px, 88vw);
  padding: 16px 22px;
  border-radius: 999px;
  background: rgba(18, 18, 26, 0.72);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  box-shadow:
    inset 0 0 0 1px rgba(139, 124, 255, 0.45),
    0 0 24px rgba(139, 124, 255, 0.28),
    0 14px 50px rgba(0, 0, 0, 0.55);
  animation: search-glow 3.2s ease-in-out infinite;
  transition: box-shadow 0.25s ease;
}

@keyframes search-glow {
  0%, 100% {
    box-shadow:
      inset 0 0 0 1px rgba(139, 124, 255, 0.38),
      0 0 20px rgba(139, 124, 255, 0.22),
      0 14px 50px rgba(0, 0, 0, 0.55);
  }
  50% {
    box-shadow:
      inset 0 0 0 1px rgba(139, 124, 255, 0.68),
      0 0 40px rgba(139, 124, 255, 0.46),
      0 14px 50px rgba(0, 0, 0, 0.55);
  }
}

.search-bar:focus-within {
  animation: none;
  box-shadow:
    inset 0 0 0 1.5px rgba(139, 124, 255, 0.95),
    0 0 48px rgba(139, 124, 255, 0.55),
    0 14px 50px rgba(0, 0, 0, 0.6);
}

.search-icon {
  width: 22px;
  height: 22px;
  flex: none;
  color: var(--text-dim);
  transition: color 0.2s ease;
}
.search-bar:focus-within .search-icon { color: #b3a8ff; }

.search {
  flex: 1;
  min-width: 0;
  border: none;
  outline: none;
  background: transparent;
  color: var(--text);
  font-family: inherit;
  font-size: 19px;
  font-weight: 500;
}
.search::placeholder { color: var(--text-dim); }
.search::-webkit-search-decoration,
.search::-webkit-search-cancel-button { -webkit-appearance: none; appearance: none; }

.search-clear {
  flex: none;
  border: none;
  background: transparent;
  color: var(--text-dim);
  font-size: 24px;
  line-height: 1;
  cursor: pointer;
  padding: 0 2px;
  transition: color 0.15s ease;
}
.search-clear:hover { color: var(--text); }
.search-clear[hidden] { display: none; }

/* Tag filters, offered under the search bar as you type. */
.suggestions {
  position: absolute;
  top: calc(100% + 10px);
  left: 0;
  right: 0;
  z-index: 31;
  margin: 0;
  padding: 6px;
  list-style: none;
  max-height: min(44vh, 330px);
  overflow-y: auto;
  border-radius: 20px;
  background: rgba(18, 18, 26, 0.9);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  box-shadow:
    inset 0 0 0 1px rgba(139, 124, 255, 0.3),
    0 18px 50px rgba(0, 0, 0, 0.6);
}
.suggestions[hidden] { display: none; }

.suggestion {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding: 9px 16px;
  border-radius: 14px;
  cursor: pointer;
  font-size: 15px;
  font-weight: 500;
  color: var(--text);
}

.suggestion:hover,
.suggestion.active { background: rgba(139, 124, 255, 0.2); }

/* The part of the tag already typed. */
.suggestion mark {
  background: none;
  color: #b3a8ff;
  font-weight: 700;
}

.suggestion .count {
  flex: none;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  color: var(--text-dim);
}

.search-empty {
  position: fixed;
  top: calc(50% + 54px);
  left: 50%;
  transform: translateX(-50%);
  z-index: 30;
  margin: 0;
  color: var(--text-dim);
  font-size: 14px;
  text-align: center;
  max-width: 88vw;
}
.search-empty[hidden] { display: none; }

@media (prefers-reduced-motion: reduce) {
  .search-bar { animation: none; }
}

/* Copy confirmation toast. */
.toast {
  position: fixed;
  left: 50%;
  bottom: 32px;
  transform: translate(-50%, 12px);
  padding: 10px 18px;
  border-radius: 999px;
  background: var(--card-2);
  color: var(--text);
  font-size: 13px;
  font-weight: 600;
  box-shadow: 0 6px 24px rgba(0,0,0,0.45);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s ease, transform 0.2s ease;
  z-index: 10;
}
.toast.show {
  opacity: 1;
  transform: translate(-50%, 0);
}

/* Home, top left — back out to catnip.media. Same glass treatment as the
   search bar so it reads over whatever emoji happens to scroll behind it.
   env() keeps it clear of the notch when added to the Home Screen. */
.home {
  position: fixed;
  top: calc(22px + env(safe-area-inset-top));
  left: calc(22px + env(safe-area-inset-left));
  z-index: 20;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 46px;
  height: 46px;
  border-radius: 999px;
  color: var(--text-dim);
  background: rgba(18, 18, 26, 0.72);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  box-shadow: inset 0 0 0 1px rgba(139, 124, 255, 0.35),
              0 8px 24px rgba(0, 0, 0, 0.45);
  transition: color 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease;
}

.home svg { width: 22px; height: 22px; }

.home:hover,
.home:focus-visible {
  color: #b3a8ff;
  transform: translateY(-2px);
  box-shadow: inset 0 0 0 1.5px rgba(139, 124, 255, 0.7),
              0 10px 30px rgba(0, 0, 0, 0.5);
}

/* App Store badge, bottom right. Above the edge fade and the toast, below the
   search bar — the badge is the one thing that should never cover the input. */
.app-store {
  position: fixed;
  right: 28px;
  bottom: 28px;
  z-index: 20;
  display: block;
  line-height: 0;
  opacity: 0.85;
  transition: opacity 0.2s ease, transform 0.2s ease;
}

.app-store:hover {
  opacity: 1;
  transform: translateY(-2px);
}

/* Apple's own embed size; the artwork has its clear space baked in. */
.app-store img {
  display: block;
  width: 245px;
  height: 82px;
  object-fit: contain;
}

/* Desktop only. Touch devices (coarse pointer) get the App Store smart banner
   from the apple-itunes-app meta tag instead, and narrow screens have no room. */
@media (pointer: coarse), (max-width: 700px) {
  .app-store { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .track { animation: none; }
  .skeleton { animation: none; }
  .app-store { transition: none; }
  .app-store:hover { transform: none; }
  .home { transition: none; }
  .home:hover, .home:focus-visible { transform: none; }
}
