/* Festai design tokens — the ONE place a colour, radius or shadow is decided.
 *
 * Loaded by BOTH shells (participant base.html and admin/base.html), after their vendor
 * stylesheet so it wins. Before this file the product wore two different brands: the
 * participant side was default-Bootstrap blue and the admin side was default-Vuexy purple,
 * which is what made the same app look like two half-finished ones.
 *
 * The palette IS Vuexy's, deliberately. The admin side already ships it in core.css, so
 * adopting it on the participant side costs nothing, cannot drift, and makes the whole
 * product one brand. Everything below is either that palette or a darkened member of it
 * chosen to clear 4.5:1 as text.
 */

:root {
  /* Brand ---------------------------------------------------------------------------- */
  --fest-primary: #7367f0;          /* fills, buttons, active states */
  --fest-primary-600: #6355e8;
  --fest-primary-700: #5346c9;      /* 6.80:1 on white — the shade TEXT and links use */
  --fest-primary-soft: #eeecfd;     /* tinted surface */

  --fest-accent: #ff9f43;           /* festival warmth: podium, live pills, highlights */
  --fest-accent-ink: #9a5a05;       /* 4.94:1 on white */
  --fest-accent-soft: #fff2e3;

  --fest-success: #28c76f;
  --fest-success-ink: #1a8148;      /* 4.51:1 on #f4f5fa, 4.91:1 on white — computed
                                       against the DARKER of the two grounds it sits on.
                                       #1a8a4c cleared white at 4.53 and then measured
                                       4.03 on the participant page, which is the whole
                                       reason quirk 36 says to measure the ground. */
  --fest-success-fill: #1b874b;     /* white label 4.55:1 — the FILLED button */
  --fest-success-fill-dark: #197b45;
  --fest-success-soft: #e3f8ed;

  --fest-danger: #ff4c51;
  --fest-danger-ink: #c9282d;       /* 5.32:1 on white */
  --fest-danger-fill: #d43f43;      /* white label 4.57:1 — the FILLED button */
  --fest-danger-fill-dark: #c43b3e;
  --fest-danger-soft: #ffe9ea;
  --fest-warning-ink: #8a5a00;     /* 5.44:1 on --fest-surface-sunk, the picker's ground */

  --fest-info: #00bad1;
  --fest-info-ink: #0b7c8c;
  --fest-info-soft: #e0f7fa;

  /* Ink and surface ------------------------------------------------------------------ */
  --fest-ink: #2f2b3d;              /* Vuexy's body colour — headings and body text */
  /* Must clear 4.5:1 on BOTH the white card AND the tinted 'you' row (#eeecfd),
     where a highlighted participant's secondary line sits. #6f6b7d measured 5.15
     on white but only 4.43 on the tint, which is where axe caught it. */
  --fest-ink-muted: #676375;        /* 5.80:1 on white, 4.99:1 on --fest-primary-soft */
  --fest-ink-faint: #a5a3ae;        /* decorative only, never body text */
  --fest-surface: #fff;
  --fest-surface-sunk: #f4f5fa;     /* the page behind the cards */
  --fest-border: #e4e4e8;

  /* The projector's dark ground, shared by DISPLAY and the participant app bar so the
     room's two screens read as the same product. */
  --fest-night: #16192c;
  --fest-night-2: #1f2340;
  --fest-night-ink: #f2f3f8;

  /* Shape ---------------------------------------------------------------------------- */
  --fest-radius-sm: .375rem;
  --fest-radius: .625rem;
  --fest-radius-lg: 1rem;
  --fest-radius-pill: 999px;

  /* One shadow scale. Bootstrap's default .shadow-sm is a hairline that reads as a
     rendering artefact at phone density; these are soft and slightly warm instead. */
  --fest-shadow-sm: 0 1px 2px rgba(47, 43, 61, .06), 0 2px 6px rgba(47, 43, 61, .05);
  --fest-shadow: 0 2px 4px rgba(47, 43, 61, .06), 0 8px 20px rgba(47, 43, 61, .08);
  --fest-shadow-lg: 0 6px 12px rgba(47, 43, 61, .08), 0 18px 40px rgba(47, 43, 61, .12);

  /* Type ----------------------------------------------------------------------------- */
  --fest-font: "Public Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
    "Helvetica Neue", Arial, sans-serif;
  /* Tabular figures matter on every screen this app has: a scoreboard, a countdown and a
     "12 iš 20" all jitter when the digits are proportional. */
  --fest-nums: "Public Sans", ui-monospace, sans-serif;
}

/* --- Bootstrap remap ---------------------------------------------------------------
 * The participant side runs stock Bootstrap 5, which does not know about the tokens
 * above. Rather than restyle every component, point Bootstrap's own variables at them
 * once: .btn-primary, .text-primary, .form-control:focus, .badge and the rest follow.
 * The admin side already has these values from core.css, so this block is a no-op there.
 */
:root {
  --bs-primary: var(--fest-primary);
  --bs-primary-rgb: 115, 103, 240;
  --bs-link-color: var(--fest-primary-700);
  --bs-link-color-rgb: 83, 70, 201;
  --bs-link-hover-color: var(--fest-primary-600);
  --bs-body-color: var(--fest-ink);
  --bs-body-color-rgb: 47, 43, 61;
  --bs-secondary-color: var(--fest-ink-muted);
  --bs-border-color: var(--fest-border);
  --bs-body-font-family: var(--fest-font);
  --bs-border-radius: var(--fest-radius);
  --bs-border-radius-sm: var(--fest-radius-sm);
  --bs-border-radius-lg: var(--fest-radius-lg);
}

/* --- Shared primitives -------------------------------------------------------------
 * Small pieces both shells use. Anything that only one side needs lives in that side's
 * own stylesheet.
 */

/* The brand mark: a filled lozenge with the wordmark beside it. Used in the participant
   app bar, on the auth cards and in the admin sidebar, so the product is recognisable
   before a single word is read. */
.fest-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 1.75rem;
  height: 1.75rem;
  border-radius: .5rem;
  background: linear-gradient(135deg, var(--fest-primary) 0%, #9d7bff 100%);
  color: #fff;
  font-weight: 800;
  font-size: .95rem;
  line-height: 1;
  letter-spacing: -.02em;
}

.fest-wordmark {
  font-weight: 800;
  letter-spacing: -.02em;
  text-decoration: none;
  color: inherit;
}

/* An empty state is a designed screen, not a stranded sentence. Every "nothing here yet"
   in the app renders through this so they all look considered. */
.fest-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding: 2.5rem 1.25rem;
}

.fest-empty-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 3.5rem;
  height: 3.5rem;
  margin-bottom: 1rem;
  border-radius: var(--fest-radius-lg);
  background: var(--fest-primary-soft);
  color: var(--fest-primary-700);
}

.fest-empty-icon svg { width: 1.75rem; height: 1.75rem; }
.fest-empty-title { font-weight: 700; font-size: 1.0625rem; margin-bottom: .25rem; }
.fest-empty-body {
  color: var(--fest-ink-muted);
  font-size: .9375rem;
  line-height: 1.5;
  max-width: 30rem;
  margin-bottom: 0;
}

.fest-empty--warm .fest-empty-icon {
  background: var(--fest-accent-soft);
  color: var(--fest-accent-ink);
}

/* The stock file input renders "Choose Files / No file chosen" — in English, in a
   Lithuanian app, at whatever width the browser feels like. It is hidden and a real label
   is styled in its place; the input still receives the click, so keyboard focus and the
   no-JavaScript path both keep working. */
.fest-file { position: relative; }
.fest-file input[type="file"] {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  cursor: pointer;
}

.fest-file-face {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .5rem;
  padding: 1.5rem 1rem;
  border: 2px dashed var(--fest-border);
  border-radius: var(--fest-radius);
  background: var(--fest-surface-sunk);
  color: var(--fest-ink-muted);
  text-align: center;
}

.fest-file input[type="file"]:focus-visible + .fest-file-face,
.fest-file:hover .fest-file-face {
  border-color: var(--fest-primary);
  background: var(--fest-primary-soft);
  color: var(--fest-primary-700);
}

.fest-file-face strong { color: var(--fest-ink); font-size: .9375rem; }
.fest-file:hover .fest-file-face strong { color: var(--fest-primary-700); }
.fest-file-face small { font-size: .75rem; }

/* Numbers that must not jitter as they change: scores, countdowns, tallies. */
.fest-num { font-variant-numeric: tabular-nums; font-feature-settings: "tnum" 1; }

/* A live indicator. The pulse is the only motion in the product's chrome, and it is
   suppressed for anyone who asked for less of it. */
.fest-live-dot {
  display: inline-block;
  width: .5rem;
  height: .5rem;
  border-radius: 50%;
  background: var(--fest-success);
  box-shadow: 0 0 0 0 rgba(40, 199, 111, .6);
  animation: fest-pulse 2s infinite;
}

@keyframes fest-pulse {
  0% { box-shadow: 0 0 0 0 rgba(40, 199, 111, .6); }
  70% { box-shadow: 0 0 0 .5rem rgba(40, 199, 111, 0); }
  100% { box-shadow: 0 0 0 0 rgba(40, 199, 111, 0); }
}

@media (prefers-reduced-motion: reduce) {
  .fest-live-dot { animation: none; }
  * { scroll-behavior: auto !important; }
}

/* --- Bootstrap component repaint ----------------------------------------------------
 * Bootstrap 5.3 does NOT build its variant components from `--bs-primary`: the mixin bakes
 * literal hex into each one (`.btn-primary { --bs-btn-bg: #0d6efd }`). So remapping the
 * theme variable above recolours links and focus rings but leaves every filled primary
 * component on stock blue — which is what left the participant side wearing Bootstrap's
 * brand next to the admin side wearing ours.
 *
 * The primary, success and danger families are repainted. Warning keeps Bootstrap's own
 * shade: nothing renders it as a filled button with a label on top.
 */
/* The FILL is --fest-primary-600, not --fest-primary: white on #7367f0 measures 4.26:1,
   just under the 4.5:1 AA wants for a 13-16px button label (axe flagged every primary
   button on the product). #6355e8 is the same hue two steps down and measures 5.26:1.
   The brand colour itself is untouched — the mark, the active tab, the focus ring and the
   live dots still carry #7367f0, where nothing sits on top of them. */
.btn-primary {
  --bs-btn-bg: var(--fest-primary-600);
  --bs-btn-border-color: var(--fest-primary-600);
  --bs-btn-hover-bg: var(--fest-primary-700);
  --bs-btn-hover-border-color: var(--fest-primary-700);
  --bs-btn-active-bg: var(--fest-primary-700);
  --bs-btn-active-border-color: var(--fest-primary-700);
  --bs-btn-disabled-bg: var(--fest-primary-600);
  --bs-btn-disabled-border-color: var(--fest-primary-600);
  --bs-btn-focus-shadow-rgb: 115, 103, 240;
}

/* SUCCESS AND DANGER ARE FILLED BUTTONS NOW, so the note above them no longer holds. The
   buzzer's verdict pair ("Teisingai +1" / "Neteisingai −1") and the participant's chosen
   TIESA/MELAS all carry a white label on the raw hue, and axe measured them at 2.21:1 and
   3.27:1 — the same failure the primary family had, in the same place. Fills darkened
   until white clears 4.5:1; the raw hues stay where nothing sits on top of them (the
   .bg-label-* grounds, the live dots, the correct-answer tint). */
.btn-success {
  --bs-btn-bg: var(--fest-success-fill);            /* white 4.55:1 */
  --bs-btn-border-color: var(--fest-success-fill);
  --bs-btn-hover-bg: var(--fest-success-fill-dark);
  --bs-btn-hover-border-color: var(--fest-success-fill-dark);
  --bs-btn-active-bg: var(--fest-success-fill-dark);
  --bs-btn-active-border-color: var(--fest-success-fill-dark);
  --bs-btn-disabled-bg: var(--fest-success-fill);
  --bs-btn-disabled-border-color: var(--fest-success-fill);
}
.btn-danger {
  --bs-btn-bg: var(--fest-danger-fill);             /* white 4.57:1 */
  --bs-btn-border-color: var(--fest-danger-fill);
  --bs-btn-hover-bg: var(--fest-danger-fill-dark);
  --bs-btn-hover-border-color: var(--fest-danger-fill-dark);
  --bs-btn-active-bg: var(--fest-danger-fill-dark);
  --bs-btn-active-border-color: var(--fest-danger-fill-dark);
  --bs-btn-disabled-bg: var(--fest-danger-fill);
  --bs-btn-disabled-border-color: var(--fest-danger-fill);
}
/* The OUTLINE pair is a label on the page ground, not on a fill, so it takes the inks —
   which are computed against #f4f5fa, the participant page, not against white. */
/* The hover/active LABEL colour has to be set too, not just the ground. Vuexy's own block
   (vendor core.css) lands after this one on the admin stack and sets
   `--bs-btn-hover-color: var(--bs-success)`, so darkening the hover ground alone painted
   #28c76f on #1b874b — 2.10:1, worse than what was there before. An accessibility fix that
   introduces a contrast regression two selectors away is the failure mode quirk 36 is
   about: state the ink for every state you restyle. */
.btn-outline-success { --bs-btn-color: var(--fest-success-ink);
                       --bs-btn-border-color: var(--fest-success-ink);
                       --bs-btn-hover-bg: var(--fest-success-fill);
                       --bs-btn-hover-border-color: var(--fest-success-fill);
                       --bs-btn-hover-color: #fff;
                       --bs-btn-active-bg: var(--fest-success-fill);
                       --bs-btn-active-border-color: var(--fest-success-fill);
                       --bs-btn-active-color: #fff; }
.btn-outline-danger  { --bs-btn-color: var(--fest-danger-ink);
                       --bs-btn-border-color: var(--fest-danger-ink);
                       --bs-btn-hover-bg: var(--fest-danger-fill);
                       --bs-btn-hover-border-color: var(--fest-danger-fill);
                       --bs-btn-hover-color: #fff;
                       --bs-btn-active-bg: var(--fest-danger-fill);
                       --bs-btn-active-border-color: var(--fest-danger-fill);
                       --bs-btn-active-color: #fff; }

.btn-link { --bs-btn-color: var(--fest-primary-700); --bs-btn-hover-color: var(--fest-primary-600); }

.progress, .progress-stacked { --bs-progress-bar-bg: var(--fest-primary); }

.text-bg-primary {
  background-color: rgba(115, 103, 240, var(--bs-bg-opacity, 1)) !important;
  color: #fff !important;
}

.form-check-input:checked {
  background-color: var(--fest-primary);
  border-color: var(--fest-primary);
}

.form-check-input:focus,
.form-select:focus {
  border-color: var(--fest-primary);
  box-shadow: 0 0 0 .2rem rgba(115, 103, 240, .2);
}

.spinner-border, .spinner-grow { color: var(--fest-primary); }

/* The generic focus ring, for anything not covered above (links, buttons, tab stops). */
.btn:focus-visible, a:focus-visible {
  outline: 2px solid var(--fest-primary);
  outline-offset: 2px;
}

/* --- Name filter --------------------------------------------------------------------
 * Every long list in this app is a list of PEOPLE. The box sits above the list and hides
 * non-matching rows as you type. Shared by both shells: the organiser meets it on the
 * approval queue and the award grid, a guest meets it on the scoreboard.
 */
.fest-filter {
  position: relative;
  display: flex;
  align-items: center;
  max-width: 22rem;
}

.fest-filter-icon {
  position: absolute;
  left: .6875rem;
  display: flex;
  color: var(--fest-ink-muted);
  pointer-events: none;
}

.fest-filter-input {
  padding-left: 2.375rem;
  padding-right: 4.5rem;
}

/* Safari draws its own clear button on type=search and it collides with the count. */
.fest-filter-input::-webkit-search-cancel-button { display: none; }

.fest-filter-count {
  position: absolute;
  right: .75rem;
  font-size: .75rem;
  font-variant-numeric: tabular-nums;
  color: var(--fest-ink-muted);
  pointer-events: none;
}

/* A filter that empties the list must SAY it emptied the list — otherwise it reads as
   "there is nobody here", which is a different and alarming statement. */
/* --- List header: the heading (or the filter tabs) and the search on ONE row ----------
 * They are one control surface — you say WHICH list, then you narrow it — so a search on
 * its own line below cost a whole row above the table and read as an unrelated widget.
 * Search sits right, where a table's search sits.
 *
 * Lives in brand.css rather than admin.css because the participant scoreboard uses it too,
 * and the participant side never loads the admin stylesheet (quirk 6).
 *
 * Wraps to two lines when there is no room — four filter pills plus a readable input do not
 * fit under about 992px, and on a phone the participant board wraps at every real width,
 * which is the layout it had before. Stacking beats shrinking the placeholder into
 * "Ieškoti pagal…".
 *
 * Centred on each other, not bottom-aligned: a pill and an input are different heights, and
 * matching their BOTTOMS left the input reading as slightly raised above the tab strip.
 */
.fest-list-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: .75rem 1.5rem;
}

.fest-list-head .nav-pills { margin-bottom: 0; }

.fest-list-head .fest-filter {
  /* Shrinks before it wraps, but never below a readable placeholder. */
  flex: 0 1 22rem;
  min-width: 15rem;
}

/* The search must not sit on the table's first row. A card header carries its own bottom
 * padding, but the tab strips keep `pb-0` so the pills stay visually attached to the table
 * beneath them — which left the input touching it too.
 */
.fest-list-head.pb-0 { padding-bottom: .875rem !important; }

.fest-filter-empty {
  padding: 1.5rem 1rem;
  text-align: center;
  color: var(--fest-ink-muted);
}
