@font-face {
  font-family: 'Outfit';
  font-style: normal;
  font-weight: 100 900;
  font-display: block;
  src: url('fonts/outfit-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Outfit';
  font-style: normal;
  font-weight: 100 900;
  font-display: block;
  src: url('fonts/outfit-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

:root {
  --chalk: #FFF6EE;
  --apricot: #F7B98D;
  --coral: #EF8F8A;
  --orchid: #9A5AA6;
  --violet: #4B3290;
  --night: #221D5C;
  --mint: #7FE3C4;
  --col: 560px;
  --safe-t: env(safe-area-inset-top, 0px);
  --safe-b: env(safe-area-inset-bottom, 0px);
  --ease: cubic-bezier(.22, .9, .24, 1);
}

* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }

/* Several panels below set display: flex, which outranks the hidden
   attribute's own display: none. Without this, hiding the start screen does
   nothing and its buttons stay on top of the game. */
[hidden] { display: none !important; }

html, body {
  margin: 0; padding: 0; height: 100%; width: 100%;
  overflow: hidden; position: fixed; overscroll-behavior: none;
  background: var(--night);
  font-family: 'Outfit', 'Futura', 'Century Gothic', 'Avenir Next', system-ui, -apple-system, 'Segoe UI', sans-serif;
  color: var(--chalk);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

#app { position: fixed; inset: 0; touch-action: manipulation; }

#c { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }

/* The HUD sits on whatever colour the sky happens to be at this height, so
   both ends of the screen carry a whisper of shade to hold the type up. */
#app::before, #app::after {
  content: ''; position: absolute; left: 0; right: 0; z-index: 4; pointer-events: none;
}
#app::before {
  top: 0; height: 27vh;
  background: linear-gradient(180deg, rgba(16,11,44,.30), rgba(16,11,44,.10) 55%, rgba(16,11,44,0));
}
#app::after {
  bottom: 0; height: 16vh;
  background: linear-gradient(0deg, rgba(16,11,44,.28), rgba(16,11,44,0));
}

.col { max-width: var(--col); margin: 0 auto; width: 100%; }

/* ------------------------------------------------------------- gameplay HUD */

#hud {
  position: absolute; top: calc(var(--safe-t) + 14px); left: 0; right: 0;
  pointer-events: none; padding: 0 18px; z-index: 5;
}
#hudTop { position: relative; display: flex; justify-content: center; }
#scoreWrap { text-align: center; }
#score {
  font-size: clamp(54px, 16.5vw, 104px);
  font-weight: 800; line-height: .88; letter-spacing: -.035em;
  font-variant-numeric: tabular-nums;
  text-shadow: 0 3px 22px rgba(24, 12, 60, .38), 0 1px 3px rgba(24, 12, 60, .3);
  transition: transform .16s var(--ease);
}
#score.pop { animation: scorePop .34s var(--ease); }
@keyframes scorePop { 0% { transform: scale(1); } 34% { transform: scale(1.13); } 100% { transform: scale(1); } }
#best {
  font-size: 13px; font-weight: 600; letter-spacing: .16em; text-transform: uppercase;
  opacity: .62; margin-top: 8px;
  text-shadow: 0 1px 10px rgba(24, 12, 60, .4);
}
#combo {
  text-align: center; margin-top: 8px;
  font-size: 13px; font-weight: 700; letter-spacing: .14em; text-transform: uppercase;
  color: var(--mint); text-shadow: 0 0 16px rgba(127, 227, 196, .6);
}

.iconBtn {
  pointer-events: auto; appearance: none; border: 0; cursor: pointer;
  width: 42px; height: 42px; border-radius: 50%;
  background: rgba(255, 246, 238, .16);
  backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
  border: 1px solid rgba(255, 246, 238, .2);
  display: grid; place-items: center; color: var(--chalk);
  transition: transform .12s var(--ease), background .16s;
}
.iconBtn svg { width: 21px; height: 21px; fill: currentColor; }
.iconBtn:active { transform: scale(.9); background: rgba(255, 246, 238, .26); }

#pauseBtn { position: absolute; right: 0; top: 2px; }

#hint {
  position: absolute; bottom: calc(var(--safe-b) + 16px); left: 0; right: 0;
  text-align: center; pointer-events: none; z-index: 4;
  font-size: 11.5px; font-weight: 700; letter-spacing: .28em; text-transform: uppercase;
  opacity: .5; text-shadow: 0 1px 10px rgba(24, 12, 60, .5);
  transition: opacity .4s ease;
}
#hint.dim { opacity: .26; }

/* The two seats, named over their own half. A challenge against a ghost has
   the bottom of the screen to itself, so the names sit there; a live versus
   match has the cheer row along that edge, and the names move up under the
   score bar rather than underneath the thumbs. */
#splitNames {
  position: absolute; left: 0; right: auto;
  bottom: calc(var(--safe-b) + 46px); z-index: 4; pointer-events: none;
  display: flex; justify-content: space-between;
  font-size: 11.5px; font-weight: 700; letter-spacing: .18em; text-transform: uppercase;
  opacity: .7;
}
#splitNames.top {
  bottom: auto;
  /* Clear of the bar whatever the window is: the bar is a fixed stack of
     pixels, so a percentage below a floor would run back into it on a short
     screen, which is exactly a phone laid on its side. */
  top: max(calc(var(--safe-t) + 90px), 17.5vh);
}
#splitNames span {
  flex: 1 1 0; min-width: 0; text-align: center; padding: 0 10px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  text-shadow: 0 1px 10px rgba(24, 12, 60, .6);
}

/* ------------------------------------------------------------- start screen */

#start {
  position: absolute; inset: 0; z-index: 6;
  display: flex; flex-direction: column; align-items: center; justify-content: flex-end;
  padding: 0 24px calc(var(--safe-b) + 26px);
  text-align: center;
}
#wordmark {
  position: absolute; top: calc(var(--safe-t) + 11vh);
  display: flex; gap: .02em;
  font-size: clamp(52px, 16vw, 96px); font-weight: 800;
  letter-spacing: .1em; line-height: 1;
  text-shadow: 0 6px 30px rgba(24, 12, 60, .4);
}
#wordmark span { display: inline-block; animation: rise .7s var(--ease) backwards; }
#wordmark span:nth-child(1) { animation-delay: .02s; }
#wordmark span:nth-child(2) { animation-delay: .08s; }
#wordmark span:nth-child(3) { animation-delay: .14s; }
#wordmark span:nth-child(4) { animation-delay: .2s; }
#wordmark span:nth-child(5) { animation-delay: .26s; }
@keyframes rise { from { opacity: 0; transform: translateY(22px); } to { opacity: 1; transform: none; } }

#tagline {
  position: absolute; top: calc(var(--safe-t) + 11vh + clamp(62px, 19vw, 112px));
  margin: 0; font-size: 14px; font-weight: 500; opacity: .68; letter-spacing: .01em;
}

#startBottom { display: flex; flex-direction: column; gap: 12px; width: 100%; max-width: 340px; }
#startBest { font-size: 12px; font-weight: 700; letter-spacing: .2em; text-transform: uppercase; opacity: .6; margin-bottom: 2px; }

.pill {
  appearance: none; cursor: pointer; border: 0;
  font-family: inherit; font-size: 16px; font-weight: 700; letter-spacing: .01em;
  padding: 15px 22px; border-radius: 999px; color: var(--chalk);
  background: rgba(255, 246, 238, .17);
  border: 1px solid rgba(255, 246, 238, .22);
  backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px);
  transition: transform .12s var(--ease), background .18s, filter .18s;
  white-space: nowrap;
}
.pill:active { transform: scale(.955); }
.pill.primary {
  background: var(--chalk); color: var(--night);
  font-weight: 800; font-size: 17px; padding: 17px 26px;
  box-shadow: 0 10px 30px rgba(12, 6, 40, .34);
}
.pill.ghost { background: rgba(255, 246, 238, .09); font-weight: 600; font-size: 14px; padding: 12px 18px; }
.pill.wide { flex: 1 1 0; min-width: 0; padding-left: 10px; padding-right: 10px; font-size: 15px; }
.row { display: flex; gap: 10px; }

#playBtn { animation: breathe 2.4s ease-in-out infinite; }
@keyframes breathe { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.035); } }
#playBtn:active { animation: none; transform: scale(.955); }

/* ------------------------------------------------------------------ sheets */

/* No backdrop-filter here. A blur over the whole screen makes the browser
   read the live canvas back every frame, which measured as the single most
   expensive thing on the page. The dim and the opaque sheet read the same. */
.scrim {
  position: absolute; inset: 0; z-index: 20;
  display: flex; align-items: center; justify-content: center;
  background: rgba(12, 8, 36, .22);
  opacity: 0; transition: opacity .26s ease;
}
.scrim.bottom { align-items: flex-end; }
.scrim.on { opacity: 1; }

.sheet {
  width: 100%; max-width: 460px; margin: 0 12px calc(var(--safe-b) + 12px);
  padding: 22px 20px 20px;
  background: linear-gradient(180deg, #2E2468, #1C1544);
  border: 1px solid rgba(255, 246, 238, .14);
  border-radius: 28px;
  box-shadow: 0 26px 70px rgba(8, 4, 28, .6);
  display: flex; flex-direction: column; gap: 11px;
  transform: translateY(26px) scale(.98); opacity: 0;
  transition: transform .36s var(--ease), opacity .28s ease;
}
.scrim.on .sheet { transform: none; opacity: 1; }
.sheet.tall { max-height: 88vh; max-height: 88dvh; overflow-y: auto; -webkit-overflow-scrolling: touch; }

.sheet h2 { margin: 0 0 4px; font-size: 22px; font-weight: 800; letter-spacing: -.01em; }
.sheetHead { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.closeBtn { width: 36px; height: 36px; flex: 0 0 auto; }
.closeBtn svg { width: 17px; height: 17px; }

/* --------------------------------------------------------------- game over */

#newBest {
  align-self: center;
  background: var(--mint); color: #10352b;
  font-size: 12.5px; font-weight: 800; letter-spacing: .16em;
  padding: 7px 15px; border-radius: 999px;
  box-shadow: 0 0 30px rgba(127, 227, 196, .55);
  animation: badgePop .5s var(--ease);
}
@keyframes badgePop { 0% { transform: scale(.4); opacity: 0; } 60% { transform: scale(1.1); } 100% { transform: scale(1); opacity: 1; } }

#finalScore {
  text-align: center; font-size: clamp(64px, 21vw, 104px); font-weight: 800;
  line-height: .88; letter-spacing: -.04em; font-variant-numeric: tabular-nums;
  margin-top: 4px;
}
#finalMeta {
  text-align: center; font-size: 13px; font-weight: 600; letter-spacing: .12em;
  text-transform: uppercase; opacity: .58; margin-bottom: 4px;
}
#rankLine {
  text-align: center; font-size: 14px; font-weight: 600; color: var(--mint);
  margin: -2px 0 6px;
}

/* ------------------------------------------------------------ leaderboard */

.tabs {
  display: flex; gap: 6px; padding: 4px; border-radius: 999px;
  background: rgba(255, 246, 238, .09); margin-bottom: 4px;
}
.tabs button {
  flex: 1 1 0; appearance: none; border: 0; cursor: pointer; font-family: inherit;
  background: transparent; color: var(--chalk); opacity: .6;
  font-size: 13.5px; font-weight: 700; padding: 9px 4px; border-radius: 999px;
  transition: background .2s, opacity .2s, color .2s;
  white-space: nowrap;
}
.tabs button.on { background: var(--chalk); color: var(--night); opacity: 1; }

/* flex: none matters. As a shrinking flex item inside the sheet, a board
   taller than the space left was squeezed to a shorter box than its content
   and the rows spilled out over the note below them. */
#boardBody, #overBoard { display: flex; flex: none; flex-direction: column; gap: 2px; min-height: 90px; }

.lbRow {
  display: flex; align-items: center; gap: 11px;
  padding: 9px 11px; border-radius: 14px;
  font-size: 15px;
}
.lbRow .rk { flex: 0 0 32px; font-size: 13.5px; font-weight: 800; opacity: .5; font-variant-numeric: tabular-nums; }
.lbRow .av {
  flex: 0 0 32px; width: 32px; height: 32px; border-radius: 50%;
  display: grid; place-items: center; font-size: 12.5px; font-weight: 800; color: #17143a;
  letter-spacing: -.02em;
}
.lbRow .nm { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600; }
.lbRow .nm a { color: inherit; text-decoration: none; }
.lbRow .sc { flex: 0 0 auto; font-weight: 800; font-variant-numeric: tabular-nums; }
.lbRow.you {
  background: rgba(127, 227, 196, .16);
  box-shadow: inset 0 0 0 1px rgba(127, 227, 196, .42);
}
.lbRow.you .rk, .lbRow.you .sc { color: var(--mint); opacity: 1; }
.lbRow.me { background: rgba(255, 246, 238, .07); }
.lbSplit { height: 1px; background: rgba(255, 246, 238, .13); margin: 6px 4px; }
.lbMsg { text-align: center; opacity: .55; font-size: 13.5px; padding: 22px 8px; font-weight: 500; }
#boardNote { font-size: 11.5px; opacity: .38; text-align: center; margin: 8px 0 0; line-height: 1.4; }

/* ---------------------------------------------------------- versus / lobby */

#lobbyBody { display: flex; flex: none; flex-direction: column; gap: 11px; }
.lobbyNote { font-size: 13.5px; opacity: .68; line-height: 1.5; margin: 0; }
.codeBox {
  display: flex; align-items: center; gap: 10px; justify-content: space-between;
  background: rgba(255, 246, 238, .09); border-radius: 16px; padding: 13px 16px;
}
.codeBox .code { font-size: 19px; font-weight: 800; letter-spacing: .06em; }
.codeBox .lbl { font-size: 11px; letter-spacing: .2em; text-transform: uppercase; opacity: .55; }
.waiting { display: flex; align-items: center; gap: 10px; font-size: 14.5px; opacity: .8; }
.dotPulse { width: 9px; height: 9px; border-radius: 50%; background: var(--mint); animation: blink 1.2s ease-in-out infinite; }
@keyframes blink { 0%, 100% { opacity: .25; transform: scale(.8); } 50% { opacity: 1; transform: scale(1); } }

/* The bar spans the same width the two panes are laid out in, so each name
   sits over its own seat rather than over the middle of the screen. */
#versusBar {
  position: absolute; top: calc(var(--safe-t) + 10px); left: 0; right: 0; z-index: 5;
  max-width: 980px; margin: 0 auto; padding: 0 16px; pointer-events: none;
}
.vsSide { display: flex; flex: 1 1 0; align-items: center; gap: 10px; min-width: 0; }
.vsSide.right { justify-content: flex-end; }
.vsStack { display: flex; flex-direction: column; min-width: 0; }
.vsStack.right { align-items: flex-end; }
/* The disc is drawn from the name itself, because a name is the only thing the
   SDK hands a page: there is no picture to fetch and no address to build one
   from. One letter, a hue derived from the same name, and the two seats are
   told apart at a glance at the size a phone draws them. */
.vsAv {
  flex: 0 0 auto; width: 44px; height: 44px; border-radius: 50%;
  display: grid; place-items: center;
  font-size: 17px; font-weight: 800; color: var(--night);
  background: var(--mint);
  box-shadow: 0 3px 12px rgba(12, 6, 40, .35);
}
.vsAv.them { background: var(--coral); }
.vsMark {
  flex: 0 0 auto; font-size: 13px; font-weight: 800; letter-spacing: .18em;
  opacity: .55; padding: 0 4px;
}
.vsName { font-size: 12px; font-weight: 700; letter-spacing: .14em; text-transform: uppercase; opacity: .68; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }
.vsScore { font-size: 30px; font-weight: 800; font-variant-numeric: tabular-nums; letter-spacing: -.03em; line-height: 1; }
#vsLead {
  height: 6px; border-radius: 999px; margin: 7px 0 2px;
  background: rgba(255, 246, 238, .18); overflow: hidden;
}
#vsLeadFill {
  height: 100%; width: 50%; border-radius: 999px;
  background: var(--mint); transition: width .3s var(--ease), background .3s;
}
#versusTop { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }

/* Each toast is placed against the pane it belongs to, so the container is a
   bare overlay and the position is written on the toast itself. */
#toasts { position: absolute; inset: 0; z-index: 6; pointer-events: none; }
.toast {
  position: absolute; transform: translateX(-50%);
  font-size: 13.5px; font-weight: 700; padding: 8px 15px; border-radius: 999px;
  background: rgba(20, 14, 52, .8); border: 1px solid rgba(255, 246, 238, .16);
  white-space: nowrap; max-width: 76vw;
  overflow: hidden; text-overflow: ellipsis;
  animation: toastIn .34s var(--ease), toastOut .4s ease 2.3s forwards;
}
.toast.bad { color: var(--coral); }
.toast.good { color: var(--mint); }
/* The centring lives in the keyframes too: a toast that animates its own
   transform would otherwise jump half its width sideways on the first frame. */
@keyframes toastIn { from { opacity: 0; transform: translate(-50%, 10px) scale(.94); } to { opacity: 1; transform: translate(-50%, 0); } }
@keyframes toastOut { to { opacity: 0; transform: translate(-50%, -8px); } }

#cheerBar {
  position: absolute; bottom: calc(var(--safe-b) + 14px); left: 0; right: 0; z-index: 6;
  display: flex; justify-content: center; gap: 10px;
}
.cheer {
  appearance: none; border: 0; cursor: pointer; font-size: 22px; line-height: 1;
  width: 46px; height: 46px; border-radius: 50%;
  background: rgba(255, 246, 238, .13); border: 1px solid rgba(255, 246, 238, .18);
  -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
  transition: transform .12s var(--ease);
}
.cheer:active { transform: scale(.86); }
.cheerPop {
  position: absolute; bottom: calc(var(--safe-b) + 68px);
  font-size: 26px; animation: cheerUp 1.5s ease-out forwards; pointer-events: none;
}
@keyframes cheerUp { from { opacity: 1; transform: translateY(0) scale(.7); } to { opacity: 0; transform: translateY(-70px) scale(1.25); } }

#challResult {
  text-align: center; font-size: 14.5px; font-weight: 700;
  padding: 10px 14px; border-radius: 16px;
  background: rgba(255, 246, 238, .1);
  border: 1px solid rgba(255, 246, 238, .14);
}

.vsNote { text-align: center; }
/* The room's own sentence, kept word for word. It can be a whole exception,
   so it wraps, it is selectable so it can be copied out, and the left rule
   marks it as quoted rather than as something this page is saying. */
.vsRoomSaid {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12px; opacity: .82; line-height: 1.45; margin: 0;
  padding: 8px 0 8px 11px; border-left: 3px solid rgba(255, 214, 168, .55);
  overflow-wrap: anywhere; user-select: text; -webkit-user-select: text;
}
.vsTo { font-size: .58em; font-weight: 700; opacity: .58; }
.vsFinal {
  text-align: center; font-size: 46px; font-weight: 800;
  letter-spacing: -.03em; font-variant-numeric: tabular-nums;
  margin: 6px 0 2px;
}

.nameInput {
  appearance: none; width: 100%; font-family: inherit;
  font-size: 17px; font-weight: 700; text-align: center;
  color: var(--chalk); background: rgba(255, 246, 238, .1);
  border: 1px solid rgba(255, 246, 238, .22);
  border-radius: 14px; padding: 14px 16px;
}
.nameInput:focus { outline: 2px solid var(--mint); outline-offset: 1px; }

/* ----------------------------------------------------------- floaters */

#floaters {
  position: absolute; inset: 0; z-index: 7; pointer-events: none; overflow: hidden;
}
/* The reward words rise off the top face, and for the first third of a second
   they are over the pale glaze of the slab they just landed on. A glow alone
   disappears into that; the outline is what keeps them legible over both the
   slab and the sky. */
.floater {
  position: absolute; transform: translate(-50%, -50%);
  display: flex; align-items: center; gap: 9px;
  font-size: 27px; font-weight: 800; letter-spacing: .005em;
  white-space: nowrap;
}
.flWord, .flTail {
  color: var(--chalk);
  text-shadow:
    -2px -2px 0 rgba(24, 12, 60, .7), 2px -2px 0 rgba(24, 12, 60, .7),
    -2px 2px 0 rgba(24, 12, 60, .7), 2px 2px 0 rgba(24, 12, 60, .7),
    0 0 14px rgba(24, 12, 60, .55), 0 5px 18px rgba(24, 12, 60, .5);
}
/* The count is the part that has to land at a glance, so it is the one piece
   that does not sit on the sky: a mint pill with the tower's own deep navy in
   it reads at any height, over any slab, against either end of the gradient. */
.flPill {
  color: var(--night); background: var(--mint);
  font-size: 21px; border-radius: 999px; padding: 2px 13px 4px;
  box-shadow: 0 2px 0 rgba(24, 12, 60, .35), 0 0 20px rgba(127, 227, 196, .75);
}
.flTail {
  font-size: 19px; color: var(--mint); opacity: .95;
  text-shadow:
    -2px -2px 0 rgba(24, 12, 60, .7), 2px -2px 0 rgba(24, 12, 60, .7),
    -2px 2px 0 rgba(24, 12, 60, .7), 2px 2px 0 rgba(24, 12, 60, .7);
}
/* The rise is a class, not the element's own style, so a combo label that is
   already on screen can be told to start it again from the top. */
.floater.run { animation: floatUp .95s var(--ease) forwards; }
.floater.mint .flWord { color: var(--mint); }
@keyframes floatUp {
  0% { opacity: 0; transform: translate(-50%, -50%) scale(.7); }
  18% { opacity: 1; transform: translate(-50%, -62%) scale(1.08); }
  100% { opacity: 0; transform: translate(-50%, -190%) scale(1); }
}

/* -------------------------------------------------------------- responsive */

@media (min-width: 620px) {
  :root { --col: 520px; }
  .sheet { margin-bottom: 28px; }
}
/* A short window is a laptop with the browser chrome folded away, or a phone
   laid on its side. The start block is the same three pieces as anywhere else,
   but at this height they would eat two thirds of the screen and leave the idle
   tower a stripe to stand in, so every part of it is tightened at once. */
@media (max-height: 620px) {
  #start { padding-bottom: calc(var(--safe-b) + 14px); }
  #wordmark { top: calc(var(--safe-t) + 6vh); font-size: clamp(36px, 11vw, 58px); }
  #tagline { top: calc(var(--safe-t) + 6vh + clamp(42px, 13vw, 66px)); }
  #startBottom { gap: 9px; }
  #startBottom .pill.primary { padding: 13px 24px; font-size: 16px; }
  #startBottom .pill { padding: 11px 20px; font-size: 15px; }
  #score { font-size: clamp(42px, 12vw, 70px); }
  .sheet.tall { max-height: 94vh; }
}

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