/* tokens.css — Design tokens for the /p membership system (CSS variables).
   Mirrors the main-site space theme (style.css :root). No new tokens.
   Source of truth: WEB_REQUIREMENTS §2 / Figma EawTef80UGQ46xbqSNZEPL. */

:root {
  /* Backgrounds */
  --bg-primary:    #07071A;
  --bg-secondary:  #141527;
  --bg-elevated:   #1C1E35;
  --navbar-bg:     #080818;
  --bottom-bg:     #030410;
  --dropdown-bg:   #0E0E22;

  /* Accents */
  --accent-gold:   #F4A140;
  --accent-teal:   #13CDCB;
  --like-red:      #E8435A;

  /* Text */
  --text-primary:  #FFFFFF;
  --text-secondary:#8B8FA8;

  /* Lines / surfaces */
  --border:        rgba(255, 255, 255, 0.08);

  /* Typography (Thai + Latin share Noto Sans Thai per main site) */
  --font-thai:  'Noto Sans Thai', sans-serif;
  --font-latin: 'Noto Sans Thai', sans-serif;

  /* Layout */
  --navbar-h: 56px;
  --bottom-h: 64px;

  /* Responsive breakpoint (MOBILE_STRATEGY §B2: single breakpoint).
     < 768px = mobile, >= 768px = desktop. Use in media queries as 768px. */
  --bp-mobile: 768px;

  /* Spring-physics easing (Feel Lab reference spec: stiffness 170, damping
     16, mass 1) — a damped-harmonic-oscillator curve sampled at 25 points
     over 450ms and encoded as a CSS `linear()` easing function, instead of
     a hand-picked "back out" bezier. Gives modal/sheet entrances a real
     settle-with-overshoot feel. Falls back to the browser default (~ease)
     in browsers without `linear()` support (Chrome 113+/Safari 17.4+/
     Firefox 112+) since an unparsed value is simply ignored. */
  --ease-spring: linear(0, 0.027, 0.0969, 0.1952, 0.3093, 0.4293, 0.5476, 0.6584, 0.7581, 0.8443, 0.9162, 0.9738, 1.0178, 1.0495, 1.0704, 1.0823, 1.0869, 1.0858, 1.0806, 1.0726, 1.0629, 1.0526, 1.0422, 1.0323, 1);

  /* Shared space-themed gradient (2026-07-08 restore) — every /p page's own
     `body` rule layers this under --bg-primary: `background: var(--space-gradient), var(--bg-primary);`.
     A solid-only fallback had briefly replaced this everywhere while fixing an
     unrelated white-flash-during-load bug; that fix only needed a non-white
     *color*, not the loss of the gradient art. Paired with starfield.js's
     twinkling stars below, now applied site-wide (2026-07-10 — was
     myPPW-only). */
  /* 2026-07-27 (Feel Lab ref) — extended from 2 corner points to 4 (a real
     mesh gradient) by adding a faint teal bottom-left + gold top-right pair
     at lower opacity than the original two. Same 2 accent colors only, no
     new palette additions. */
  --space-gradient:
    radial-gradient(ellipse 600px 420px at 50% 0%, rgba(19, 205, 203, 0.10), transparent 65%),
    radial-gradient(ellipse 500px 400px at 100% 100%, rgba(244, 161, 64, 0.08), transparent 60%),
    radial-gradient(ellipse 450px 350px at 0% 100%, rgba(19, 205, 203, 0.06), transparent 60%),
    radial-gradient(ellipse 400px 320px at 100% 0%, rgba(244, 161, 64, 0.05), transparent 55%);
}

/* Grain overlay (2026-07-27, Feel Lab ref) — SVG feTurbulence noise at very
   low opacity to soften the space-gradient's color bands, not a decorative
   texture (kept low enough to be imperceptible at a glance). Covers every
   /p page since tokens.css is the one stylesheet all of them load. */
body::before {
  content: '';
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  opacity: 0.03;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='100'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* Body-level font base (2026-07-10 fix) — every OTHER page's own body rule
   (my.css) sets this, but p/index.html (login/signup) never loads my.css;
   its own content still rendered in Thai fine because auth.css scopes
   font-family to `.auth-page`, not `body`. That gap only became visible once
   the Concert Guide popup could be opened from every page's nav — its modal
   is appended directly to `document.body` (outside `.auth-page`), so on
   this one page it fell through to the browser's default font instead of
   Noto Sans Thai. tokens.css is the only stylesheet every /p page loads
   (including this one), so the base belongs here, not in my.css. */
body { font-family: var(--font-thai); }

/* ── Shared loading spinner (2026-07-07) ─────────────────────────────────
   Every /p page loads this file, so one definition covers the whole system
   (Admin Console included). Use `.ppw-loading` (spinner + text row) for a
   page/section's initial data-fetch state, or bare `.ppw-spinner` inline
   (e.g. on a submit button) while an action is in flight. */
@keyframes ppw-spin { to { transform: rotate(360deg); } }
.ppw-spinner {
  display: inline-block;
  width: 16px; height: 16px;
  border: 2px solid rgba(255, 255, 255, 0.2);
  border-top-color: var(--accent-gold);
  border-radius: 50%;
  animation: ppw-spin 0.7s linear infinite;
  flex-shrink: 0;
}
.ppw-loading {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  color: var(--text-secondary);
  font-size: 14px;
  padding: 40px 0;
}
/* Separate class for the text only (2026-07-10) — was a bare text node, so
   the opacity fade below couldn't target it without also dimming the
   spinner icon beside it. */
.ppw-loading-text { opacity: 0.8; }

/* Stuck-loading recovery state (2026-07-13, see boot-timeout.js) — swapped
   in only after a page's initial spinner has already auto-reloaded once
   and is still stuck, so this stacks vertically instead of the normal
   spinner+text row to make room for the retry button. */
.ppw-loading--stuck { flex-direction: column; gap: 14px; }
.ppw-retry-btn {
  background: var(--accent-gold);
  color: var(--bg-primary);
  border: none;
  border-radius: 8px;
  padding: 10px 24px;
  font-family: var(--font-thai);
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
}
.ppw-retry-btn:hover { opacity: 0.9; }

/* ── Space-vibe starfield (mountStarfield(), starfield.js) — decorative
   only, z-index:0/pointer-events:none, never affects layout, colors, or
   content. Moved here from my.css (2026-07-10) — now mounted on every /p
   page (login, myPPW, Goods Space, Soul Lucky), not just myPPW, so it needs
   a home every page loads; tokens.css is the only stylesheet linked
   everywhere. find.js instead mounts it scoped inside .find-map — see the
   .is-scoped override in find.css, which wins on specificity over the
   unscoped position:fixed rule below regardless of stylesheet order. */
#p-starfield, #p-shooting-stars { position: fixed; inset: 0; pointer-events: none; z-index: 0; overflow: hidden; }

.p-star {
  position: absolute;
  border-radius: 50%;
  background: #fff;
  /* Base opacity matches the animation's own low point — without this, a
     star sits at the browser's default opacity:1 (solid white dot, looking
     like a dead pixel) for its whole animation-delay before p-twinkle
     actually starts (2026-07-08 fix). */
  opacity: var(--opacity-lo, 0.2);
  animation: p-twinkle var(--twinkle-dur, 3s) var(--twinkle-delay, 0s) infinite ease-in-out;
}
@keyframes p-twinkle {
  0%, 100% { opacity: var(--opacity-lo, 0.2); transform: scale(1); }
  50%      { opacity: var(--opacity-hi, 0.9); transform: scale(1.15); }
}

.p-shooting-star {
  position: absolute;
  width: 100px; height: 1px;
  background: linear-gradient(90deg, rgba(255,255,255,0) 0%, rgba(255,255,255,0.85) 60%, rgba(255,255,255,0) 100%);
  opacity: 0;
  animation: p-shoot var(--dur, 5s) var(--delay, 0s) infinite ease-in;
}
@keyframes p-shoot {
  0%   { opacity: 0; transform: translate(0, 0) rotate(var(--ang, -30deg)); }
  3%   { opacity: 1; }
  25%  { opacity: 0; transform: translate(var(--dx, 300px), var(--dy, 150px)) rotate(var(--ang, -30deg)); }
  100% { opacity: 0; transform: translate(var(--dx, 300px), var(--dy, 150px)) rotate(var(--ang, -30deg)); }
}

/* Real flag <img> (flag-icon.js) — replaces Unicode flag emoji everywhere,
   since Windows' Segoe UI Emoji has no flag glyphs (2026-07-09 fix). Moved
   here from auth.css (2026-07-10) — flag-icon.js is used on pages that don't
   load auth.css (e.g. admin-country.js), so the one rule needs a home every
   page actually loads; tokens.css is the only stylesheet linked everywhere. */
.flag-icon { border-radius: 2px; object-fit: cover; vertical-align: middle; flex-shrink: 0; }

/* Fluent Emoji <img> (emojiIcon()/emojiEl(), emoji-icons.js at the repo root)
   — replaces literal emoji characters everywhere in /p, same reasoning and
   same fix location as .flag-icon just above: it must render at text size
   (1em), not the source SVG's native ~32-48px, and this is the one
   stylesheet every /p page actually loads (2026-07-15 fix — this rule
   already existed in the main site's style.css, but /p never loads that file). */
/* transform: translateZ(0) (2026-07-27 ask) — promotes each icon to its own
   GPU compositing layer, independent of any ancestor's `backdrop-filter`
   (navbar, dropdowns, glass buttons — all over /p). Works around an iOS
   WebKit bug where content painted inside a `backdrop-filter: blur()`
   container (worse combined with `position: sticky`, exactly `.ppw-nav`'s
   case) gets rasterized at reduced fidelity along with the blur itself —
   macOS Safari is unaffected, matching every report so far. A null
   transform, so it has zero visual/layout effect on its own. */
.emoji-icon { width: 1em; height: 1em; vertical-align: -0.125em; display: inline-block; transform: translateZ(0); }

/* Shared search-box clear ("×") button (search-clear.js, 2026-07-26) — every
   /p search input across the public pages and Admin Console uses this same
   button, so one definition here (the one stylesheet every /p page loads)
   keeps them visually identical. */
.search-clear-btn {
  position: absolute;
  right: 8px;
  top: 50%;
  transform: translateY(-50%);
  width: 20px;
  height: 20px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: transparent;
  color: var(--text-secondary);
  cursor: pointer;
}
.search-clear-btn[hidden] { display: none; }
.search-clear-btn svg { width: 12px; height: 12px; display: block; }
.search-clear-btn:hover { color: var(--text-primary); background: rgba(255,255,255,0.08); }

/* ── Shared pagination (2026-07-07) — admin-shell.js's list pages AND the
   public Marketplace grid (market.js) both build this via pagination.js,
   so one definition here keeps them visually + behaviorally identical. */
.ppw-pagination {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 12px 4px 0;
  font-size: 12px;
  color: var(--text-secondary);
  flex-wrap: wrap;
}
.ppw-pagination__controls { display: flex; align-items: center; gap: 8px; }
.ppw-pagination__page { font-size: 12px; color: var(--text-secondary); min-width: 72px; text-align: center; }
.ppw-pagination__btn {
  width: 30px; height: 30px;
  display: flex; align-items: center; justify-content: center;
  background: rgba(255,255,255,0.04);
  border: 1px solid var(--border);
  border-radius: 8px;
  color: var(--text-primary);
  font-size: 14px;
  cursor: pointer;
}
.ppw-pagination__btn:disabled { opacity: 0.35; cursor: default; }
.ppw-pagination__select {
  background: rgba(255,255,255,0.04);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 6px 8px;
  color: var(--text-primary);
  font-family: inherit;
  font-size: 12px;
}
.ppw-pagination__select option { background: var(--bg-elevated); color: var(--text-primary); }

/* ── Floating "back to top" button (2026-07-07, restyled same day —
   subtle/translucent by request, not an attention-grabbing accent color) ── */
.ppw-back-to-top {
  position: fixed;
  right: 16px;
  bottom: 20px;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  display: flex; align-items: center; justify-content: center; /* centers the SVG glyph (2026-07-15 ask — was the plain "↑" character) */
  background: rgba(255, 255, 255, 0.12);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  color: rgba(255, 255, 255, 0.75);
  border: 1px solid rgba(255, 255, 255, 0.15);
  font-size: 14px;
  font-weight: 700;
  cursor: pointer;
  box-shadow: 0 4px 14px rgba(0,0,0,0.3);
  opacity: 0;
  pointer-events: none;
  /* translateZ(0) appended (2026-07-27) — same iOS backdrop-filter
     compositing fix as .emoji-icon above; this button's icon is a raw
     inline SVG (ARROW_UP_SVG, scroll-top.js), not an .emoji-icon, so it
     isn't covered by that rule. Must stay combined with the existing
     translateY (not a separate `transform` declaration) since this is a
     single `transform` property — a later duplicate would silently
     override it, dropping the fix; the .is-visible override below keeps
     the same combined form for the same reason. */
  transform: translateY(8px) translateZ(0);
  transition: opacity 0.2s ease, transform 0.2s ease, background 0.15s ease;
  z-index: 300;
}
.ppw-back-to-top.is-visible { opacity: 1; pointer-events: auto; transform: translateY(0) translateZ(0); }
.ppw-back-to-top:hover { background: rgba(255, 255, 255, 0.2); }

/* ── Toast notifications (toast.js) ──────────────────────────────────────
   Duplicated into style.css for the main site — same convention as this
   file's scrollbar rule going the other direction (TROUBLESHOOTING_BIBLE
   §6.11). Replaces alert() sitewide (2026-07-25 ask) with a non-blocking,
   auto-dismissing notice. z-index 9999 — above nav.css's 1000/1001 and
   guide-modal.css's 1200 (the highest prior z-index anywhere in /p), so a
   toast is never hidden behind a modal. Every /p page loads this file. */
.ppw-toast-host {
  position: fixed;
  left: 50%;
  bottom: 24px;
  transform: translateX(-50%);
  z-index: 9999;
  display: flex;
  flex-direction: column-reverse;
  gap: 8px;
  align-items: center;
  pointer-events: none;
  max-width: calc(100vw - 32px);
}
.ppw-toast {
  pointer-events: auto;
  cursor: pointer;
  padding: 16px 22px;
  border-radius: 12px;
  /* A light frosted-glass tint (2026-07-26 fix) — the previous dark navy
     fill (rgba(28,30,53,...)) was too close to the page's own near-black
     background/space-gradient, so the toast barely stood out despite the
     blur. Lighter than the page reads as a distinct floating card instead. */
  background: rgba(255, 255, 255, 0.10);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  border: 1px solid rgba(255, 255, 255, 0.18);
  border-left: 5px solid var(--text-secondary);
  color: var(--text-primary);
  font-family: var(--font-thai);
  font-size: 14px;
  font-weight: 600;
  line-height: 1.5;
  box-shadow: 0 14px 36px rgba(0, 0, 0, 0.45);
  opacity: 0;
  transform: translateY(12px);
  transition: opacity 0.2s ease, transform 0.2s ease;
  max-width: 420px;
}
.ppw-toast.is-in { opacity: 1; transform: translateY(0); }
/* Type glow (2026-07-26 ask: "more prominent") — a soft colored glow on top
   of the base drop shadow, tinted per type, on every toast sitewide. */
.ppw-toast--error { border-left-color: var(--like-red); box-shadow: 0 14px 36px rgba(0, 0, 0, 0.45), 0 0 28px rgba(232, 67, 90, 0.28); }
.ppw-toast--success { border-left-color: #4FD187; box-shadow: 0 14px 36px rgba(0, 0, 0, 0.45), 0 0 28px rgba(79, 209, 135, 0.28); }
.ppw-toast--info { border-left-color: var(--accent-teal); box-shadow: 0 14px 36px rgba(0, 0, 0, 0.45), 0 0 28px rgba(19, 205, 203, 0.28); }

/* ── Shared scrollbar (2026-07-07, restyled same day) — neutral/translucent
   so it recedes into each page's own theme instead of announcing itself in
   accent-gold everywhere. Every /p page loads this file. */
* { scrollbar-width: thin; scrollbar-color: rgba(255,255,255,0.18) transparent; }
*::-webkit-scrollbar { width: 10px; height: 10px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb {
  background: rgba(255,255,255,0.18); border-radius: 999px; border: 2px solid transparent; background-clip: padding-box;
}
*::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.32); background-clip: padding-box; }
