/* Back to Brawl — fullscreen inbox.
 *
 * Tokens, fonts and component recipes are the official inbox's own, read out of
 * its stylesheets rather than eyeballed. Note the body face: the site declares
 * Nunito *SemiBold* under the family name `nunito` at weight 400, so a plain
 * Nunito 400 renders noticeably lighter than the real thing. */

@font-face {
  font-family: lilita-one; font-style: normal; font-weight: 400; font-display: swap;
  src: url("assets/LilitaOne-Regular.woff2") format("woff2"),
       url("assets/LilitaOne-Regular.woff")  format("woff");
}
/* Supercell ships Nunito subset to Latin only — 0 of the 256 code points in
 * the Cyrillic block — so Russian body text fell out of the family entirely and
 * was rendered by whatever system face the WebView reached for. That is why it
 * looked nothing like the rest of the page. These are the same typeface at the
 * same weight, just the subsets they left out; unicode-range keeps their own
 * file serving Latin and only sends Cyrillic to the added ones. */
@font-face {
  font-family: nunito; font-style: normal; font-weight: 400; font-display: swap;
  src: url("assets/Nunito-SemiBold.woff2") format("woff2"),
       url("assets/Nunito-SemiBold.woff")  format("woff");
}
@font-face {
  font-family: nunito; font-style: normal; font-weight: 400; font-display: swap;
  src: url("assets/Nunito-SemiBold-cyrillic.woff2") format("woff2");
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: nunito; font-style: normal; font-weight: 400; font-display: swap;
  src: url("assets/Nunito-SemiBold-cyrillic-ext.woff2") format("woff2");
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
@font-face {
  font-family: "Pusia Bold"; font-style: normal; font-weight: 400; font-display: swap;
  src: url("assets/Pusia-Bold.woff2") format("woff2"),
       url("assets/Pusia-Bold.woff")  format("woff");
}

:root {
  --stage-width: 1024;
  --blue:        #0252cc;
  --blue-deep:   #013a94;
  --blue-lift:   #0a63e8;
  --ink:         #14041a;
  --card:        #1b2740;
  --card-lift:   #24334f;
  --line:        #ffffff1f;
  --text:        #ffffff;
  --text-dim:    #b2bdcc;
  --gold:        #ffc83d;
  --gold-deep:   #d99400;
  --green:       #2ecc71;
  --green-deep:  #1e8b4d;
  --red:         #e8443a;

  --font-title: lilita-one, sans-serif;
  --font-body:  nunito, sans-serif;
  --font-title-weight: normal;
  --stroke: .10460251046025104rem;   /* replaced per language by applyTitleFont */
  --line-height-tight: 1;
  --line-height-normal: 1.1475;
  --content-max-width: 1024px;

  /* the site's own palette */
  --brawl-black: #000;
  --brawl-navy: #101560;
  --brawl-white: #fff;
  --brawl-orange: #ffab18;
  --brawl-gray: #353c4e;
  --brawl-dark-gray: #1f1f1f;
  --brawl-red: #f11013;

  --safe-top: 0px;
  --safe-bottom: 0px;
  --safe-left: 0px;
  --safe-right: 0px;

  --nav-h: 2.9rem;
  /* the torn paper edge is exactly this tall in the original */
  --paper-h: 6.69456rem;
}

* {
  box-sizing: border-box; margin: 0; padding: 0;
  -webkit-tap-highlight-color: transparent;
  /* No selection, no blue highlight, and no iOS long-press callout — this is a
   * game screen, not a document. The voucher field opts back in below. */
  -webkit-user-select: none; user-select: none;
  -webkit-touch-callout: none;
}
img, svg { -webkit-user-drag: none; user-drag: none; pointer-events: none; }

/* The original pins the document itself so nothing can be dragged past the
 * artwork: html and body are both `overflow: hidden; overscroll-behavior: none`
 * and only the inner column scrolls. We had it on body alone, which still left
 * the document free to rubber-band on iOS. */
html {
  font-size: clamp(13px, calc(100vw / var(--stage-width) * 16), 26px);
  width: 100%;
  height: 100vh;
  overflow: hidden;
  overscroll-behavior: none;
}

/* The page is a fixed screen with one internal scroller. Letting the document
 * itself scroll lets iOS rubber-band past the ends, which exposes the ground
 * colour above the artwork — fine for a web page, wrong for a game screen. */
body {
  height: 100%;
  overflow: hidden;
  overscroll-behavior: none;
  font-family: var(--font-body);
  color: var(--text);
  /* What a fling exposes. Momentum carries on after the finger lifts, so the
   * touch guard cannot catch it and the strip at the end of a fast scroll shows
   * whatever is behind the scroller. Painting it in the colours of the two ends
   * — the event ground above, the footer's purple below — means the strip reads
   * as more of the page instead of a bare slab. */
  background: linear-gradient(180deg,
    #491c6c 0%, #491c6c 48%,
    #321593 52%, #321593 100%);
}

/* Nothing is shown until the page says it is finished.
 *
 * The native side decides when to reveal the webview, and it has more than one
 * way to get there — the page asking, or its own timer — so the page cannot
 * rely on being revealed at exactly the right moment. Instead it hides its own
 * content until the artwork has loaded and the sections have been filled in.
 * The body's gradient still paints, so an early reveal shows a clean ground in
 * the page's own colours rather than a half-built layout with no tab bar, no
 * cards and no torn edges. */
html:not(.ready) body > *,
html:not(.ready) .article-view {
  visibility: hidden;
}

/* the drifting pattern each section lays over its ground — the original's
 * _movePattern, verbatim: translate(-5.23013rem) to translateY(-15.6904rem). */
@keyframes movePattern {
  0%   { transform: translate3d(-5.23013rem, 0, 0); }
  100% { transform: translate3d(0, -15.6904rem, 0); }
}

/* the sliver of near-black behind the notch, as on the official page */
.top-bar {
  position: fixed; inset: 0 0 auto 0; z-index: 1;
  height: calc(var(--safe-top) + .84rem);
  background: var(--ink);
  pointer-events: none;
}

h1, h2, h3, .title {
  font-family: var(--font-title);
  font-weight: var(--font-title-weight);
  letter-spacing: .01em;
  text-transform: uppercase;
  -webkit-text-stroke: var(--stroke) var(--ink);
  paint-order: stroke fill;
  text-shadow: 0 .12rem 0 #00000059;
}

/* ── navigation ───────────────────────────────────────────────────────────── */

.nav {
  position: fixed; z-index: 100;
  top: 0; left: 0; right: 0;
  padding-top: calc(var(--safe-top) + .5rem);
  padding-left: calc(var(--safe-left) + .6rem);
  padding-right: calc(var(--safe-right) + .6rem);
  display: flex; align-items: flex-start; justify-content: center;
  gap: .7rem;
  pointer-events: none;
}
.nav > * { pointer-events: auto; }

/* Both nav buttons are wide angled plates that deliberately hang off the
 * screen corner — the art only reads correctly when it is clipped that way,
 * which is why the arrow sits at one far edge of the file. Geometry is the
 * original's, converted from its 1024pt stage (1pt = .0625rem):
 *   width 221pt, height .409502 x 221pt, top -28pt, inset -129pt. */
.icon-btn {
  position: fixed; z-index: 200;
  width: 13.81rem; height: 5.66rem;
  top: calc(var(--safe-top) - 1.75rem);
  border: 0; outline: none; cursor: pointer;
  background: no-repeat center / 100% 100%;
  transition: transform .12s ease, filter .12s ease;
}
.icon-btn > * { display: none; }
.icon-btn[hidden] { display: none; }
#back-button {
  left: calc(var(--safe-left) - 8.06rem);
  background-image: url("assets/nav-back.svg");
}
#home-button {
  right: calc(var(--safe-right) - 8.06rem);
  background-image: url("assets/nav-home.svg");
}

@keyframes tabBarSlideIn {
  from { opacity: 0; transform: translateY(-100%); }
  to   { opacity: 1; transform: none; }
}

.tabs {
  display: flex; align-items: center; gap: 1.1rem;
  animation: tabBarSlideIn .3s ease-out .75s both;
  padding: .3rem .55rem;
  border-radius: .8rem;
  background: #0b1a3ba6;
  box-shadow: inset 0 0 0 .08rem var(--line), 0 .2rem .6rem #00000040;
  backdrop-filter: blur(.4rem);
}

.tab {
  position: relative;
  display: inline-flex; align-items: center; gap: .41841rem;
  height: 2.30126rem; padding: 0 1.22315rem;
  border: 0; outline: none;
  font-family: var(--font-title); font-size: .82rem; text-transform: uppercase;
  color: var(--text-dim);
  background: url("assets/btn-middle-grey.webp") no-repeat center / 100% 100%;
  cursor: pointer;
  transition: color .18s ease, transform .12s ease;
  white-space: nowrap;
}
/* left and right caps, sized and offset exactly as the original */
.tab::before, .tab::after {
  content: ""; position: absolute; top: 0;
  width: .490934rem; height: 2.30126rem;
  background-repeat: no-repeat; background-size: 100% 100%;
}
.tab::before { left: -.438633rem;  background-image: url("assets/btn-left-grey.webp"); }
.tab::after  { right: -.438633rem; background-image: url("assets/btn-right-grey.webp"); }
.tab .tab-icon { width: 1.05rem; height: 1.05rem; object-fit: contain; filter: drop-shadow(0 .06rem 0 #0006); }
.tab .dot {
  width: .42rem; height: .42rem; border-radius: 50%;
  background: var(--red); box-shadow: 0 0 0 .1rem #00000047;
}
.tab .dot[hidden] { display: none; }
.tab[hidden] { display: none; }
.tab.active { color: #fff; background-image: url("assets/btn-middle-gold.webp"); }
.tab.active::before { background-image: url("assets/btn-left-gold.webp"); }
.tab.active::after  { background-image: url("assets/btn-right-gold.webp"); }

/* ── layout ───────────────────────────────────────────────────────────────── */

.scroll {
  position: relative; z-index: 1;
  height: 100%;
  overflow-y: auto; overflow-x: hidden;
  /* the official page hides its scrollbar the same two ways */
  scrollbar-width: none;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  padding: 0 0 var(--safe-bottom);
}

.scroll::-webkit-scrollbar,
.article-view::-webkit-scrollbar { display: none; }

@keyframes rise { from { opacity: 0; transform: translateY(.7rem); } to { opacity: 1; transform: none; } }

.section-title { display: none; }

.grid {
  display: grid; gap: 1.04603rem;
  grid-template-columns: repeat(3, minmax(0, 1fr));
}
@media (max-width: 44rem) { .grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 28rem) { .grid { grid-template-columns: 1fr; } }

/* ── cards ────────────────────────────────────────────────────────────────── */

.card {
  position: relative;
  display: flex; flex-direction: column;
  border: solid .104603rem var(--brawl-black);
  border-radius: .209205rem;
  background: linear-gradient(180deg, #1a2170 0%, var(--brawl-navy) 100%);
  box-shadow: inset 0 .12rem 0 #ffffff1a, 0 .104603rem #04040466;
  cursor: pointer;
  transition: transform .14s ease, filter .14s ease;
}
.card::after {
  content: ""; position: absolute; left: 6%; right: 6%; bottom: -.42rem;
  height: .7rem; z-index: -1;
  background: url("assets/eightbit-shadow.svg") no-repeat center / 100% 100%;
  pointer-events: none;
}
.card.pressed { filter: brightness(1.08); }
.card.wide { grid-column: 1 / -1; }

.card .thumb {
  aspect-ratio: 16 / 9; width: 100%; max-height: 8.5rem;
  border-radius: .104603rem .104603rem 0 0; overflow: hidden;
  background: linear-gradient(135deg, #35507f, #1d2b47) center/cover no-repeat;
  display: grid; place-items: center;
}
.card.wide .thumb { aspect-ratio: 24 / 7; max-height: 9.5rem; }
.card .thumb .glyph { font-size: 2.6rem; filter: drop-shadow(0 .15rem .2rem #0006); }

.card .body { display: flex; flex-direction: column; flex: 1; padding: .73rem .84rem .84rem; }
.card .kicker {
  /* .body is a flex column, so an inline-block would stretch to full width */
  display: inline-block; align-self: flex-start; margin-bottom: .35rem;
  font-family: var(--font-title); font-size: .62rem; letter-spacing: .06em;
  text-transform: uppercase; color: var(--brawl-black);
  background: var(--brawl-orange); border-radius: .209205rem; padding: .14rem .45rem;
}
.card h3 { font-size: 1.05rem; line-height: var(--line-height-tight); margin-bottom: .34rem; }
.card p { font-size: .8rem; line-height: var(--line-height-normal); color: #dbe1ff; }
.card .meta { margin-top: auto; padding-top: .5rem; font-size: .7rem; color: #b3bbe8; }

/* ── misc ─────────────────────────────────────────────────────────────────── */

.note {
  margin-top: 1.6rem; padding: .6rem .8rem;
  border-radius: .55rem; border: .08rem dashed #ffffff33;
  font-size: .72rem; color: #cbd8ea;
}
.empty { padding: 2.5rem 1rem; text-align: center; color: #b8c6db; font-size: .88rem; }


/* ── sections ───────────────────────────────────────────
 * The page is one continuous scroll and the sections are joined exactly the
 * three ways the official site joins its own:
 *
 *   1. the first section just starts — there is nothing above it to tear from;
 *   2. a "torn" section drops the site's own jagged Seperator svg over the
 *      join and slides up under it, so the ground above shows through the
 *      crack (_creatorsContainer_ + _newsSection_ in the original);
 *   3. the paper section pulls a torn paper edge up over the section above,
 *      lays a paper texture under its content and closes the page with the
 *      matching bottom edge (_esportsContainer_).
 *
 * Every number below is the original's, unconverted. */
.section {
  position: relative; z-index: 1;
  display: flex; flex-direction: column;
  width: 100vw; margin-left: calc(50% - 50vw);
  scroll-margin-top: calc(var(--safe-top) + var(--nav-h));
}

/* the ground: colour, pattern and content, clipped so the drifting pattern
 * stays inside it */
.section > .ground {
  position: relative;
  width: 100%;
  padding: 0 calc(var(--safe-right) + 1rem) 4.70711rem calc(var(--safe-left) + 1rem);
  overflow: hidden;
}
.section > .ground > .inner {
  position: relative; z-index: 1;
  max-width: var(--content-max-width); margin: 0 auto;
}
.section:first-child > .ground { padding-top: calc(var(--safe-top) + var(--nav-h) + 1rem); }

/* The original's own recipe: an oversized tiled layer translated inside a
 * clipping parent. An earlier version animated background-position instead, to
 * avoid needing something to clip it — but that repaints a full-screen tiled
 * layer every frame, on all four sections at once, which is what dragged the
 * page down to a crawl. .ground already clips (overflow: hidden) and the torn
 * edges live on .section outside it, so the oversized layer is safe here. */
.section-bg {
  position: absolute; top: 0; left: 0; z-index: 0; pointer-events: none;
  width: calc(100vw + 10.4603rem);
  height: calc(100% + 20.9205rem);
  background-repeat: repeat; background-size: 20.9205rem;
  animation: movePattern 30s linear infinite;
  will-change: transform;
}

/* ─ join 2: the crack ─ */
.section.torn { margin-top: -4.96862rem; }
/* the ground slides up under the crack, so its content has to start below it —
 * the original buys that room with its grid's 3.76569rem top padding */
.section.torn > .ground { padding-top: 3.76569rem; }
.section.paper > .ground { padding-top: 1.25523rem; }
.section.torn > .separator {
  position: relative; z-index: 1;
  width: 100%; height: 4.70711rem;
  margin-bottom: -2.92887rem;
  background: url("assets/separator.svg") 50% / 100% 100% no-repeat;
  pointer-events: none;
}

/* ─ join 3: the paper ─ */
.section.paper { margin-top: 0; }
.section.paper::before, .section.paper::after {
  content: ""; display: block;
  width: 100%; height: 6.69456rem;
  background-position: 50%; background-repeat: no-repeat; background-size: cover;
}
.section.paper::before {
  position: relative; z-index: 1;
  margin-top: -6.69456rem;
  background-image: url("assets/paper-top.webp");
}
.section.paper::after {
  margin-top: -1px;
  background-image: url("assets/paper-bottom.webp");
}
.section.paper > .ground {
  /* the paper body itself — no ground colour and no pattern under it */
  background: url("assets/paper-middle.jpg") repeat;
  background-size: 10.4603rem 10.4603rem;
  padding-bottom: 1.04603rem;
}

/* the section above a join leaves room for the join to bite into */
.section.event > .ground    { padding-bottom: 4.70711rem; }
.section.news > .ground     { padding-bottom: 4.70711rem; }
.section.community > .ground{ padding-bottom: 7.84519rem; }

.section.event     > .ground { background: radial-gradient(60% 60%, #7a2bd6 0%, #40156e 100%); }
.section.news      > .ground { background: radial-gradient(50% 50%, #5e28ff 0%, #321593 100%); }
.section.community > .ground { background: radial-gradient(50% 50%, #194c2a 0%, #163428 50%, #131b25 100%); }

.section.event     .section-bg { background-image: url("assets/skullpattern-02.svg"); }
.section.news      .section-bg { background-image: url("assets/skullpattern-05.svg"); }
.section.community .section-bg { background-image: url("assets/creatorspattern-05.svg"); }

/* Creators and Esports carry the site's own logo art over their ground; News
 * carries nothing, exactly as on the official page. */
.section-logo {
  position: relative; z-index: 2;
  display: block; margin: 0 auto 1.1rem;
  height: auto;
}
.section-logo.creators { width: 10.9833rem; }
.section-logo.bsc { width: 5.23013rem; }

/* the strip the tab bar sits on, as on the official page */
.nav::before {
  content: ""; position: fixed; z-index: -1;
  top: 0; left: 0; width: 100vw;
  height: calc(var(--safe-top) + 2.6rem);
  background: linear-gradient(#14041ad9, #14041a00);
  pointer-events: none;
}

/* article view: the page owns the screen, tabs step aside for the back button */
body[data-view="article"] .tabs { display: none; }

/* the esports ground is paper, so anything unstyled on it reads as ink */
.section.paper .note { color: #3a3325; border-color: #00000038; }

/* ── community event ──────────────────────────────────────────────────────
 * The server's own community-event.json, drawn in the site's idiom: hero art
 * behind the title, one bar for the shared kill count and the milestone
 * rewards laid out along it. */

.section.event > .ground {
  background:
    linear-gradient(#40156ecc 0%, #40156e66 45%, #40156e 100%),
    url("assets/event/charlie-bg.jpg") 50% 18% / cover no-repeat,
    radial-gradient(60% 60%, #7a2bd6 0%, #40156e 100%);
}
/* the art already carries the scene — a pattern on top of it just muddies it */
.section.event .section-bg { display: none; }

.event { display: flex; flex-direction: column; align-items: center; }

.event-hero { text-align: center; padding-top: 8rem; }
/* the event heading is game art, not a chip: the title face at full size with
 * the same heavy outline the game gives its banners */
.event-title {
  font-family: var(--font-title); font-weight: var(--font-title-weight);
  font-size: 3.1rem; line-height: .95; text-transform: uppercase;
  letter-spacing: .01em;
  color: #fff;
  -webkit-text-stroke: .28rem var(--brawl-black);
  paint-order: stroke fill;
  text-shadow: 0 .16rem 0 #00000073;
  transform: rotate(-2deg);
  margin-bottom: .45rem;
}
@media (max-width: 34rem) { .event-title { font-size: 2.2rem; } }
.event-sub {
  font-family: var(--font-title); text-transform: uppercase;
  font-size: .9rem; letter-spacing: .12em; color: #ffd9f4;
  -webkit-text-stroke: var(--stroke) var(--brawl-black); paint-order: stroke fill;
  text-shadow: 0 .1rem 0 #00000073;
}

/* ── the event track ──────────────────────────────────────────────────────
 * One progression with its rewards pinned along it, the way the game's own
 * community events read. The track is padded by half a badge so the first and
 * last rewards sit fully on screen at 0% and 100%. */
.event-track {
  position: relative;
  width: 100%; max-width: 39rem;
  margin: .5rem auto 0;
  padding: 0 2.3rem;
}

/* Plain CSS, deliberately.
 *
 * Two attempts at building this from the game's own sprites — one sliced out of
 * the ui.sc atlas, one assembled from the fankit's four-piece bar kit — both
 * came out worse than this: the art carries its own margins and proportions,
 * and without the sprite names and layout to go with it, fitting it to a box of
 * our dimensions is guesswork. The extracted files are still in assets/ui/ if
 * that is ever worth another go, offline rather than on the page. */
.event-bar {
  position: relative;
  height: 1.5rem; padding: .18rem;
  border: .104603rem solid var(--brawl-black); border-radius: .8rem;
  background: #180a30;
  box-shadow: inset 0 .2rem .38rem #000000a6, 0 .104603rem #04040466;
}
.event-bar > span {
  display: block; height: 100%; min-width: .9rem;
  border-radius: .62rem;
  background: linear-gradient(180deg, #ffe888 0%, #ffc02e 42%, #ff9b0a 72%, #e2760a 100%);
  box-shadow: inset 0 .12rem 0 #fff8d4, inset 0 -.1rem .2rem #b35c00,
              0 0 .7rem #ffab1873;
  transition: width .6s ease-out;
  position: relative;
}
/* the bright head of the fill, so the eye lands on where the progress is */
.event-bar > span::before {
  content: ""; position: absolute; top: 0; bottom: 0; right: 0;
  width: .55rem;
  background: linear-gradient(90deg, #ffffff00, #fffdf0d9);
  border-radius: .62rem;
}

.event-count {
  /* clear of the spider, who hangs above the badges on her thread */
  margin: .1rem 0 4.7rem; text-align: center;
  font-family: var(--font-title); font-weight: var(--font-title-weight);
  font-size: 1.5rem;
  -webkit-text-stroke: var(--stroke) var(--brawl-black); paint-order: stroke fill;
  text-shadow: 0 .12rem 0 #00000073;
}
.event-count strong { color: #ffd257; display: inline-block; }
.event-count span { color: #cfc6ff; margin-left: .35rem; font-size: .82em; }

/* the rewards, each pinned at the point on the track it is reached */
.milestones {
  position: relative;
  height: 7.1rem; margin: 0; padding: 0;
  list-style: none;
}
.milestone {
  position: absolute; z-index: 3; top: -3.05rem;
  left: var(--at);
  transform: translateX(-50%);
  width: 6.6rem;
  display: flex; flex-direction: column; align-items: center; gap: .22rem;
  text-align: center;
}

/* The reward tile.
 *
 * A rounded square with a heavy black outline and a bevelled inner frame —
 * which is how Brawl Stars draws every icon it puts in a frame; the fankit's
 * own Icons & Buttons set is rounded squares to a one. Circles were the reason
 * this read as a generic progress tracker rather than as the game. The chunky
 * bottom shadow is part of the same grammar: nothing in that UI sits flat. */
.milestone-badge {
  position: relative;
  overflow: hidden;
  display: grid; place-items: center;
  width: 4.6rem; height: 4.6rem;
  border-radius: .85rem;
  border: .2rem solid var(--brawl-black);
  background:
    linear-gradient(180deg, #ffffff2e 0%, #ffffff00 42%),
    linear-gradient(180deg, #39427a 0%, #1b2145 100%);
  box-shadow: 0 .3rem 0 #04040480, inset 0 -.22rem .34rem #00000059;
}
.milestone-art {
  position: relative; z-index: 2;
  display: flex; align-items: center; justify-content: center; gap: .1rem;
  width: 84%; height: 84%;
}
.milestone-art img {
  max-height: 100%; object-fit: contain;
  filter: drop-shadow(0 .1rem .14rem #000000b3);
}
.milestone-art[data-count="1"] img { max-width: 90%; }
.milestone-art[data-count="2"] img { max-width: 50%; }

.milestone-goal {
  font-family: var(--font-title); font-weight: var(--font-title-weight);
  font-size: 1rem; line-height: 1;
  -webkit-text-stroke: var(--stroke) var(--brawl-black); paint-order: stroke fill;
  text-shadow: 0 .12rem 0 #00000073;
}
.milestone-label {
  font-size: .66rem; line-height: 1.15;
  min-height: 2.1em;
}
.milestone-action {
  font-family: var(--font-title); font-weight: var(--font-title-weight);
  font-size: .66rem; letter-spacing: .04em; text-transform: uppercase;
  padding: .14rem .62rem .18rem; border-radius: .55rem;
  box-shadow: inset 0 .0784519rem 0 #ffffff2e, 0 .0784519rem 0 #00000059;
}

/* the glow behind a badge that can be taken */
.milestone-glow {
  position: absolute; inset: -.2rem; z-index: 0;
  border-radius: .85rem; opacity: 0; pointer-events: none;
}
/* the sweep of light behind it, clipped to the badge and faded so the circle
 * never shows a hard edge */
.milestone-rays {
  position: absolute; inset: 0; z-index: 1;
  border-radius: .66rem; overflow: hidden; pointer-events: none;
  -webkit-mask-image: radial-gradient(closest-side, #000 18%, #0009 55%, transparent 92%);
  mask-image: radial-gradient(closest-side, #000 18%, #0009 55%, transparent 92%);
}
/* the tick stamped on a reward already taken */
/* Outside the tile, not inside it: the tile clips its own contents so the rays
 * stay in, and a corner stamp placed within would be clipped with them. */
.milestone-tick {
  position: absolute; z-index: 3;
  top: 3.5rem; right: .78rem;
  width: 1.6rem; height: 1.6rem; border-radius: 50%;
  border: .12rem solid var(--brawl-black);
  background: linear-gradient(180deg, #4be59a, #22c46f);
  display: none;
}
.milestone-tick::after {
  content: ""; position: absolute; left: 28%; top: 46%;
  width: .42rem; height: .22rem;
  border-left: .16rem solid #062b17; border-bottom: .16rem solid #062b17;
  transform: rotate(-45deg);
}

/* ─ locked ─ */
.milestone.locked .milestone-goal   { color: #aab4dc; }
.milestone.locked .milestone-label  { color: #b9c2e8; }
.milestone.locked .milestone-action { background: #39406e; color: #cfd6f5; }

/* ─ ready ─ */
.milestone.ready { cursor: pointer; }
.milestone.ready .milestone-badge {
  background:
    linear-gradient(180deg, #ffffff3d 0%, #ffffff00 42%),
    linear-gradient(180deg, #6a51c4 0%, #33236b 100%);
  box-shadow: 0 .3rem 0 #04040480, inset 0 -.22rem .34rem #00000059,
              inset 0 0 0 .2rem #ffd257;
}
.milestone.ready .milestone-glow {
  box-shadow: 0 0 0 .12rem #ffe9a8, 0 0 1.2rem #ffab188c;
  animation: milestonePulse 1.8s ease-in-out infinite;
  will-change: opacity;
}
.milestone.ready .milestone-art img { animation: prizeFloat 2.6s ease-in-out infinite; }
.milestone.ready .milestone-goal  { color: #ffd257; }
.milestone.ready .milestone-label { color: #eaeeff; }
.milestone.ready .milestone-action {
  background: linear-gradient(180deg, #ffd257 0%, #ffab18 60%, #e28a0a 100%);
  color: #3a2400;
  position: relative; overflow: hidden;
}
.milestone.ready .milestone-action::after {
  content: ""; position: absolute; inset: 0;
  background: #fff6d4; mix-blend-mode: soft-light;
  animation: claimBreathe 1.9s ease-in-out infinite;
  pointer-events: none;
}
.milestone.ready .milestone-rays::after {
  content: ""; position: absolute; inset: -35%;
  background: conic-gradient(from 0deg, #ffd25700 0deg, #ffd25733 22deg, #ffd25700 44deg,
                             #ffd25700 180deg, #ffd25733 202deg, #ffd25700 224deg, #ffd25700 360deg);
  animation: milestoneRays 9s linear infinite;
}

/* ─ sending ─ */
.milestone.sending { cursor: progress; }
.milestone.sending .milestone-goal   { color: #d3daff; }
.milestone.sending .milestone-label  { color: #eaeeff; }
.milestone.sending .milestone-action { background: #4a5182; color: #eef1ff; }

/* ─ claimed ─ */
.milestone.claimed .milestone-badge {
  background:
    linear-gradient(180deg, #ffffff33 0%, #ffffff00 42%),
    linear-gradient(180deg, #2c7a4e 0%, #14401f 100%);
  box-shadow: 0 .3rem 0 #04040480, inset 0 -.22rem .34rem #00000059,
              inset 0 0 0 .2rem #4be59a;
}
.milestone.claimed .milestone-tick { display: block; }
.milestone.claimed .milestone-art img { opacity: .65; }
.milestone.claimed .milestone-goal   { color: #8ff0bb; }
.milestone.claimed .milestone-label  { color: #d8f5e5; }
.milestone.claimed .milestone-action {
  background: linear-gradient(180deg, #4be59a, #35d488); color: #062b17;
}

@keyframes milestonePulse { 0%, 100% { opacity: .45; } 50% { opacity: 1; } }
@keyframes prizeFloat {
  0%, 100% { transform: translateY(0) scale(1); }
  50%      { transform: translateY(-.18rem) scale(1.06); }
}
@keyframes claimBreathe { 0%, 100% { opacity: 0; } 50% { opacity: .75; } }

/* a refused tap — locked, or already taken */
@keyframes milestoneNudge {
  0%, 100% { transform: translateX(-50%); }
  20%      { transform: translateX(calc(-50% - .22rem)); }
  60%      { transform: translateX(calc(-50% + .16rem)); }
}
.milestone.nudge { animation: milestoneNudge .32s ease-in-out; }



/* ── footer ───────────────────────────────────────────────────────────────
 * _footerContainer_ in the original: the purple ground again, pulled up under
 * the paper's bottom edge, with the back-to-top button centred in it. */
.page-footer {
  position: relative; z-index: 0;
  display: flex; align-items: center; justify-content: center;
  width: 100vw; margin-left: calc(50% - 50vw);
  height: 9.51883rem; margin-top: -1.67364rem;
  padding-bottom: var(--safe-bottom);
  background: radial-gradient(50% 50%, #5e28ff 0%, #321593 100%);
  overflow: hidden;
}
.page-footer .section-bg { background-image: url("assets/skullpattern-05.svg"); }

/* the site's 9-slice button art, same three pieces the tabs use */
.brawl-btn {
  position: relative; z-index: 1;
  display: inline-flex; align-items: center; justify-content: center;
  height: 2.30126rem; padding: 0 1.4rem;
  border: 0; outline: none; cursor: pointer;
  font-family: var(--font-title); font-size: .85rem; text-transform: uppercase;
  color: #fff; -webkit-text-stroke: var(--stroke) var(--brawl-black);
  paint-order: stroke fill; text-shadow: 0 .1rem 0 #00000073;
  background: url("assets/btn-middle-gold.webp") no-repeat center / 100% 100%;
}
.brawl-btn::before, .brawl-btn::after {
  content: ""; position: absolute; top: 0;
  width: .490934rem; height: 100%;
  background-repeat: no-repeat; background-size: 100% 100%;
}
.brawl-btn::before { left: -.438633rem;  background-image: url("assets/btn-left-gold.webp"); }
.brawl-btn::after  { right: -.438633rem; background-image: url("assets/btn-right-gold.webp"); }

/* ── Charlie's web dressing ───────────────────────────────────────────────
 * Only on the section whose hero is Cocooner (see renderEvent) — a later
 * event with a different brawler keeps the plain cards. */

/* The reward badges carry no silk. The corner web is drawn for the corner of a
 * rectangle — inside a 3.5rem circle it crops to a scribble and reads as dirt
 * on the icon rather than as a web. The theme is carried by the two spiders and
 * the strand the row hangs from, which is enough. */

/* the strand the whole row hangs from */
.section.event[data-theme="web"] .milestones-wrap {
  position: relative;
  padding-top: .55rem;
}
.section.event[data-theme="web"] .milestones-wrap::before {
  content: ""; position: absolute; z-index: 0;
  top: 0; left: -2%; right: -2%; height: .1rem;
  background: linear-gradient(90deg, #ffffff00 0%, #ffffff59 12%, #ffffff59 88%, #ffffff00 100%);
  pointer-events: none;
}
.milestones-wrap { position: relative; }

/* Charlie herself, riding the progress bar at whatever the count has reached */
.event-track { position: relative; }
.event-spider {
  position: absolute; z-index: 3;
  top: -4.35rem; width: 2.4rem; height: 2.4rem;
  margin-left: -1.2rem;                  /* centre on the point, not beside it */
  /* a pale rim, or she disappears into the purple backdrop behind the bar */
  filter: drop-shadow(0 0 .12rem #d9c6ffcc) drop-shadow(0 .14rem .2rem #00000099);
  transition: left .6s ease-out;         /* once per poll, not per frame */
  pointer-events: none;
  animation: spiderBob 3.4s ease-in-out infinite;
}
/* the thread she is hanging by */
.event-spider::before {
  content: ""; position: absolute; left: 50%; bottom: 82%;
  width: .09rem; height: 1.5rem;
  margin-left: -.04rem;
  background: linear-gradient(#ffffff00, #ffffff8c);
}
@keyframes spiderBob {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(.22rem); }
}
.section.event[data-theme="web"] .event-spider { display: block; }
.event-spider { display: none; }


/* ── animation ────────────────────────────────────────────────────────────
 * Everything below is decorative. It is all switched off under
 * prefers-reduced-motion at the end of this file. */

.spider { display: block; width: 100%; height: 100%; overflow: visible; }

/* Legs paddle in two alternating groups, the way a real one moves — all eight
 * in unison reads as a bouncing sticker. transform-box/origin keep each leg
 * pivoting at its own root instead of at the svg corner. */
.spider .leg {
  transform-box: fill-box;
  transform-origin: 0% 50%;
  animation: legPaddle 1.15s ease-in-out infinite;
}
.spider .leg-l0, .spider .leg-l2, .spider .leg-r1, .spider .leg-r3 { animation-delay: -.575s; }
@keyframes legPaddle {
  0%, 100% { transform: rotate(0deg); }
  50%      { transform: rotate(-7deg); }
}
.spider .spider-body { transform-box: fill-box; transform-origin: 50% 50%; animation: bodyBob 1.15s ease-in-out infinite; }
@keyframes bodyBob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(.6px); } }

/* the spider riding the progress bar */
.event-spider .spider { animation: spiderSway 3.4s ease-in-out infinite; }
@keyframes spiderSway {
  0%, 100% { transform: rotate(-5deg); }
  50%      { transform: rotate(5deg); }
}

/* a second one patrolling the strand the reward row hangs from */
/* The walker is a full-width track that slides by transform. Animating `left`
 * relaid out the row on every frame; a percentage translate on the track is
 * relative to the track's own width, so it covers the same span for free. */
.web-walker {
  position: absolute; z-index: 2;
  top: -.75rem; left: 0; right: 0;
  height: 1.5rem;
  pointer-events: none;
  display: none;
  animation: walkStrand 26s ease-in-out infinite;
  will-change: transform;
}
.web-walker > .spider {
  width: 1.5rem; height: 1.5rem;
  filter: drop-shadow(0 .1rem .16rem #00000080);
  animation: walkFace 26s steps(1, end) infinite;
}
.section.event[data-theme="web"] .web-walker { display: block; }
@keyframes walkStrand {
  0%   { transform: translate3d(2%, 0, 0); }
  46%  { transform: translate3d(88%, 0, 0); }
  50%  { transform: translate3d(88%, 0, 0); }
  96%  { transform: translate3d(2%, 0, 0); }
  100% { transform: translate3d(2%, 0, 0); }
}
/* she turns around at each end rather than moonwalking back */
@keyframes walkFace {
  0%, 47%   { transform: scaleX(1); }
  48%, 97%  { transform: scaleX(-1); }
  98%, 100% { transform: scaleX(1); }
}

/* The fill sweeps a highlight along itself so the bar never looks frozen. Only
 * the clipping belongs here: this rule used to re-declare position:relative and,
 * sitting later in the file, it overrode the fill's absolute positioning — the
 * percentage width then had nothing to resolve against and collapsed to its
 * minimum, so the bar looked almost empty however full it was. */
.event-bar > span { overflow: hidden; }
.event-bar > span::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(100deg, #ffffff00 0%, #ffffff00 38%, #ffffffa6 50%, #ffffff00 62%, #ffffff00 100%);
  transform: translateX(-100%);
  animation: barSweep 2.8s ease-in-out infinite;
}
@keyframes barSweep { to { transform: translateX(100%); } }

/* the counter flashes as it climbs */
.event-count strong.ticking { animation: countFlash .9s ease-out; }
@keyframes countFlash {
  0%   { color: #fff8dc; transform: scale(1.14); }
  100% { color: var(--brawl-orange); transform: scale(1); }
}
.event-count strong { display: inline-block; }

/* a prize that can be taken drifts, and its plate catches sparks */
.milestone.ready .milestone-art img { animation: prizeFloat 2.6s ease-in-out infinite; }
@keyframes prizeFloat {
  0%, 100% { transform: translateY(0) scale(1); }
  50%      { transform: translateY(-.26rem) scale(1.06); }
}
/* the prize lifts out of its plate, so the plate keeps a little headroom and
 * the card no longer crops what rises out of it */
.milestone.ready .milestone-art { overflow: visible; }
.milestone.ready .milestone-art img { z-index: 2; }
/* the CLAIM strip brightens by fading a light overlay in, not by animating a
 * filter — a filter loop rasterises the strip on every frame */
.milestone.ready .milestone-action { position: relative; overflow: hidden; }
.milestone.ready .milestone-action::after {
  content: ""; position: absolute; inset: 0;
  background: #fff6d4;
  mix-blend-mode: soft-light;
  animation: claimBreathe 1.9s ease-in-out infinite;
  pointer-events: none;
}
@keyframes claimBreathe {
  0%, 100% { opacity: 0; }
  50%      { opacity: .75; }
}

/* the burst thrown when a reward is taken */
.burst { position: absolute; inset: 0; z-index: 3; pointer-events: none; }
.burst i {
  position: absolute; top: 42%; left: 50%;
  width: .3rem; height: .3rem; border-radius: 50%;
  background: radial-gradient(circle, #fff8dc 0%, #ffd257 55%, #ffab18 100%);
  box-shadow: 0 0 .3rem #ffd257cc;
  animation: burstOut .85s ease-out var(--delay, 0ms) both;
}
@keyframes burstOut {
  0%   { transform: translate(-50%, -50%) scale(.3); opacity: 0; }
  18%  { opacity: 1; }
  100% { transform: translate(calc(-50% + var(--dx)), calc(-50% + var(--dy))) scale(.5); opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .spider .leg, .spider .spider-body, .event-spider .spider, .web-walker,
  .milestone.ready .milestone-rays::after,
  .event-bar > span::after, .event-count strong.ticking,
  .milestone.ready, .milestone.ready .milestone-art img,
  .milestone.ready .milestone-goal, .milestone.ready .milestone-action,
  .burst i,
  .section.event[data-theme="web"] > .ground, .section-bg {
    animation: none !important;
  }
}

/* ── the opened article ───────────────────────────────────────────────────
 * Geometry, colours and type are the official _articleView_'s, read off the
 * live page rather than guessed. Its own side padding token is 6.27615rem at
 * the 1024pt stage; it tightens on narrow screens so the column still breathes.
 */
:root { --article-side-padding: 6.27615rem; }
@media (max-width: 44rem) { :root { --article-side-padding: 2.5rem; } }
@media (max-width: 28rem) { :root { --article-side-padding: 1.25rem; } }

.article-view {
  position: fixed; inset: 0; z-index: 50;
  display: flex; flex-direction: column;
  overflow-y: auto; overflow-x: hidden;
  scrollbar-width: none;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  color: var(--brawl-white);
  background: #101560;
}
.article-view[hidden] { display: none; }

.article-header {
  position: relative;
  flex-shrink: 0;
  width: 100%; height: 14.9582rem;
  border-bottom: .104603rem solid var(--brawl-black);
}
/* the glow the original throws behind the hero */
.article-header::before {
  content: ""; position: absolute; top: 0; left: 0; z-index: 0;
  width: 100%; height: 31.25rem;
  border-radius: 67.5rem;
  background: radial-gradient(50% 50%, #5e28ff66 0%, #5e28ff00 100%);
  pointer-events: none;
}
.article-header::after {
  content: ""; position: absolute; top: 0; left: 0;
  width: 100%; height: 100%;
  box-shadow: inset 0 -.156904rem #04040466;
  pointer-events: none;
}
.article-hero {
  position: relative; z-index: 1;
  width: 100%; height: 100%;
  padding-top: var(--safe-top);
  background: center / cover no-repeat;
}
.article-hero.empty { background: linear-gradient(135deg, #35507f, #1d2b47); }

.article-content {
  position: relative; z-index: 0;
  /* 1 0 auto, not 1: a zero flex-basis lets this shrink to the viewport, and
   * with overflow hidden the rest of the article is simply cut off. */
  display: flex; flex-direction: column; flex: 1 0 auto;
  justify-content: space-between; align-items: center;
  overflow: hidden;
  background: linear-gradient(#101560 0%, #171f8c 100%), #2a59db;
}
.article-content::after {
  content: ""; position: absolute; top: 0; left: 0; z-index: -1;
  width: 100%; height: 100%;
  background: url("assets/skullpattern-02.svg") 0 0 / 50%;
  pointer-events: none;
}

.article-title-block {
  width: 100%; max-width: var(--content-max-width);
  padding: 1.67364rem var(--article-side-padding);
  display: flex; flex-direction: column; gap: .83682rem;
}
.article-meta { display: flex; gap: 1.25523rem; height: 1.25523rem; }
.article-category, .article-date {
  font-family: var(--font-title); font-weight: var(--font-title-weight);
  font-size: 1.04603rem; text-transform: uppercase;
}
.article-category { color: #f5ab32; }
.article-date { color: #ffffff80; }
.article-title {
  font-family: var(--font-title); font-weight: var(--font-title-weight);
  font-size: 2.09205rem; line-height: normal; text-transform: uppercase;
  color: var(--brawl-white);
  -webkit-text-stroke: var(--stroke) var(--brawl-black);
  paint-order: stroke fill;
  filter: drop-shadow(0 max(.104603rem, 1px) 0 var(--brawl-black));
  /* the base heading rule adds a shadow the original does not have here */
  text-shadow: none;
}

.article-details {
  width: 100%; max-width: var(--content-max-width);
  /* the bottom padding clears the pinned footer, not just the page end */
  padding: 0 var(--article-side-padding) calc(3.34728rem + 4.4rem + var(--safe-bottom));
  display: flex; flex-direction: column; flex: 1 0 auto; gap: 2.51046rem;
}
.article-block { display: flex; flex-direction: column; gap: .83682rem; }
.article-block-title {
  font-family: var(--font-title); font-weight: var(--font-title-weight);
  font-size: 1.4rem; text-transform: uppercase;
  -webkit-text-stroke: var(--stroke) var(--brawl-black); paint-order: stroke fill;
}
.article-block p {
  font-family: var(--font-body);
  font-size: 1.04603rem; line-height: 1.6;
  color: var(--brawl-white);
}
.article-block a { color: var(--brawl-orange); text-decoration: underline; }

/* Pinned to the bottom of the screen rather than sitting at the end of the
 * article, so the arrows stay reachable wherever the reader is. The bar itself
 * lets touches through — only the two buttons take them — or it would eat
 * scroll gestures across the whole width. */
.article-footer {
  position: fixed; z-index: 60;
  left: 0; right: 0; bottom: 0;
  width: 100%;
  pointer-events: none;
}
/* so text scrolling underneath stays readable behind them */
.article-footer::before {
  content: ""; position: absolute; inset: -1.6rem 0 0 0;
  background: linear-gradient(#10156000 0%, #101560cc 38%, #101560 72%, #101560 100%);
  pointer-events: none;
}
.article-footer-inner {
  position: relative;
  display: flex; flex-direction: row; justify-content: space-between;
  width: 100%; max-width: var(--content-max-width);
  margin: 0 auto;
  padding: 1.67364rem 1.25523rem calc(1.25523rem + var(--safe-bottom));
  padding-left: calc(1.25523rem + var(--safe-left));
  padding-right: calc(1.25523rem + var(--safe-right));
  transform: translateY(10.4603rem);
  pointer-events: none;
}
.article-nav { pointer-events: auto; }
.article-footer[data-animate="true"] > .article-footer-inner {
  animation: bounceInUp .8s cubic-bezier(.65, .46, .45, .94) forwards;
}
@keyframes bounceInUp {
  0%   { transform: translateY(10.4603rem); }
  50%  { transform: translateY(-.523013rem); }
  75%  { transform: translateY(.104603rem); }
  100% { transform: translateY(0); }
}
.article-nav {
  display: flex; align-items: center; gap: .627615rem;
  border: 0; background: none; cursor: pointer;
  font-family: var(--font-title); font-weight: var(--font-title-weight);
  font-size: 1.04603rem; line-height: var(--line-height-tight);
  text-transform: uppercase; color: var(--brawl-white);
}
.article-nav[hidden] { display: none; }
.article-nav-newer { margin-left: auto; }
.article-nav-icon { width: 2.51046rem; height: 2.51046rem; }
.article-nav-icon.flip { transform: scaleX(-1); }

@media (prefers-reduced-motion: reduce) {
  .article-footer[data-animate="true"] > .article-footer-inner { animation: none; transform: none; }
}

/* ── sections that are not built yet ──────────────────────────────────────
 * They keep their place in the page so the tab bar still scrolls to them. */
.coming-soon {
  display: flex; flex-direction: column; align-items: center; gap: .55rem;
  padding: 3.2rem 1.2rem 3.6rem;
  text-align: center;
}
.coming-soon-title {
  font-family: var(--font-title); font-weight: var(--font-title-weight);
  font-size: 1.6rem; text-transform: uppercase;
  color: var(--brawl-white);
  -webkit-text-stroke: var(--stroke) var(--brawl-black); paint-order: stroke fill;
  text-shadow: 0 .12rem 0 #00000059;
}
.coming-soon-body {
  max-width: 22rem;
  font-size: .86rem; line-height: var(--line-height-normal);
  color: #d3daff;
}
/* the esports ground is paper, so light-on-light has to become ink */
.section.paper .coming-soon-body { color: #4a4234; }
.section.paper .coming-soon-title { color: #3a3325; -webkit-text-stroke-width: 0; text-shadow: 0 .1rem 0 #00000026; }

/* ── the page's own notice ────────────────────────────────────────────────
 * Used for the server's answer to a claim, which arrives over the game's
 * connection long after the call that sent it has returned. */
.notice {
  position: fixed; z-index: 300;
  left: 50%; top: calc(var(--safe-top) + var(--nav-h) + .9rem);
  transform: translate(-50%, -.6rem);
  max-width: min(26rem, calc(100vw - 3rem));
  padding: .55rem 1rem .62rem;
  border: .104603rem solid var(--brawl-black); border-radius: .26rem;
  font-family: var(--font-title); font-weight: var(--font-title-weight);
  font-size: .88rem; line-height: var(--line-height-normal);
  letter-spacing: .01em; text-align: center;
  -webkit-text-stroke: var(--stroke) var(--brawl-black); paint-order: stroke fill;
  text-shadow: 0 .1rem 0 #00000059;
  box-shadow: 0 .2rem .5rem #00000073, inset 0 .1rem 0 #ffffff26;
  opacity: 0; pointer-events: none;
  transition: opacity .22s ease, transform .22s ease;
}
.notice.show { opacity: 1; transform: translate(-50%, 0); }
.notice.error { background: linear-gradient(180deg, #e0453b, #b32a22); color: #fff; }
.notice.info  { background: linear-gradient(180deg, #3b4a8f, #26315f); color: #e6ebff; }

/* ── the music switch ─────────────────────────────────────────────────────
 * Tucked under the back button, clear of both the tab bar and the article's
 * older/newer row, and above the article view so it stays reachable there. */
.music-btn {
  position: fixed; z-index: 70;
  top: calc(var(--safe-top) + 4.15rem);
  left: calc(var(--safe-left) + .9rem);
  width: 2.35rem; height: 2.35rem;
  display: grid; place-items: center;
  padding: 0; cursor: pointer;
  border: .104603rem solid var(--brawl-black); border-radius: 50%;
  background: linear-gradient(180deg, #3d4666 0%, #262d4b 100%);
  box-shadow: inset 0 .1rem 0 #ffffff2e, 0 .104603rem #04040466;
  transition: transform .12s ease, background .18s ease;
}
.music-btn svg {
  width: 1.35rem; height: 1.35rem;
  fill: none; stroke: #eaeeff; stroke-width: 1.9;
  stroke-linecap: round; stroke-linejoin: round;
}
.music-btn .speaker { fill: #eaeeff; }
.music-btn .cross { display: none; }

/* off */
.music-btn[aria-pressed="false"] {
  background: linear-gradient(180deg, #2a2f45 0%, #1b1f33 100%);
}
.music-btn[aria-pressed="false"] svg { stroke: #8b93b8; }
.music-btn[aria-pressed="false"] .speaker { fill: #8b93b8; }
.music-btn[aria-pressed="false"] .waves { display: none; }
.music-btn[aria-pressed="false"] .cross { display: block; }

html:not(.ready) .music-btn { visibility: hidden; }

/* ── the opening ──────────────────────────────────────────────────────────
 * Plays once when the inbox is revealed, over the finished page. Nothing here
 * takes a tap and nothing here repaints: every step is transform or opacity. */
.opening {
  position: fixed; inset: 0; z-index: 200;
  overflow: hidden;
  pointer-events: none;
}

/* the curtain, parting on the circus the scene is already set in */
/* Parked open in the base rule, and the animation runs it from closed to
 * open — not the other way round. A webview that is throttled or off-screen
 * never advances an animation, and written the obvious way round that leaves
 * the curtain shut over the whole page until the removal timer fires. This way
 * the failure mode is "no effect", not "the inbox is a purple wall". */
.opening-curtain {
  position: absolute; top: 0; bottom: 0;
  width: 52%;
  will-change: transform;
  animation: curtainPart .85s cubic-bezier(.6, .02, .3, 1) both;
}
.opening-curtain.left  { transform: translateX(-115%); }
.opening-curtain.right { transform: translateX(115%); }
.opening-curtain.left {
  left: 0;
  transform-origin: left center;
}
.opening-curtain.right {
  right: 0;
  transform-origin: right center;
  animation-name: curtainPartRight;
}

/* A real big-top drape reads by its folds, not by its colour: alternating lit
 * and shadowed bands, tightening towards the parting edge where the fabric
 * gathers. The scene behind is already hung with magenta curtains, so these
 * match those rather than the usual circus red. */
.opening-curtain::before {
  content: ""; position: absolute; inset: 0;
  background:
    repeating-linear-gradient(90deg,
      #3d0b33 0, #6d1857 1.1rem, #a02d7c 2.1rem, #6d1857 3.1rem, #3d0b33 4.2rem),
    linear-gradient(90deg, #250825 0%, #5e1450 55%, #7d1f63 100%);
}
/* the gathered edge, plus the gold rope trim that finishes it */
.opening-curtain::after {
  content: ""; position: absolute; top: 0; bottom: 0;
  width: 2.6rem;
  background:
    linear-gradient(90deg, #00000000 0%, #00000073 62%, #0000008c 100%),
    repeating-linear-gradient(90deg,
      #2a0726 0, #7d1f63 .5rem, #2a0726 1rem);
}
.opening-curtain.left::after  { right: 0; border-right: .18rem solid #d9a441; }
/* Mirrored about its own centre, not about its left edge: the latter pushes the
 * strip outside the curtain, and it was still sitting on screen after the
 * curtain itself had slid away. */
.opening-curtain.right::after { left: 0;  border-left:  .18rem solid #d9a441;
  transform: scaleX(-1); }

@keyframes curtainPart      { from { transform: translateX(0); } to { transform: translateX(-115%); } }
@keyframes curtainPartRight { from { transform: translateX(0); } to { transform: translateX(115%); } }

/* The scalloped pelmet across the top. This is the part that says circus
 * rather than theatre: a row of swags with a gold edge, cut by a mask so the
 * scallops are real shapes and not a drawn-on line. It lifts away rather than
 * parting sideways, like the drapes below it. */
.opening-valance {
  position: absolute; top: 0; left: 0; right: 0; z-index: 2;
  height: 5.2rem;
  transform: translateY(-102%);
  will-change: transform;
  animation: valanceLift .8s cubic-bezier(.6, .02, .3, 1) .1s both;
  background:
    repeating-linear-gradient(90deg,
      #3d0b33 0, #6d1857 1.1rem, #a02d7c 2.1rem, #6d1857 3.1rem, #3d0b33 4.2rem),
    linear-gradient(180deg, #7d1f63 0%, #4a0f3d 100%);
  -webkit-mask-image:
    radial-gradient(circle 1.5rem at 1.5rem 0, #000 97%, #0000 99%),
    linear-gradient(#000, #000);
  -webkit-mask-size: 3rem 1.5rem, 100% calc(100% - 1.5rem);
  -webkit-mask-position: 0 100%, 0 0;
  -webkit-mask-repeat: repeat-x, no-repeat;
  mask-image:
    radial-gradient(circle 1.5rem at 1.5rem 0, #000 97%, #0000 99%),
    linear-gradient(#000, #000);
  mask-size: 3rem 1.5rem, 100% calc(100% - 1.5rem);
  mask-position: 0 100%, 0 0;
  mask-repeat: repeat-x, no-repeat;
}
/* the gold beading that follows the scallops */
.opening-valance::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0;
  height: 1.6rem;
  background: radial-gradient(circle 1.5rem at 1.5rem 0,
                #0000 88%, #d9a441 90%, #f0cf7a 94%, #d9a441 96%, #0000 98%)
              0 100% / 3rem 1.5rem repeat-x;
}
@keyframes valanceLift {
  from { transform: translateY(0); }
  to   { transform: translateY(-102%); }
}

/* silk drawing itself into the top corners */
.opening-web {
  position: absolute; top: 0;
  width: 13rem; height: 13rem;
  opacity: 0;
  will-change: transform, opacity;
  animation: webSpin 1.5s ease-out .35s forwards;
}
/* Spun from the corner it is anchored to, outwards — the default origin is the
 * middle of the box, which made the web grow out of thin air in the centre and
 * meet its own corner last. */
.opening-web.tl {
  left: 0; transform-origin: top left;
  background: url("assets/web-corner.svg") no-repeat top left / contain;
}
.opening-web.tr {
  right: 0; transform-origin: top right;
  background: url("assets/web-corner-right.svg") no-repeat top right / contain;
}
@keyframes webSpin {
  0%   { opacity: 0; transform: scale(.2); }
  35%  { opacity: .55; }
  70%  { opacity: .45; transform: scale(1); }
  100% { opacity: 0; transform: scale(1.04); }
}

/* the spiders, dropping in and hauling themselves back up */
.opening-spider {
  position: absolute; top: 0;
  width: var(--size); height: var(--size);
  margin-left: calc(var(--size) / -2);
  transform: translateY(-140%);
  will-change: transform;
  animation: spiderDrop 2.1s cubic-bezier(.34, 1.2, .5, 1) var(--delay) forwards;
}
.opening-spider .spider { filter: drop-shadow(0 .12rem .2rem #00000099); }
/* the thread it is hanging from, running off the top of the screen */
.opening-thread {
  position: absolute; left: 50%; bottom: 72%;
  width: .09rem; height: 200vh;
  margin-left: -.045rem;
  background: linear-gradient(#ffffff00, #ffffff8c);
}
@keyframes spiderDrop {
  0%   { transform: translateY(-140%) rotate(0deg); }
  38%  { transform: translateY(var(--drop)) rotate(var(--swing)); }
  58%  { transform: translateY(calc(var(--drop) - 1.4rem)) rotate(calc(var(--swing) * -.6)); }
  72%  { transform: translateY(var(--drop)) rotate(0deg); }
  100% { transform: translateY(-140%) rotate(0deg); }
}

@media (prefers-reduced-motion: reduce) { .opening { display: none; } }


/* ── press feedback ───────────────────────────────────────────────────────
 * The official page's own recipe: the thing being pressed shrinks to 90% while
 * held, and an invisible hitbox laid over it grows 10% to compensate, so the
 * area that answers the finger does not shrink away from under it. Driven from
 * pointer events by setupPressables(), not from :active, which sticks after a
 * scroll in a WebView and does not release when the finger slides off. */
/* No blanket `position: relative` here: it overrode the reward tiles' own
 * absolute positioning and dropped them out of the track. Everything pressable
 * is already positioned by its own rule; only the article's arrows were not. */
.article-nav { position: relative; }
[data-pressable] > .pressable-hitbox,
[data-pressable-target] > .pressable-hitbox {
  position: absolute; top: 0; left: 0;
  width: 100%; height: 100%;
  pointer-events: none;
  transition: transform .1s;
}
[data-pressable].pressed,
[data-pressable-target].pressed { transform: scale(.9); }
[data-pressable].pressed > .pressable-hitbox,
[data-pressable-target].pressed > .pressable-hitbox { transform: scale(1.1); }

/* The nav plates are the exception in the original too: a gentler .97 over
 * .2s rather than the snap to .9 everything else gets. */
.icon-btn.pressed { transform: scale(.97); transition: transform .2s; }

/* A reward tile is positioned by a translate, so its press has to keep it. */
.milestone.pressed { transform: translateX(-50%) scale(.9); }


/* ── Upcoming event teaser (Supercell-style card) ─────────────────────────── */
.upcoming-card {
  margin: 1.8rem auto; max-width: 26rem;
  display: flex; flex-direction: column; align-items: center; gap: .8rem;
  padding: 2rem 1.5rem 2.3rem;
  background: linear-gradient(180deg, var(--card-lift), var(--card));
  border: .16rem solid var(--brawl-black); border-radius: .85rem;
  box-shadow: 0 .42rem 0 #00000066, inset 0 .14rem 0 #ffffff2b;
}
.upcoming-badge {
  font-family: var(--font-title); font-weight: var(--font-title-weight);
  text-transform: uppercase; letter-spacing: .06em; font-size: 1.05rem;
  padding: .32rem 1.15rem; border-radius: 999px;
  background: linear-gradient(180deg, var(--gold), var(--gold-deep));
  color: #3a2200; border: .14rem solid var(--brawl-black);
  -webkit-text-stroke: .05rem #0000004d; paint-order: stroke fill;
  box-shadow: 0 .2rem 0 #00000059, inset 0 .11rem 0 #ffffff73;
}
.upcoming-sub {
  font-family: var(--font-title); font-weight: var(--font-title-weight);
  text-transform: uppercase; font-size: .95rem; color: var(--text-dim);
  -webkit-text-stroke: var(--stroke) var(--brawl-black); paint-order: stroke fill;
}
.upcoming-count { display: flex; flex-direction: column; align-items: center; line-height: 1; }
.upcoming-days {
  font-family: var(--font-title); font-weight: var(--font-title-weight);
  font-size: 4rem; color: var(--gold);
  -webkit-text-stroke: var(--stroke) var(--brawl-black); paint-order: stroke fill;
  text-shadow: 0 .18rem 0 #00000073;
}
.upcoming-days-label {
  font-family: var(--font-title); font-weight: var(--font-title-weight);
  text-transform: uppercase; font-size: 1.05rem; color: var(--text);
  -webkit-text-stroke: var(--stroke) var(--brawl-black); paint-order: stroke fill;
}
.upcoming-title {
  font-family: var(--font-title); font-weight: var(--font-title-weight);
  text-transform: uppercase; font-size: 1.3rem; color: var(--text); text-align: center;
  -webkit-text-stroke: var(--stroke) var(--brawl-black); paint-order: stroke fill;
  text-shadow: 0 .12rem 0 #00000059;
}
