/* ── Design tokens ─────────────────────────────────────────── */
/*
  Light is the base palette: every token is given a value here, unconditionally,
  and the dark block below only ever *re-assigns* them. Nothing is defined for
  the first time inside a media query, so there is no combination of settings
  that can leave a colour undefined.

  The `-ink` tokens are the text drawn ON a filled primary/danger/success
  surface. They exist because the fills get lighter in dark mode -- white on a
  pale indigo button is unreadable -- so the label has to flip to dark with
  them. Every filled control reads its foreground from one of these rather than
  hard-coding #fff.
*/
:root {
  color-scheme: light;

  --bg: #f4f6fb;
  --surface: #ffffff;
  --surface-2: #f8fafc;
  --border: #e2e8f0;
  --border-strong: #cbd5e1;
  --text: #0f172a;
  --text-muted: #5c6a80;

  --primary: #4f46e5;
  --primary-hover: #4338ca;
  --primary-soft: #eef2ff;
  --primary-ink: #ffffff;

  --danger: #dc2626;
  --danger-hover: #b91c1c;
  --danger-soft: #fef2f2;
  --danger-border: #fecaca;
  --danger-text: #991b1b;
  --danger-ink: #ffffff;

  --success: #047857;
  --success-soft: #ecfdf5;
  --success-border: #a7f3d0;
  --success-text: #065f46;
  --success-ink: #ffffff;

  --warn: #b45309;
  --warn-soft: #fffbeb;
  --warn-border: #fde68a;

  /* Composite surfaces that are not a flat token colour. */
  --accent-panel: linear-gradient(135deg, #eef2ff 0%, #f5f3ff 100%);
  --accent-panel-border: #ddd6fe;
  --auth-bg: linear-gradient(135deg, #4f46e5 0%, #7c3aed 50%, #2563eb 100%);
  --toast-bg: #0f172a;
  --toast-text: #ffffff;
  --spinner-track: rgba(0, 0, 0, .12);

  --radius: 12px;
  --radius-sm: 8px;
  --shadow: 0 1px 3px rgba(15, 23, 42, .08), 0 8px 24px rgba(15, 23, 42, .06);
  --shadow-up: 0 -6px 24px rgba(15, 23, 42, .09);
  --shadow-toast: 0 8px 30px rgba(15, 23, 42, .3);
  --font: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}

/*
  Dark mode.

  Applied when the device asks for it, when Telegram says its own theme is dark
  (telegram.js puts .tg-dark on <html> from Telegram's colorScheme), or when
  something sets data-theme="dark" explicitly. data-theme="light" always wins,
  so there is a way to pin the light palette on a dark device.

  The token list below is exactly the list above -- same names, same order --
  so the two can be read side by side. Shadows go heavier and darker because a
  soft grey shadow is invisible on a dark ground; the elevation has to come
  from near-black instead.
*/
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;

    --bg: #0b1220;
    --surface: #121b2f;
    --surface-2: #1a2439;
    --border: #26324c;
    --border-strong: #3b4a6b;
    --text: #e7ecf6;
    --text-muted: #96a3bd;

    --primary: #818cf8;
    --primary-hover: #a5b4fc;
    --primary-soft: #1e2547;
    --primary-ink: #0b1220;

    --danger: #f87171;
    --danger-hover: #fca5a5;
    --danger-soft: #2f1618;
    --danger-border: #5c2b2f;
    --danger-text: #fca5a5;
    --danger-ink: #2a0d10;

    --success: #34d399;
    --success-soft: #0e2a20;
    --success-border: #1e5040;
    --success-text: #6ee7b7;
    --success-ink: #04241a;

    --warn: #fbbf24;
    --warn-soft: #2c2410;
    --warn-border: #5a4415;

    --accent-panel: linear-gradient(135deg, #1a2142 0%, #221d3d 100%);
    --accent-panel-border: #343162;
    --auth-bg: linear-gradient(135deg, #312e81 0%, #4c1d95 50%, #1e3a8a 100%);
    --toast-bg: #e7ecf6;
    --toast-text: #0b1220;
    --spinner-track: rgba(255, 255, 255, .16);

    --shadow: 0 1px 3px rgba(0, 0, 0, .5), 0 8px 24px rgba(0, 0, 0, .4);
    --shadow-up: 0 -6px 24px rgba(0, 0, 0, .45);
    --shadow-toast: 0 8px 30px rgba(0, 0, 0, .6);
  }
}

/* The same palette, chosen explicitly rather than by device preference. */
:root[data-theme="dark"],
:root.tg-dark {
  color-scheme: dark;

  --bg: #0b1220;
  --surface: #121b2f;
  --surface-2: #1a2439;
  --border: #26324c;
  --border-strong: #3b4a6b;
  --text: #e7ecf6;
  --text-muted: #96a3bd;

  --primary: #818cf8;
  --primary-hover: #a5b4fc;
  --primary-soft: #1e2547;
  --primary-ink: #0b1220;

  --danger: #f87171;
  --danger-hover: #fca5a5;
  --danger-soft: #2f1618;
  --danger-border: #5c2b2f;
  --danger-text: #fca5a5;
  --danger-ink: #2a0d10;

  --success: #34d399;
  --success-soft: #0e2a20;
  --success-border: #1e5040;
  --success-text: #6ee7b7;
  --success-ink: #04241a;

  --warn: #fbbf24;
  --warn-soft: #2c2410;
  --warn-border: #5a4415;

  --accent-panel: linear-gradient(135deg, #1a2142 0%, #221d3d 100%);
  --accent-panel-border: #343162;
  --auth-bg: linear-gradient(135deg, #312e81 0%, #4c1d95 50%, #1e3a8a 100%);
  --toast-bg: #e7ecf6;
  --toast-text: #0b1220;
  --spinner-track: rgba(255, 255, 255, .16);

  --shadow: 0 1px 3px rgba(0, 0, 0, .5), 0 8px 24px rgba(0, 0, 0, .4);
  --shadow-up: 0 -6px 24px rgba(0, 0, 0, .45);
  --shadow-toast: 0 8px 30px rgba(0, 0, 0, .6);
}

* { box-sizing: border-box; }

body {
  margin: 0;
  font-family: var(--font);
  background: var(--bg);
  color: var(--text);
  -webkit-font-smoothing: antialiased;
  line-height: 1.5;
}

a { color: var(--primary); }

.muted { color: var(--text-muted); font-size: .92rem; }
.req { color: var(--danger); }
.mt-lg { margin-top: 20px; }

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

.h1 { font-size: 1.6rem; margin: 0 0 6px; letter-spacing: -.02em; }
.h2 { font-size: 1.1rem; margin: 0 0 12px; letter-spacing: -.01em; }
.eyebrow {
  margin: 0 0 6px; font-size: .74rem; font-weight: 700;
  letter-spacing: .12em; text-transform: uppercase; color: var(--primary);
}

/* ── Buttons ───────────────────────────────────────────────── */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  padding: 10px 18px;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  font: inherit; font-weight: 600; font-size: .9rem;
  cursor: pointer; text-decoration: none;
  transition: background-color .15s, border-color .15s, color .15s, transform .05s;
  white-space: nowrap;
}
.btn:active { transform: translateY(1px); }
.btn:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
.btn[disabled] { opacity: .55; cursor: not-allowed; }

.btn--primary { background: var(--primary); color: var(--primary-ink); }
.btn--primary:hover:not([disabled]) { background: var(--primary-hover); }
.btn--ghost { background: var(--surface); color: var(--text); border-color: var(--border); }
.btn--ghost:hover:not([disabled]) { background: var(--surface-2); border-color: var(--border-strong); }
.btn--sm { padding: 6px 12px; font-size: .82rem; }
.btn--lg { padding: 13px 26px; font-size: 1rem; }
.btn--block { width: 100%; }

/* ── Layout ────────────────────────────────────────────────── */
.shell {
  max-width: 1080px;
  margin: 0 auto;
  padding: 28px 20px 64px;
}
.shell--narrow { max-width: 720px; }

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  margin-bottom: 20px;
}
.card--pad { padding: 24px; }

.footer { padding: 28px 20px 40px; text-align: center; }

/* ── Nav ───────────────────────────────────────────────────── */
.nav {
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  position: sticky; top: 0; z-index: 20;
}
/*
  Brand, switcher, links -- in that source order. The switcher takes the free
  space with margin-left:auto rather than the row using space-between, because
  space-between would push the three apart evenly and strand the globe button
  in the middle of the bar.
*/
.nav__inner {
  max-width: 1080px; margin: 0 auto;
  padding: 12px 20px;
  display: flex; align-items: center; gap: 12px;
  flex-wrap: wrap;
}
.nav__inner > .langswitch { margin-left: auto; }
.nav__brand {
  display: inline-flex; align-items: center; gap: 8px;
  text-decoration: none; color: var(--text); font-weight: 700;
}
.nav__logo {
  display: inline-grid; place-items: center;
  width: 32px; height: 32px;
  background: var(--primary); color: var(--primary-ink);
  border-radius: 9px; font-size: .78rem; font-weight: 800; letter-spacing: .04em;
}
/* The brand mark is an image; keep the same 32px square and rounded corners. */
img.nav__logo {
  display: block;
  object-fit: cover;
  background: none;
}
.nav__links { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.nav__brandtext { white-space: nowrap; }
.nav__link {
  padding: 7px 11px; border-radius: var(--radius-sm);
  text-decoration: none; color: var(--text-muted);
  font-size: .9rem; font-weight: 600;
}
.nav__link:hover { background: var(--surface-2); color: var(--text); }
.nav__link.is-active { background: var(--primary-soft); color: var(--primary); }
.nav__logout { margin: 0; }

/* ── Alerts ────────────────────────────────────────────────── */
.alert {
  padding: 12px 16px;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  margin: 0 0 18px;
  font-size: .92rem;
}
.alert--error { background: var(--danger-soft); border-color: var(--danger-border); color: var(--danger-text); }
.alert--ok    { background: var(--success-soft); border-color: var(--success-border); color: var(--success-text); }
.alert--warn  { background: var(--warn-soft); border-color: var(--warn-border); color: var(--warn); }
.alert__list { margin: 0; padding-left: 20px; }

/* ── Auth pages ────────────────────────────────────────────── */
.page-auth {
  min-height: 100vh;
  background: var(--auth-bg);
  display: grid; place-items: start center;
  padding: 40px 20px;
}
.auth { width: 100%; max-width: 420px; }
.auth__brand {
  display: flex; align-items: center; justify-content: center; gap: 10px;
  margin-bottom: 20px;
  color: #fff; font-weight: 700; font-size: 1.1rem; text-decoration: none;
}
.auth__brand .nav__logo { background: rgba(255, 255, 255, .18); }

/* ── Forms ─────────────────────────────────────────────────── */
.form { margin-top: 18px; }
.field { margin-bottom: 16px; }
.field__label { display: block; font-size: .86rem; font-weight: 600; margin-bottom: 6px; }
.field__input {
  width: 100%;
  padding: 11px 13px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  font: inherit; font-size: .95rem;
  background: var(--surface); color: var(--text);
}
.field__input:focus {
  outline: none;
  border-color: var(--primary);
  box-shadow: 0 0 0 3px var(--primary-soft);
}
.field__input--file { padding: 9px 12px; }
.field__group { display: flex; gap: 8px; }
.field__group .field__input { flex: 1; }
.field__toggle {
  padding: 0 13px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  font: inherit; font-size: .82rem; font-weight: 600;
  color: var(--text-muted); cursor: pointer;
}
.field__toggle:hover { color: var(--text); }
.field__hint { margin: 6px 0 0; font-size: .82rem; color: var(--text-muted); }

/* ── Hero ──────────────────────────────────────────────────── */
.hero { overflow: hidden; }
.hero__media { background: var(--surface-2); }
.hero__img {
  display: block; width: 100%;
  max-height: 420px; object-fit: cover;
}
.hero__img--empty {
  display: grid; place-items: center;
  height: 220px; color: var(--text-muted); font-size: .9rem;
}
.hero__body { padding: 26px 24px 30px; }
.hero__title { font-size: 2rem; margin: 0 0 16px; letter-spacing: -.03em; }
.hero__cta {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  margin-top: 22px;
}

.price { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; margin-bottom: 20px; }
.price__amount { font-size: 2.1rem; font-weight: 800; letter-spacing: -.03em; color: var(--primary); }
.price__currency { font-size: 1rem; font-weight: 700; color: var(--primary); }
.price__per { font-size: .9rem; color: var(--text-muted); }

/* ── Prize card (what you are buying) ──────────────────────── */
.prize {
  display: grid;
  grid-template-columns: minmax(0, 260px) minmax(0, 1fr);
  overflow: hidden;
  margin-bottom: 16px;
}
.prize__media { background: var(--surface-2); }
.prize__img {
  display: block;
  width: 100%; height: 100%;
  min-height: 170px; max-height: 240px;
  object-fit: cover;
}
.prize__img--empty {
  display: grid; place-items: center;
  color: var(--text-muted); font-size: .85rem;
}
.prize__body {
  padding: 20px 22px;
  display: flex; flex-direction: column; justify-content: center;
  min-width: 0;
}
.prize__title {
  font-size: 1.65rem; margin: 0 0 14px;
  letter-spacing: -.03em; line-height: 1.15;
  overflow-wrap: anywhere;
}
.prize .price { margin-bottom: 0; }
/*
  The two small caps labels on the ticket page's prize card -- "CLASS" over the
  title and "REACHED" over the draw list -- carry the same size as everywhere
  else by default, which at that letter-spacing reads as fine print next to the
  title and the price they introduce. Only the ticket page is bumped: it is the
  card the customer sits with while picking numbers.
*/
.prize__body .eyebrow,
.prize__body .lots__title {
  font-size: .95rem;
  letter-spacing: .1em;
}

/*
  What each draw wins, under the price -- on the home hero and on the ticket
  page's prize card alike (views/partials/lots.ejs).

  A rule above it rather than a card of its own: it belongs to the card it sits
  in, and a second bordered box inside a bordered box only adds noise. The
  label and the prize are one flex row so a long prize name wraps under itself
  instead of pushing "1st draw" off the card.
*/
.lots {
  margin-top: 16px;
  padding-top: 14px;
  border-top: 1px solid var(--border);
}
.lots__title {
  margin: 0 0 8px;
  font-size: .74rem; font-weight: 800;
  letter-spacing: .12em; text-transform: uppercase;
  color: var(--success-text);
}
.lots__list { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; }
.lot {
  display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
  font-size: .95rem;
}
.lot__label { font-weight: 700; color: var(--text-muted); white-space: nowrap; }
.lot__prize { font-weight: 800; color: var(--text); overflow-wrap: anywhere; min-width: 0; }
/*
  On the home hero the list has the ticket-number panel under it, so it needs
  the same 20px the price above it leaves. On the ticket page it is the last
  thing in the card and the card's own padding closes it -- a margin there
  would read as a lopsided box.
*/
.hero__body .lots { margin-bottom: 20px; }

/* ── Photo slider (more than one photo in the upload folder) ── */
/*
  The stack, not a strip.

  Slides sit on top of one another and cross-fade, which is what lets the box
  keep one height however different the photos are -- a filmstrip that slides
  sideways has to agree on a width for every frame, and the organiser's folder
  never will. The height belongs to the slider rather than to the images, for
  the same reason: object-fit does the rest, so a portrait photo and a wide one
  both fill the same box instead of the card jumping as it advances.
*/
.slider {
  position: relative;
  overflow: hidden;
  background: var(--surface-2);
  /* Its own stacking context, so the controls layer inside it and nothing here
     can rise above the sticky navigation. */
  isolation: isolate;
}
.slider--hero { height: clamp(220px, 40vw, 420px); }
.slider--prize { height: 100%; min-height: 170px; max-height: 240px; }

.slider__viewport { position: absolute; inset: 0; }
.slider__slide {
  position: absolute; inset: 0;
  opacity: 0;
  transition: opacity .7s ease;
}
.slider__slide.is-current { opacity: 1; }
.slider__img { display: block; width: 100%; height: 100%; object-fit: cover; }

/*
  A slow drift across whichever photo is showing. Six seconds of 4% scale is
  under a pixel a frame -- felt rather than watched -- and it is what keeps a
  still photograph from looking like a page that has stopped loading.
*/
.slider__slide.is-current .slider__img { animation: slider-drift 6.5s ease-out forwards; }
@keyframes slider-drift {
  from { transform: scale(1); }
  to   { transform: scale(1.045); }
}

/* Enough shade at the bottom edge for white controls to stay legible over a
   bright photograph, and no more. Behind them, and never in the way of a swipe. */
.slider::after {
  content: '';
  position: absolute; left: 0; right: 0; bottom: 0;
  height: 72px;
  background: linear-gradient(to top, rgba(2, 6, 23, .45), rgba(2, 6, 23, 0));
  pointer-events: none;
}

.slider__nav {
  position: absolute; top: 50%;
  transform: translateY(-50%);
  width: 34px; height: 34px;
  display: grid; place-items: center;
  padding: 0; border: 0; border-radius: 999px;
  background: rgba(2, 6, 23, .45);
  color: #fff;
  cursor: pointer;
  opacity: 0;
  transition: opacity .18s ease, background-color .18s ease;
}
.slider__nav--prev { left: 10px; }
.slider__nav--next { right: 10px; }
.slider:hover .slider__nav,
.slider:focus-within .slider__nav { opacity: 1; }
.slider__nav:hover { background: rgba(2, 6, 23, .72); }
.slider__nav:focus-visible { opacity: 1; outline: 2px solid #fff; outline-offset: 2px; }
/*
  A finger has no hover to reveal them with, so on a touch screen the arrows
  are simply always there -- a little quieter, since the swipe is the real
  control and these are the fallback.
*/
@media (hover: none) {
  .slider__nav { opacity: .8; }
}

.slider__dots {
  position: absolute; left: 0; right: 0; bottom: 12px;
  display: flex; justify-content: center; gap: 6px;
  padding: 0 12px;
}
.slider__dot {
  width: 7px; height: 7px;
  padding: 0; border: 0; border-radius: 999px;
  background: rgba(255, 255, 255, .5);
  cursor: pointer;
  transition: width .25s ease, background-color .25s ease;
}
.slider__dot:hover { background: rgba(255, 255, 255, .8); }
/* The current one stretches rather than merely brightening, so which photo you
   are on is readable at a glance and without relying on colour. */
.slider__dot[aria-current="true"] { width: 20px; background: #fff; }
.slider__dot:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }

/* How long is left before it advances. The width is driven by slider.js, which
   is also what freezes it while the slider is paused. */
.slider__progress {
  position: absolute; left: 0; right: 0; bottom: 0;
  height: 3px;
  background: rgba(255, 255, 255, .18);
}
.slider__progressbar {
  display: block; height: 100%; width: 0;
  background: rgba(255, 255, 255, .85);
}

/*
  Asked for stillness, given stillness: no cross-fade, no drift, and -- in
  slider.js -- no autoplay at all, which is why the progress bar goes with it.
  The arrows and dots still work, so nothing is lost but the movement.
*/
@media (prefers-reduced-motion: reduce) {
  .slider__slide { transition: none; }
  .slider__slide.is-current .slider__img { animation: none; }
  .slider__progress { display: none; }
  .slider__dot { transition: none; }
}

/* ── Announcement panel (ticket number + countdown) ────────── */
.announce {
  display: flex; flex-wrap: wrap; gap: 18px;
  padding: 18px 20px;
  margin: 0 0 20px;
  background: var(--accent-panel);
  border-color: var(--accent-panel-border);
  box-shadow: none;
}
.announce[hidden] { display: none; }

.announce__item { flex: 1 1 180px; min-width: 0; }
.announce__item[hidden] { display: none; }

.announce__label {
  display: block;
  font-size: .72rem; font-weight: 700;
  letter-spacing: .12em; text-transform: uppercase;
  color: var(--primary);
  margin-bottom: 6px;
}
.announce__value {
  display: block;
  font-size: 2rem; font-weight: 800;
  letter-spacing: -.03em;
  font-variant-numeric: tabular-nums;
  color: var(--text);
  line-height: 1.1;
}

.countdown { display: flex; gap: 10px; align-items: flex-end; }
.countdown__seg { display: flex; flex-direction: column; align-items: center; }
.countdown__seg[hidden] { display: none; }
.countdown__n {
  font-size: 1.85rem; font-weight: 800;
  letter-spacing: -.03em; line-height: 1.1;
  font-variant-numeric: tabular-nums;
  color: var(--text);
}
.countdown__u {
  font-size: .68rem; font-style: normal; font-weight: 600;
  letter-spacing: .08em; text-transform: uppercase;
  color: var(--text-muted);
}
.countdown--done .countdown__n { color: var(--danger); }

/* ── Result: the minute countdown and the winner ───────────── */
/*
  One panel, four states, switched by data-status on .result and by [hidden] on
  the three blocks inside it. `idle` hides the panel outright -- there is no
  such thing here as a timer parked at 00:00.

  position: relative + overflow: hidden because the confetti is thrown inside
  this box and must not spill across the page or add a scrollbar to it.
*/
.result {
  position: relative;
  overflow: hidden;
  padding: 20px 22px;
  margin: 0 0 20px;
  background: var(--accent-panel);
  border-color: var(--accent-panel-border);
  box-shadow: none;
}
.result[hidden] { display: none; }
.result[data-status="drawn"] {
  /* The one state that gets its own look: a result is the loudest thing this
     page ever has to say. Dark ground, light text, in both themes. */
  background: linear-gradient(135deg, #4c1d95 0%, #6d28d9 45%, #db2777 100%);
  border-color: transparent;
  box-shadow: var(--shadow);
}

.result__timer > .announce__label { margin-bottom: 8px; }

.countdown--result .countdown__n { font-size: 2.4rem; }
.countdown--result { gap: 6px; }
.countdown__seg--colon .countdown__n { opacity: .45; }

/* ── The winner card ───────────────────────────────────────── */
.winner {
  position: relative;
  z-index: 1;               /* above the confetti layer */
  text-align: center;
  padding: 8px 4px 4px;
  color: #fff;
}
.winner[hidden] { display: none; }

.winner__cheer {
  margin: 0 0 14px;
  font-size: 1.15rem; font-weight: 800;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: #fde68a;
}
.winner__label {
  margin: 0 0 6px;
  font-size: .72rem; font-weight: 700;
  letter-spacing: .16em; text-transform: uppercase;
  color: rgba(255, 255, 255, .78);
}
.winner__number {
  margin: 0 auto 18px;
  display: inline-block;
  padding: 10px 26px;
  border-radius: var(--radius);
  background: rgba(255, 255, 255, .14);
  border: 2px solid rgba(255, 255, 255, .4);
  font-size: 3.4rem; font-weight: 900;
  line-height: 1.05;
  letter-spacing: -.02em;
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
  text-shadow: 0 2px 18px rgba(0, 0, 0, .35);
}
.winner__name {
  margin: 0 0 6px;
  font-size: 1.45rem; font-weight: 800;
  letter-spacing: -.01em;
  overflow-wrap: anywhere;
}
.winner__sub {
  margin: 0;
  font-size: .95rem;
  color: rgba(255, 255, 255, .82);
}
.winner__sub[hidden] { display: none; }

/*
  The card's arrival. Applied by result.js once per result, never on every
  state frame -- the stream resends the same winner every thirty seconds and
  nobody wants that bouncing at them.
*/
.winner--pop { animation: winner-pop .7s cubic-bezier(.2, .9, .3, 1.4) both; }
.winner--pop .winner__number { animation: winner-flash 1.6s ease-in-out .25s 2; }

@keyframes winner-pop {
  0%   { transform: scale(.86); opacity: 0; }
  60%  { transform: scale(1.04); opacity: 1; }
  100% { transform: scale(1); opacity: 1; }
}
@keyframes winner-flash {
  0%, 100% { box-shadow: 0 0 0 0 rgba(253, 230, 138, 0); }
  50%      { box-shadow: 0 0 0 12px rgba(253, 230, 138, .22); }
}

/* ── Drew nothing ──────────────────────────────────────────── */
.result__empty { text-align: center; padding: 6px 0; }
.result__empty[hidden] { display: none; }
.result__empty-mark { font-size: 1.8rem; display: block; margin-bottom: 8px; }
.result__empty-title {
  margin: 0 0 6px;
  font-size: 1.1rem; font-weight: 800;
  color: var(--text);
}

/* ── Confetti ──────────────────────────────────────────────── */
/*
  Built from elements by result.js, which sets --x, --dur, --delay and the rest
  through the CSSOM. That matters: the app's CSP has no 'unsafe-inline', so a
  style *attribute* would be refused, while a property set this way is not.

  pointer-events: none so the celebration can never swallow a tap.
*/
.confetti {
  position: absolute; inset: 0;
  overflow: hidden;
  pointer-events: none;
  z-index: 0;
}
.confetti__bit {
  position: absolute;
  top: -8%;
  left: var(--x, 50%);
  width: var(--w, 8px);
  height: var(--h, 14px);
  background: var(--c, #fbbf24);
  opacity: 0;
  animation: confetti-fall var(--dur, 3s) linear var(--delay, 0s) 1 forwards;
}
.confetti__bit--round { border-radius: 50%; }

@keyframes confetti-fall {
  0%   { transform: translate3d(0, -20px, 0) rotate(0deg); opacity: 1; }
  85%  { opacity: 1; }
  100% {
    transform: translate3d(var(--drift, 0), 340px, 0) rotate(var(--spin, 360deg));
    opacity: 0;
  }
}

/*
  Nothing moves for a visitor who has asked for less motion. result.js skips
  building the confetti at all; this covers the card animation and anything
  already on screen when the setting changes.
*/
@media (prefers-reduced-motion: reduce) {
  .winner--pop,
  .winner--pop .winner__number,
  .confetti__bit { animation: none; }
  .confetti__bit { opacity: 0; }
}

/* ── Sales closed ──────────────────────────────────────────── */
.closed { text-align: left; }
.closed__mark {
  display: grid; place-items: center;
  width: 52px; height: 52px;
  margin-bottom: 16px;
  border-radius: 50%;
  background: var(--warn-soft);
  font-size: 1.5rem;
}

/* ── Steps ─────────────────────────────────────────────────── */
.steps { margin-top: 32px; }
.steps__list { list-style: none; margin: 0; padding: 0; display: grid; gap: 12px; }
.step {
  display: flex; gap: 14px; align-items: flex-start;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 16px 18px;
}
.step__n {
  flex: none;
  display: grid; place-items: center;
  width: 28px; height: 28px;
  border-radius: 50%;
  background: var(--primary-soft); color: var(--primary);
  font-weight: 700; font-size: .85rem;
}
.step p { margin: 4px 0 0; }

/* ── Page head ─────────────────────────────────────────────── */
.pagehead {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 20px; flex-wrap: wrap; margin-bottom: 18px;
}
.pagehead__price { display: flex; align-items: baseline; gap: 8px; }
.pagehead__price .price__amount { font-size: 1.5rem; }

/* ── Legend + toolbar ──────────────────────────────────────── */
.legend {
  display: flex; flex-wrap: wrap; gap: 16px;
  margin-bottom: 14px; font-size: .84rem; color: var(--text-muted);
}
.legend__item { display: inline-flex; align-items: center; gap: 7px; }
.swatch {
  width: 15px; height: 15px; border-radius: 5px;
  border: 1px solid var(--border); display: inline-block;
}
.swatch--free { background: var(--surface); }
.swatch--sel  { background: var(--primary); border-color: var(--primary); }
.swatch--mine { background: var(--success); border-color: var(--success); }
.swatch--sold { background: var(--danger); border-color: var(--danger); }

.toolbar {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; flex-wrap: wrap; padding: 14px 16px;
}
.toolbar__search { display: flex; gap: 8px; flex: 1 1 260px; max-width: 380px; }
.toggle {
  display: inline-flex; align-items: center; gap: 8px;
  font-size: .88rem; color: var(--text-muted); cursor: pointer; user-select: none;
}
.toggle input { width: 16px; height: 16px; accent-color: var(--primary); }

/* ── Block tabs ────────────────────────────────────────────── */
.blocks {
  display: flex; gap: 8px; flex-wrap: wrap;
  margin-bottom: 16px;
}
.block {
  padding: 7px 13px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface);
  font: inherit; font-size: .82rem; font-weight: 600;
  color: var(--text-muted); cursor: pointer;
  transition: background-color .15s, color .15s, border-color .15s;
}
.block:hover { border-color: var(--border-strong); color: var(--text); }
.block.is-active { background: var(--primary); border-color: var(--primary); color: var(--primary-ink); }

/* ── Ticket grid ───────────────────────────────────────────── */
.grid-wrap { padding: 16px; }
.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(58px, 1fr));
  gap: 7px;
}
.grid__empty { text-align: center; color: var(--text-muted); padding: 28px 0; margin: 0; }

.cell {
  padding: 9px 2px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  font: inherit; font-size: .84rem; font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--text);
  cursor: pointer;
  transition: background-color .12s, color .12s, border-color .12s, transform .06s;
}
.cell:hover:not(:disabled) { border-color: var(--primary); color: var(--primary); }
.cell:active:not(:disabled) { transform: scale(.94); }
.cell:focus-visible { outline: 2px solid var(--primary); outline-offset: 1px; }

.cell--selected {
  background: var(--primary); border-color: var(--primary); color: var(--primary-ink);
}
.cell--selected:hover { background: var(--primary-hover); color: var(--primary-ink); }

.cell--sold {
  background: var(--danger); border-color: var(--danger); color: var(--danger-ink);
  cursor: not-allowed; opacity: .85;
  text-decoration: line-through;
}
.cell--mine {
  background: var(--success); border-color: var(--success); color: var(--success-ink);
  cursor: not-allowed;
}
.cell--flash { animation: flash 1.6s ease-out; }

@keyframes flash {
  0%, 100% { box-shadow: 0 0 0 0 rgba(79, 70, 229, 0); }
  20%      { box-shadow: 0 0 0 5px rgba(79, 70, 229, .45); }
  60%      { box-shadow: 0 0 0 5px rgba(79, 70, 229, .25); }
}

/* ── Sticky selection summary ──────────────────────────────── */
body.has-summary { padding-bottom: 118px; }

.summary {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 30;
  margin: 0;
  background: var(--surface);
  border-top: 1px solid var(--border);
  box-shadow: var(--shadow-up);
}
.summary__inner {
  max-width: 1080px; margin: 0 auto;
  padding: 14px 20px;
  display: flex; align-items: center; justify-content: space-between;
  gap: 18px; flex-wrap: wrap;
}
.summary__info { min-width: 0; flex: 1 1 240px; }
.summary__line { margin: 0; font-size: .86rem; color: var(--text-muted); }
.summary__sep { margin: 0 6px; }
.summary__list {
  display: inline-block; max-width: 100%;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  vertical-align: bottom;
}
.summary__total { margin: 2px 0 0; font-size: 1.15rem; font-weight: 700; letter-spacing: -.02em; }
.summary__total strong { color: var(--primary); }
.summary__actions { display: flex; gap: 10px; align-items: center; }

/* ── Chips, totals, payment details ────────────────────────── */
.chips { list-style: none; display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 20px; padding: 0; }
.chip {
  padding: 6px 12px;
  border-radius: 999px;
  background: var(--primary-soft); color: var(--primary);
  font-size: .85rem; font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.chip--won { background: var(--success-soft); color: var(--success-text); }
.chips--lg .chip { font-size: 1rem; padding: 8px 16px; }

.totals { margin: 0; }
.totals__row {
  display: flex; align-items: baseline; justify-content: space-between; gap: 16px;
  padding: 10px 0;
  border-bottom: 1px solid var(--border);
}
.totals__row:last-child { border-bottom: none; }
.totals__row dt { color: var(--text-muted); font-size: .9rem; }
.totals__row dd { margin: 0; font-weight: 600; text-align: right; }
.totals__row--grand dt { color: var(--text); font-weight: 600; }
.totals__row--grand dd { font-size: 1.2rem; font-weight: 800; color: var(--primary); letter-spacing: -.02em; }

.pay__row { display: flex; justify-content: space-between; gap: 16px; padding-bottom: 14px; }
.pay__label { color: var(--text-muted); font-size: .9rem; }
.pay__value { font-weight: 700; }
.pay__method {
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 13px 15px;
  margin-bottom: 10px;
}
.pay__method-name {
  margin: 0 0 4px;
  font-size: .76rem; font-weight: 700;
  letter-spacing: .08em; text-transform: uppercase; color: var(--text-muted);
}
.pay__number {
  margin: 0;
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  font-size: 1.1rem; font-weight: 700;
  font-variant-numeric: tabular-nums; letter-spacing: .02em;
  word-break: break-all;
}

/* ── Receipt preview ───────────────────────────────────────── */
.preview {
  display: block; width: 100%; max-width: 320px;
  margin-top: 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}
.preview--shown { max-width: 100%; }

.reveal { margin-top: 8px; }
.reveal summary {
  cursor: pointer; font-size: .88rem; font-weight: 600; color: var(--primary);
}

/* ── Confirmation + account ────────────────────────────────── */
.success { text-align: left; }
.success__mark {
  display: grid; place-items: center;
  width: 52px; height: 52px;
  margin-bottom: 16px;
  border-radius: 50%;
  background: var(--success-soft); color: var(--success);
  font-size: 1.6rem; font-weight: 700;
}

.order__head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 16px; margin-bottom: 14px;
}
.order__id { margin: 0; font-weight: 700; }
.order__head .muted { margin: 2px 0 0; }

.badge {
  padding: 5px 11px;
  border-radius: 999px;
  font-size: .74rem; font-weight: 700;
  letter-spacing: .06em; text-transform: uppercase;
  white-space: nowrap;
}
.badge--pending     { background: var(--warn-soft); color: var(--warn); }
.badge--approved    { background: var(--success-soft); color: var(--success-text); }
.badge--disapproved { background: var(--danger-soft); color: var(--danger-text); }

/* ── Toast ─────────────────────────────────────────────────── */
.toast {
  position: fixed; left: 50%; bottom: 24px; z-index: 60;
  transform: translate(-50%, 16px);
  max-width: min(520px, calc(100vw - 32px));
  padding: 12px 18px;
  border-radius: var(--radius-sm);
  background: var(--toast-bg); color: var(--toast-text);
  font-size: .9rem; line-height: 1.4;
  box-shadow: var(--shadow-toast);
  opacity: 0; pointer-events: none;
  transition: opacity .2s, transform .2s;
}
.toast.is-shown { opacity: 1; transform: translate(-50%, 0); }
body.has-summary .toast { bottom: 130px; }

/* ── Responsive ────────────────────────────────────────────── */
@media (max-width: 640px) {
  .shell { padding: 20px 14px 56px; }
  .card--pad { padding: 18px; }
  .hero__body { padding: 20px 18px 24px; }
  .hero__title { font-size: 1.55rem; }
  .price__amount { font-size: 1.7rem; }
  .grid { grid-template-columns: repeat(auto-fill, minmax(50px, 1fr)); gap: 6px; }
  .prize { grid-template-columns: 1fr; }
  .prize__img { max-height: 190px; min-height: 0; }
  /* The slider has no image to take its height from, so it carries the same
     190px cap the still photo has here. */
  .slider--prize { height: 190px; min-height: 0; max-height: 190px; }
  .slider--hero { height: clamp(200px, 52vw, 300px); }
  .slider__nav { width: 30px; height: 30px; }
  .prize__body { padding: 16px 18px; }
  .prize__title { font-size: 1.35rem; margin-bottom: 10px; }
  .result { padding: 16px; }
  .countdown--result .countdown__n { font-size: 2rem; }
  .winner__number { font-size: 2.6rem; padding: 8px 18px; }
  .winner__name { font-size: 1.2rem; }
  .winner__cheer { font-size: 1rem; }
  .announce { padding: 14px 16px; gap: 14px; }
  .announce__value { font-size: 1.6rem; }
  .countdown__n { font-size: 1.5rem; }
  .countdown { gap: 8px; }
  .cell { padding: 8px 1px; font-size: .78rem; }
  .summary__inner { padding: 12px 14px; }
  .summary__actions { width: 100%; }
  .summary__actions .btn { flex: 1; }
  body.has-summary { padding-bottom: 150px; }
  body.has-summary .toast { bottom: 162px; }
}

/* ── Header on a narrow screen ─────────────────────────────── */
/*
  The bar was laying itself out as one wrapping row, which on a phone (and in
  the Telegram sheet, which is narrower still) left the links breaking around
  the globe button at whatever point they happened to run out of space -- one
  link stranded on a second line, nothing aligned to anything.

  So below this width the bar becomes two deliberate rows: identity on top,
  navigation underneath. The links become a segmented pill that spans the full
  width, which gives them an obvious shape instead of a ragged edge, and sizes
  itself to however long the words happen to be in the chosen language.
*/
@media (max-width: 600px) {
  .nav__inner {
    padding: 9px 12px;
    row-gap: 9px;
    column-gap: 8px;
  }

  .nav__brand { font-size: .98rem; gap: 8px; }
  .nav__logo, img.nav__logo { width: 30px; height: 30px; }

  .langswitch__button { padding: 6px 9px; font-size: .8rem; }
  .langswitch__current { max-width: 108px; overflow: hidden; text-overflow: ellipsis; }

  /*
    Row two, full width. `order` rather than a source change so the markup
    keeps reading brand -> switcher -> links on every screen.
  */
  .nav__links {
    order: 3;
    width: 100%;
    flex-wrap: nowrap;
    gap: 4px;
    padding: 4px;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: 999px;
    /*
      The safety valve. Three long words in Afaan Oromoo cannot overflow into a
      broken second line here -- worst case the pill scrolls sideways, which is
      still a straight edge. The bar is hidden because it would sit on top of
      the pill's own rounding.
    */
    overflow-x: auto;
    scrollbar-width: none;
  }
  .nav__links::-webkit-scrollbar { display: none; }

  .nav__links > * { flex: 1 1 auto; min-width: 0; }

  .nav__link {
    padding: 8px 10px;
    border-radius: 999px;
    font-size: .85rem;
    text-align: center;
    white-space: nowrap;
  }
  .nav__link.is-active {
    background: var(--surface);
    color: var(--primary);
    box-shadow: 0 1px 2px rgba(15, 23, 42, .12);
  }

  /* The log-out / create-account button matches the pill segments beside it. */
  .nav__links .btn,
  .nav__logout .btn {
    width: 100%;
    padding: 8px 10px;
    border-radius: 999px;
    font-size: .85rem;
    white-space: nowrap;
  }
}

/* A dark bar wants its own elevation, not a grey one that vanishes. */
@media (max-width: 600px) {
  :root[data-theme="dark"] .nav__link.is-active,
  :root.tg-dark .nav__link.is-active {
    box-shadow: 0 1px 2px rgba(0, 0, 0, .5);
  }
}
@media (max-width: 600px) and (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .nav__link.is-active {
    box-shadow: 0 1px 2px rgba(0, 0, 0, .5);
  }
}

@media (max-width: 380px) {
  .grid { grid-template-columns: repeat(auto-fill, minmax(44px, 1fr)); }
}

@media (prefers-reduced-motion: reduce) {
  * { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}

/* ── Telegram Mini App ─────────────────────────────────────── */
/*
  Only ever active inside Telegram: telegram.js puts .in-telegram on <html>
  after Telegram itself confirms the page is running as a Mini App. In an
  ordinary browser none of this applies.
*/

/* Signing in covers the page, so the login form is never even glimpsed. */
.tg-cover {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: grid;
  place-items: center;
  padding: 24px;
  background: var(--bg, #f5f6f8);
}
.tg-cover__box {
  max-width: 320px;
  text-align: center;
}
.tg-cover__spinner {
  width: 34px;
  height: 34px;
  margin: 0 auto 16px;
  border: 3px solid var(--spinner-track);
  border-top-color: var(--primary, #2563eb);
  border-radius: 50%;
  animation: tg-spin .9s linear infinite;
}
.tg-cover__spinner[hidden] { display: none; }
.tg-cover__text {
  margin: 0;
  font-size: .96rem;
  font-weight: 600;
  color: var(--text, #111827);
}
.tg-cover__actions { margin-top: 16px; }

@keyframes tg-spin { to { transform: rotate(360deg); } }

@media (prefers-reduced-motion: reduce) {
  .tg-cover__spinner { animation-duration: 2.4s; }
}

/*
  Telegram draws its own header above the page and, on some clients, a home
  indicator below it. Keep the app's own chrome clear of both.
*/
.in-telegram body { padding-bottom: env(safe-area-inset-bottom, 0); }
.in-telegram .nav__inner { padding-top: calc(12px + env(safe-area-inset-top, 0px)); }

/*
  No log-out button inside Telegram. Signing in happens automatically from the
  Telegram account on every open, so the button could only ever sign somebody
  out for the half-second it takes to sign them straight back in.
*/
.in-telegram .nav__logout { display: none; }

/* ── Language switcher ─────────────────────────────────────── */
/*
  A <details> menu, so it opens and closes with no JavaScript: the switcher is
  the one control a customer who cannot read the current language still has to
  be able to work, which rules out anything that depends on the bundle loading.
*/
.langswitch { position: relative; }
.langswitch__button {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 10px; border-radius: var(--radius-sm);
  border: 1px solid var(--border); background: var(--surface);
  color: var(--text-muted);
  font-size: .85rem; font-weight: 600;
  cursor: pointer;
  list-style: none; /* no disclosure triangle */
  user-select: none;
}
/* Safari draws the marker with its own pseudo-element. */
.langswitch__button::-webkit-details-marker { display: none; }
.langswitch__button:hover { background: var(--surface-2); color: var(--text); }
.langswitch__button:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
.langswitch__globe { font-size: .95rem; line-height: 1; }
.langswitch__current { white-space: nowrap; }
.langswitch[open] .langswitch__button { background: var(--primary-soft); color: var(--primary); }

.langswitch__menu {
  position: absolute; top: calc(100% + 6px); right: 0; z-index: 40;
  min-width: 160px; margin: 0; padding: 6px;
  list-style: none;
  background: var(--surface);
  border: 1px solid var(--border); border-radius: var(--radius);
  box-shadow: var(--shadow);
}
.langswitch__option {
  display: block; padding: 8px 10px; border-radius: var(--radius-sm);
  text-decoration: none; color: var(--text);
  font-size: .9rem; font-weight: 600; white-space: nowrap;
}
.langswitch__option:hover { background: var(--surface-2); }
.langswitch__option.is-active { background: var(--primary-soft); color: var(--primary); }

/* The auth pages have no nav bar, so the brand and the switcher share a row. */
.auth__top {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  margin-bottom: 18px;
}
.auth__top .auth__brand { margin: 0; }
.auth__top .langswitch__button {
  background: rgba(255, 255, 255, .12);
  border-color: rgba(255, 255, 255, .25);
  color: #fff;
}
.auth__top .langswitch__button:hover { background: rgba(255, 255, 255, .2); color: #fff; }

/*
  "Buy Tickets" in the navigation -- the one link that is a button.

  Warm on purpose. Every other colour in this app is indigo (primary), green
  (an offer) or grey (a link), so amber-to-orange belongs to nothing else and
  cannot be mistaken for one of them; it is also the pairing that stays loud on
  both grounds this bar is drawn on -- white in the light palette, near-navy in
  the dark one and inside Telegram.

  The label is near-black rather than white. White on orange is about 2.9:1,
  which fails at this size; #3a1a02 on the same fill is over 8:1, and a dark
  label on a hot fill is what a "do this now" button looks like anyway.

  One shared definition for both palettes: these are literal colours, not
  tokens, because the whole point is that they do not move when the theme does.
*/
.nav__link--buy {
  position: relative;
  display: inline-flex; align-items: center; gap: 6px;
  padding: 8px 15px;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(135deg, #fbbf24 0%, #fb923c 55%, #f97316 100%);
  color: #3a1a02;
  font-weight: 800;
  letter-spacing: .01em;
  white-space: nowrap;
  box-shadow: 0 2px 10px rgba(249, 115, 22, .38);
  transition: background-image .15s, box-shadow .15s, transform .06s;
  animation: buy-pulse 2.6s ease-in-out infinite;
}
/*
  Hover, focus and "you are already here" all keep the fill. A CTA that goes
  quiet on the page it leads to is a CTA that looks broken -- and on /tickets
  the link still does something, it jumps to the numbers.
*/
.nav__link--buy:hover,
.nav__link--buy.is-active,
.nav__link--buy.is-active:hover {
  background: linear-gradient(135deg, #fcd34d 0%, #fb923c 50%, #ea580c 100%);
  color: #3a1a02;
  box-shadow: 0 3px 14px rgba(249, 115, 22, .5);
}
.nav__link--buy:hover { transform: translateY(-1px); }
.nav__link--buy:active { transform: translateY(0); box-shadow: 0 1px 6px rgba(249, 115, 22, .4); }
.nav__link--buy:focus-visible { outline: 2px solid #f97316; outline-offset: 2px; }

/*
  A slow halo rather than anything that moves the button. A control that grows
  and shrinks under a thumb is hard to hit on a phone, and this bar is sticky --
  whatever it does, it does for the whole visit, in the corner of the eye.
*/
@keyframes buy-pulse {
  0%, 100% { box-shadow: 0 2px 10px rgba(249, 115, 22, .38); }
  50%      { box-shadow: 0 2px 18px rgba(249, 115, 22, .62); }
}
@media (prefers-reduced-motion: reduce) {
  .nav__link--buy { animation: none; }
  .nav__link--buy:hover { transform: none; }
}

/*
  Where that button lands on the ticket page. Without the margin the sticky
  navigation sits on top of the heading it just jumped to.
*/
#pick { scroll-margin-top: 76px; }

/*
  "Invite friends", first in the navigation and lit like it.

  It used to be a quiet tinted link. It is now a filled emerald pill with a
  halo that breathes, because it is the one thing on the site a customer can
  do that costs them nothing and earns them a ticket -- and nobody presses a
  link they read past.

  Green against the orange of Buy Tickets: the two loudest controls in the bar
  stay told apart at a glance, and neither can be mistaken for the other under
  a thumb. Literal colours, like that button, so they do not move with the
  theme -- the same pill on white, on near-navy, and inside Telegram.

  The near-black label is the same choice made there for the same reason:
  white on emerald is around 2.5:1 and fails at this size, while #03291d on it
  is over 8:1.

  The halo runs half a cycle behind the buy button's (2.6s / 2 = 1.3s), so the
  two pulse in turn rather than together -- two controls throbbing in unison
  read as one flashing bar.
*/
.nav__link--invite {
  position: relative;
  display: inline-flex; align-items: center; gap: 6px;
  padding: 8px 15px;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(135deg, #6ee7b7 0%, #34d399 55%, #10b981 100%);
  color: #03291d;
  font-weight: 800;
  letter-spacing: .01em;
  white-space: nowrap;
  box-shadow: 0 2px 10px rgba(16, 185, 129, .42);
  transition: background-image .15s, box-shadow .15s, transform .06s;
  animation: invite-glow 2.6s ease-in-out infinite;
  animation-delay: 1.3s;
}
/* Hover, focus and "you are already here" all keep the fill, as on Buy Tickets. */
.nav__link--invite:hover,
.nav__link--invite.is-active,
.nav__link--invite.is-active:hover {
  background: linear-gradient(135deg, #a7f3d0 0%, #34d399 50%, #059669 100%);
  color: #03291d;
  box-shadow: 0 3px 14px rgba(16, 185, 129, .55);
}
.nav__link--invite:hover { transform: translateY(-1px); }
.nav__link--invite:active { transform: translateY(0); box-shadow: 0 1px 6px rgba(16, 185, 129, .4); }
.nav__link--invite:focus-visible { outline: 2px solid #10b981; outline-offset: 2px; }

@keyframes invite-glow {
  0%, 100% { box-shadow: 0 2px 10px rgba(16, 185, 129, .42); }
  50%      { box-shadow: 0 2px 20px rgba(16, 185, 129, .70); }
}
@media (prefers-reduced-motion: reduce) {
  .nav__link--invite { animation: none; }
  .nav__link--invite:hover { transform: none; }
}

/* ── Invitations and the free ticket ───────────────────────── */
/*
  One card, used in two places: the account page (invite link and progress) and
  the ticket grid (the claim). It borrows the accent panel the rest of the site
  uses for "this is a good thing", rather than introducing a colour of its own.
*/
.invite {
  background: var(--accent-panel);
  border-color: var(--accent-panel-border);
}
.invite__title { margin-top: 0; }

/* The link and its Copy button: side by side, stacking on a narrow phone. */
.invite__row { display: flex; gap: 8px; flex-wrap: wrap; }
.invite__input {
  flex: 1 1 240px;
  min-width: 0;              /* so a long URL shrinks instead of overflowing */
  background: var(--surface);
  font-size: .88rem;
}
.invite__copy { flex: 0 0 auto; }

/*
  Share, the primary action on the card. It opens Telegram's own forward sheet,
  which is one tap versus copy-then-find-a-chat, so Copy steps down to the ghost
  treatment beside the field.

  There was a quiet website address under this button once -- the answer to "my
  friend is not on Telegram". Invitations are Telegram invitations now, so it is
  gone, and with it the .linklike copy button it was written in.
*/
.invite__share { margin-top: 4px; }

.invite__unlocked { margin-top: 18px; margin-bottom: 0; }
.invite__unlocked p { margin: 6px 0 0; }
.invite__unlocked .mt-lg { margin-top: 14px; }

/* Three pips: how many paid referrals are counted towards the next reward. */
.invite__meter { display: flex; gap: 8px; margin: 4px 0 10px; }
.invite__pip {
  flex: 1 1 0;
  height: 8px;
  border-radius: 999px;
  background: var(--border-strong);
}
.invite__pip.is-filled { background: var(--success); }
.invite__next { margin: 0; }

/*
  The claim, on the ticket page. The summary bar keeps its layout and only
  swaps which button is shown -- but the running total belongs to a purchase,
  and there is no money in this one, so it goes.
*/
.summary--free .summary__total { display: none; }

/*
  Amharic is written in Ethiopic, which the default system-ui stack renders
  only by luck. Naming the faces that actually carry the script -- one per
  platform, none of them downloaded -- keeps the text sharp instead of leaving
  it to a fallback that may have no Ethiopic glyphs at all. Latin is unaffected:
  every name below is ahead of, not instead of, the original stack.
*/
html[lang="am"] body {
  font-family: "Noto Sans Ethiopic", "Nyala", "Abyssinica SIL", "Kefa", var(--font);
}
