html, body {
  height: 100%;
  margin: 0;
  overflow: hidden;
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

/* ===================== Theme tokens =====================
   Duplicated from perk360-web/wwwroot/css/site.css - kiosk-web is an
   independently deployed wwwroot, so this can't be shared directly.
   KNOWN DRIFT POINT: a brand-palette change in perk360-web needs to be
   copied here by hand too. */
:root,
[data-bs-theme="light"] {
  --brand: #7C3AED;
  --brand-hover: #6425D6;
  --brand-light: #A78BFA;
  --brand-subtle: #F3EEFF;

  --surface: #FFFFFF;
  --surface-alt: #FAF9FD;
  --surface-raised: #FFFFFF;
  --text-body: #211D2C;
  --text-muted: #6B6478;
  --border-color: #E7E2F1;

  color-scheme: light;
}

[data-bs-theme="dark"] {
  --brand: #A78BFA;
  --brand-hover: #BBA3FC;
  --brand-light: #C4B5FD;
  --brand-subtle: #1C1626;

  --surface: #000000;
  --surface-alt: #121017;
  --surface-raised: #16131C;
  --text-body: #F1EDFB;
  --text-muted: #A79FC0;
  --border-color: #2A2438;

  color-scheme: dark;
}

/* ===================== Kiosk-only "boutique lobby" tokens =====================
   Ported from perk360-kiosk/Themes/{Light,Dark}Theme.xaml - a champagne-gold
   accent used sparingly (hero dividers, faint trellis texture), plus the
   radial hero background every screen sits on. perk360-web has no
   equivalent of these - they're specific to the kiosk's own richer,
   jewel-case presentation. */
:root, [data-bs-theme="light"] {
  --accent-gold: #A9822E;
  --hero-bg-1: #ECE6F9;
  --hero-bg-2: #F3EFFB;
  --hero-bg-3: #F7F5FB;
  --brand-glow: rgba(124, 58, 237, 0.45);
  --pattern-opacity: 0.07;
  --kiosk-shadow: rgba(138, 131, 152, 0.35);
  --warning: #B4670F;
}

[data-bs-theme="dark"] {
  --accent-gold: #D9BE7A;
  --hero-bg-1: #1C1730;
  --hero-bg-2: #100D19;
  --hero-bg-3: #000000;
  --brand-glow: rgba(167, 139, 250, 0.55);
  --pattern-opacity: 0.05;
  --kiosk-shadow: rgba(0, 0, 0, 0.4);
  --warning: #E0A45C;
}

body {
  background: var(--hero-bg-3);
  color: var(--text-body);
}

/* ===================== Boot splash =====================
   Covers the idle screen for a fixed 2s on load - see kiosk.js's boot
   sequence - so the tenant branding fetch (logo, custom swipe background)
   always resolves before the customer sees anything, instead of flashing
   the stock gradient/logo first. Sits above every screen (z-index) on the
   same solid ground color the hero gradient fades to, then fades out. */
#kiosk-splash {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 16px;
  background: var(--hero-bg-3);
  opacity: 1;
  transition: opacity 300ms ease;
}

#kiosk-splash.kiosk-splash-hide {
  opacity: 0;
  pointer-events: none;
}

#kiosk-splash .perk360-mark {
  width: 96px;
  height: 96px;
}

#kiosk-splash .perk360-name {
  font-size: 1.4rem;
}

/* Ported from App.xaml's implicit (app-wide) Button style: solid brand
   gradient, white text, 8px radius - overrides Bootstrap's default flat
   blue .btn-primary, which otherwise clashes with the rest of the
   violet/gold palette. */
.btn-primary {
  --bs-btn-bg: linear-gradient(135deg, var(--brand) 0%, var(--brand-light) 100%);
  background: linear-gradient(135deg, var(--brand) 0%, var(--brand-light) 100%);
  border: none;
  border-radius: 8px;
  font-weight: 600;
}

.btn-primary:hover,
.btn-primary:active,
.btn-primary:focus {
  background: var(--brand-hover);
  border: none;
}

.btn-primary:disabled {
  background: var(--border-color);
  color: var(--text-muted);
}

.tracked-caps {
  text-transform: uppercase;
  letter-spacing: 0.18em;
}

/* ===================== Device pairing (SignIn.cshtml) =====================
   A one-time setup screen (or a future WebView2 wrapper's automated POST) -
   deliberately plain, not styled like the customer-facing kiosk screens. */
.pairing-page {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}

.pairing-card {
  width: 100%;
  max-width: 420px;
  background: var(--surface-raised);
  border: 1px solid var(--border-color);
  border-radius: 12px;
  padding: 32px;
}

/* ===================== Screens =====================
   Every kiosk screen sits on the same radial "hero" background with a
   faint gold trellis texture layered over it - ported from
   HeroBackgroundBrush/LuxuryPatternBrush (see kiosk-only tokens above),
   not just a flat surface color. */
.screen {
  position: fixed;
  inset: 0;
  display: flex;
  flex-direction: column;
  background: radial-gradient(ellipse 90% 90% at 50% 42%, var(--hero-bg-1) 0%, var(--hero-bg-2) 55%, var(--hero-bg-3) 100%);
  opacity: 1;
  transition: opacity 220ms ease;
}

.screen::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  opacity: var(--pattern-opacity);
  background-image:
    repeating-linear-gradient(45deg, var(--accent-gold) 0, var(--accent-gold) 1px, transparent 1px, transparent 48px),
    repeating-linear-gradient(-45deg, var(--accent-gold) 0, var(--accent-gold) 1px, transparent 1px, transparent 48px);
}

/* Real content always paints above the pattern overlay, regardless of
   each screen's specific markup (::before isn't matched by this, so it
   only selects actual children). */
.screen > * {
  position: relative;
  z-index: 1;
}

.screen.screen-hidden {
  opacity: 0;
  pointer-events: none;
}

/* Tenant-configurable swipe/eligibility screen appearance (My Tenant's
   Kiosk Appearance card, see kiosk.js's applyTenantTheme) - --tenant-bg-
   image and --tenant-text-color are set as inline custom properties per
   element by JS, not here, since a value that's merely absent (rather
   than an explicit "none"/color) is what lets these two rules fall back
   to the default hero gradient/inherited text color untouched. The color
   rule applies unconditionally (safe either way, given the inherit
   fallback); the background-image rule is gated on .has-tenant-bg so an
   unset tenant background never overwrites .screen's own gradient with
   an empty value. Descendants that already set their own explicit color
   (most notably .brand-name/.perk360-name's gradient-clip wordmark) are
   never affected by the color rule below - inheritance only reaches
   elements that don't already have their own color. */
.screen-idle,
.screen-eligibility {
  color: var(--tenant-text-color, inherit);
}

.screen-idle.has-tenant-bg,
.screen-eligibility.has-tenant-bg {
  background-image: var(--tenant-bg-image);
  background-size: cover;
  background-position: center;
}

/* The gold trellis texture was designed to sit on the flat default
   gradient - layered over an actual tenant photo it would just look like
   noise, so it's dropped whenever a tenant background is active. */
.screen-idle.has-tenant-bg::before,
.screen-eligibility.has-tenant-bg::before {
  display: none;
}

.brand-header {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 20px 28px;
}

.brand-mark {
  width: 32px;
  height: 32px;
  border-radius: 8px;
  background: linear-gradient(135deg, var(--brand) 0%, var(--brand-light) 100%);
  flex-shrink: 0;
  background-size: cover;
  background-position: center;
}

.brand-mark.has-logo {
  background-image: var(--brand-logo-url);
  background-color: transparent;
}

.brand-name {
  font-weight: 700;
  letter-spacing: 0.05em;
  background: linear-gradient(135deg, var(--brand) 0%, var(--brand-light) 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* Idle screen's own header is the app's identity (Perk360), not the
   tenant's - the tenant's mark instead takes the prominent center spot
   (.idle-mark). Static image/text, never touched by loadBranding(). */
.perk360-mark {
  width: 32px;
  height: 32px;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}

.perk360-mark img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.perk360-name {
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  background: linear-gradient(135deg, var(--brand) 0%, var(--brand-light) 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* Fixed to the viewport (not laid out inline in .brand-header any more)
   so it stays put in the lower-left corner regardless of screen content -
   .screen > * normally forces position:relative for the trellis-pattern
   stacking order (see below), so this rule needs to win that override
   too, which it does by appearing later in the cascade at equal
   specificity. */
.theme-toggle {
  position: fixed;
  left: 28px;
  bottom: 28px;
  z-index: 2;
  border: none;
  background: var(--surface-raised);
  box-shadow: 0 4px 16px 0 var(--kiosk-shadow);
  font-size: 26px;
  color: var(--text-muted);
  cursor: pointer;
  width: 56px;
  height: 56px;
  border-radius: 50%;
}

.theme-toggle:hover {
  background: var(--surface-alt);
}

/* Ported from App.xaml's SecondaryButton style - Cancel/Done/Change PIN
   and other lower-emphasis actions that shouldn't compete with a
   screen's primary action. Default (unstyled) buttons elsewhere use
   Bootstrap's .btn-primary, matching App.xaml's implicit gradient
   Button style. */
.btn-secondary-kiosk {
  background: transparent;
  color: var(--text-body);
  font-weight: 600;
  border: 2px solid var(--border-color);
  border-radius: 8px;
  padding: 10px 24px;
  cursor: pointer;
}

.btn-secondary-kiosk:hover {
  border-color: var(--brand);
}

.btn-secondary-kiosk:disabled {
  opacity: 0.5;
  cursor: default;
}

/* ===================== Idle ===================== */
.idle-hero {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  gap: 8px;
  padding: 0 24px;
}

.idle-mark {
  width: 56px;
  height: 56px;
  border-radius: 50%;
  margin-bottom: 16px;
  background: linear-gradient(135deg, var(--brand) 0%, var(--brand-light) 100%);
  background-size: cover;
  background-position: center;
  /* Ported from IdleView.xaml's DropShadowEffect (BlurRadius 24, Opacity
     0.55) - a soft glow ring, not a flat medallion. */
  box-shadow: 0 0 24px 4px var(--brand-glow);
}

/* Once a real tenant logo loads, stop forcing it into the placeholder's
   circular crop - show the logo's own shape in full (contain, no
   clipping) rather than cover-cropping whatever art the tenant
   uploaded into a circle it was never designed for. */
.idle-mark.has-logo {
  background-image: var(--brand-logo-url);
  background-color: transparent;
  background-size: contain;
  background-repeat: no-repeat;
  border-radius: 0;
}

/* Tenant/brand name leads (see Index.cshtml), "Welcome" follows - the
   divider below both still marks the whole block as one deliberate
   boutique moment. */
.idle-tenant {
  color: var(--brand);
  font-weight: 700;
  font-size: 1.5rem;
  margin: 0;
}

.display-title {
  font-size: clamp(3.25rem, 7.5vw, 5.5rem);
  font-weight: 800;
  margin: 8px 0 0;
}

/* Ported from IdleView.xaml's divider: "the one place violet and gold
   meet" - marks the welcome heading as a deliberate boutique moment
   rather than just another line of text. */
.idle-divider {
  height: 2px;
  width: 120px;
  margin: 20px auto 0;
  border-radius: 1px;
  background: linear-gradient(90deg, var(--brand) 0%, var(--accent-gold) 100%);
}

.idle-subtitle {
  color: var(--text-muted);
  font-style: italic;
  font-size: 1.4rem;
}

/* Applied instead (see kiosk.js's applyTenantTheme) whenever the current
   theme has a tenant-configured swipe background and both the mark and
   #idle-welcome-text are hidden - with nothing else on the screen, this
   is sized up further still to read as the screen's own headline and
   pushed down toward a "lower-center" resting spot instead of sitting at
   dead center. */
.idle-subtitle-solo {
  font-size: 1.85rem;
  margin-top: 20vh;
}

/* Slow breathing fade, not a hard blink - draws the eye without being
   distracting on a screen nobody's actively interacting with yet. */
@keyframes idle-subtitle-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.25; }
}

.idle-subtitle-pulse {
  animation: idle-subtitle-pulse 4s ease-in-out infinite;
}

@media (prefers-reduced-motion: reduce) {
  .idle-subtitle-pulse {
    animation: none;
  }
}

.swipe-test-panel {
  margin-top: 32px;
  width: 320px;
}

/* ===================== Keypad screens (PIN / Change PIN) ===================== */
.screen-keypad {
  align-items: center;
  justify-content: center;
}

.keypad-panel {
  width: 340px;
  text-align: center;
}

.keypad-prompt {
  font-size: 1.2rem;
  font-weight: 600;
  margin-bottom: 12px;
}

.keypad-dots {
  min-height: 28px;
  font-size: 1.6rem;
  letter-spacing: 0.4em;
  margin-bottom: 8px;
}

.keypad-status {
  margin-bottom: 12px;
}

.keypad-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 12px;
  margin-bottom: 20px;
}

.keypad-grid button {
  padding: 18px 0;
  font-size: 1.4rem;
  border-radius: 12px;
  border: 1px solid var(--border-color);
  background: var(--surface-raised);
  color: var(--text-body);
}

.keypad-grid button:active {
  background: var(--brand-subtle);
}

.keypad-actions {
  display: flex;
  gap: 12px;
}

.keypad-actions .btn {
  flex: 1;
}

/* ===================== Eligibility =====================
   Ported from EligibilityView.xaml: a 280px "Customer Information"
   sidebar card (not a top bar) beside a scrollable list of elevated
   campaign cards, each holding its own flat perk rows. */
.screen-eligibility {
  overflow: hidden;
  display: block;
}

/* Card elevation - ported from App.xaml's CardBorder style (used for
   every raised panel: sidebar, campaign cards, keypad housing, voucher
   frame) so panels read as the same "floating glass/lacquer" design
   system across every screen, not ad hoc borders. */
.card-border {
  background: var(--surface-raised);
  border: 1px solid var(--border-color);
  border-radius: 14px;
  box-shadow: 0 8px 32px 0 var(--kiosk-shadow);
}

.brand-header-floating {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 2;
}

.eligibility-layout {
  height: 100%;
  display: grid;
  grid-template-columns: 280px 1fr;
  gap: 24px;
  padding: 64px 32px 24px;
  box-sizing: border-box;
}

.eligibility-sidebar {
  padding: 24px;
  align-self: start;
  height: fit-content;
}

.eligibility-sidebar-title {
  color: var(--brand);
  font-weight: 700;
  font-size: 1rem;
  margin-bottom: 16px;
}

.eligibility-sidebar .eligibility-name {
  font-size: 1.5rem;
  font-weight: 600;
  margin-bottom: 8px;
}

.eligibility-field {
  margin-top: 16px;
}

.field-caption {
  font-size: 0.8rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.field-body {
  font-size: 1.1rem;
  margin-top: 2px;
}

.eligibility-sidebar-changepin {
  width: 100%;
  margin-top: 24px;
}

.eligibility-main {
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.eligibility-title {
  font-size: 2rem;
  font-weight: 600;
  margin: 0 0 16px;
}

.eligibility-body {
  flex: 1;
  overflow-y: auto;
  min-height: 0;
}

.eligibility-empty {
  color: var(--text-muted);
  font-size: 1.5rem;
  text-align: center;
  margin-top: 60px;
}

.campaign-group {
  margin-bottom: 16px;
  padding: 20px;
}

.campaign-group-header {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 12px;
}

.campaign-group-image {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  object-fit: cover;
  flex-shrink: 0;
}

.campaign-group-title {
  font-weight: 700;
  font-size: 1.35rem;
}

.perk-card {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 16px;
  border-radius: 8px;
  background: var(--surface);
  margin-bottom: 8px;
}

.perk-card.perk-blocked {
  opacity: 0.6;
}

.perk-card-badge {
  width: 48px;
  height: 48px;
  border-radius: 50%;
  object-fit: cover;
  background: var(--surface-alt);
  flex-shrink: 0;
}

.perk-card-body {
  flex: 1;
  min-width: 0;
}

.perk-card-promotion {
  display: flex;
  align-items: center;
  gap: 4px;
  font-size: 0.8rem;
  color: var(--brand);
  font-weight: 600;
}

.perk-card-promotion img {
  width: 14px;
  height: 14px;
  border-radius: 3px;
}

.perk-card-name {
  font-weight: 600;
  font-size: 1.1rem;
  margin: 2px 0;
}

.perk-card-summary {
  color: var(--text-muted);
  font-size: 0.9rem;
}

.perk-card-block-reason {
  color: var(--warning);
  font-size: 0.85rem;
  margin-top: 4px;
}

.perk-progress {
  width: 200px;
  margin-top: 8px;
}

.perk-progress-bar {
  height: 8px;
  border-radius: 4px;
  background: var(--surface-alt);
  overflow: hidden;
}

.perk-progress-fill {
  height: 100%;
  background: linear-gradient(135deg, var(--brand) 0%, var(--brand-light) 100%);
}

.perk-progress-text {
  font-size: 0.75rem;
  color: var(--text-muted);
  margin-top: 2px;
}

.eligibility-actions {
  display: flex;
  justify-content: flex-end;
  padding-top: 16px;
}

/* ===================== Seating =====================
   The screen itself keeps the normal hero background - only the framed
   canvas box runs the opposite theme from the rest of the kiosk (see
   kiosk.js's syncSeatingTheme()), so the floor plan's own table/seat
   colors always sit on a flat, uncluttered surface instead of competing
   with the hero gradient, without blanking out the whole screen. */
.screen-seating {
  align-items: center;
  justify-content: center;
}

/* Replaces the tenant brand-header in the screen's own flow - the
   tenant mark moved to a fixed top-left corner (see
   .brand-header-floating below), so this center spot instead shows
   which promotion/perk the customer is picking a seat for. */
.seating-title {
  text-align: center;
  margin-bottom: 16px;
}

.seating-promotion {
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-size: 0.85rem;
  font-weight: 700;
  color: var(--brand);
}

.seating-perk {
  font-size: 1.5rem;
  font-weight: 600;
  margin-top: 2px;
}

.seating-status {
  color: var(--text-muted);
}

.seating-canvas-frame {
  border: 1px solid var(--border-color);
  border-radius: 10px;
  overflow: auto;
  max-width: 90vw;
  max-height: 60vh;
  background: var(--surface);
}

.seating-selection {
  min-height: 24px;
  color: var(--text-muted);
  margin-top: 12px;
}

.seating-actions {
  display: flex;
  gap: 12px;
  margin-top: 12px;
}

/* ===================== Claim / Voucher ===================== */
.claim-body {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  padding: 16px;
}

/* The voucher shows at its real size when it fits, and shrinks (never
   grows) when it doesn't - on a screen under ~800px tall the full-size
   voucher used to push Print/Done off the bottom of the (unscrollable)
   kiosk, leaving the customer stuck until the idle timeout. The wrap's
   height is set to the voucher's own (kiosk.js's fitVoucher) and may shrink
   as a flex item; the frame is scaled to whatever height it gets. */
.voucher-frame-wrap {
  position: relative;
  flex: 0 1 auto;
  min-height: 0;
  width: 100%;
}

#voucher-frame {
  position: absolute;
  top: 0;
  left: 50%;
  transform-origin: top center;
  transform: translateX(-50%);
  border: 1px solid var(--border-color);
  background: #fff;
}

.claim-code {
  font-weight: 700;
}

.claim-status {
  color: var(--text-muted);
  min-height: 20px;
}

.claim-actions {
  display: flex;
  gap: 12px;
}

/* ===================== Overlays ===================== */
.overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.55);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
}

.overlay-card {
  background: var(--surface-raised);
  color: var(--text-body);
  border-radius: 14px;
  padding: 28px;
  width: 380px;
  text-align: center;
  box-shadow: 0 20px 50px rgba(0, 0, 0, 0.3);
}

.overlay-card h2 {
  font-size: 1.2rem;
  margin-bottom: 12px;
}

.overlay-busy {
  background: rgba(0, 0, 0, 0.25);
}

.spinner {
  width: 48px;
  height: 48px;
  border-radius: 50%;
  border: 5px solid rgba(255, 255, 255, 0.3);
  border-top-color: #fff;
  animation: kiosk-spin 800ms linear infinite;
}

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

/* ===================== Seat chips (drawn on canvas via seating.js,
   color constants kept here as the single source of truth referenced by
   both) ===================== */
:root {
  --seat-available: seagreen;
  --seat-reserved: goldenrod;
  --seat-occupied: slategray;
  --seat-blocked: dimgray;
  --seat-selected: dodgerblue;
}

/* ===================== Print ===================== */
/* Isolates the voucher iframe itself, not #screen-claim as a whole -
   #screen-claim (like every .screen) carries its own full-viewport
   radial-gradient background plus a ::before trellis-pattern overlay,
   neither of which "body *" reaches (a bare element isn't its own
   descendant, and "*" never matches a pseudo-element), so toggling
   #screen-claim back to visible printed that background and pattern
   across the whole page with the voucher just a small rectangle inside
   it. #voucher-frame already carries explicit inline px width/height
   sized from VoucherWidthMm/HeightMm (see kiosk.js's showVoucher), so
   pinning it to the page origin is all the layout it needs - none of
   its ancestors' positioning matters once they're hidden. The actual
   paper size (so the printed sheet is the voucher's own size, not the
   printer's default with the voucher stranded in one corner) comes from
   a separate @page rule kiosk.js injects per voucher, mirroring perk360-
   web's own Views/Vouchers/Print.cshtml. */
@media print {
  body * {
    visibility: hidden;
  }

  body {
    background: #fff !important;
  }

  #voucher-frame,
  #voucher-frame * {
    visibility: visible;
  }

  /* width/height 100% (of the @page-sized page, not an absolute mm value)
     overrides kiosk.js's inline px sizing (!important beats an inline
     style attribute) - a normal browser print reflows this identically
     either way since 100% of a correctly @page-sized page equals that
     exact size, but perk360-kiosk-web-wapp's silent CoreWebView2.
     PrintAsync has been seen to get the outer page/paper size right
     while still capturing the content at a smaller size, leaving it
     stranded with dead space around it. Sizing relative to the page
     itself fills whatever page actually results, regardless of that
     mismatch. */
  #voucher-frame {
    position: fixed;
    top: 0;
    left: 0;
    width: 100% !important;
    height: 100% !important;
    transform: none !important; /* on-screen fit scaling never reaches paper */
    border: none;
  }
}
