/* my.css — /p/my dashboard (myPPW). Design source: Figma 192:3.
   Mobile-first (390 frame); fluid >=768 per MOBILE_STRATEGY §B2. Uses tokens.css. */

* { box-sizing: border-box; }
body { margin: 0; background: var(--space-gradient), var(--bg-primary); background-repeat: no-repeat; background-attachment: fixed; color: var(--text-primary); font-family: var(--font-thai); }

/* Starfield CSS (#p-starfield/.p-star/etc.) now lives in tokens.css
   (2026-07-10 — moved so every /p page can share it, not just this one). */

/* Page content must be an explicitly positioned/z-indexed stacking context
   to paint above the fixed z-index:0 starfield layers (same pattern the main
   site uses for its #page-layout) — plain static-position content would
   otherwise paint BEHIND a positioned z-index:0 sibling per CSS stacking
   rules, hiding the whole page under the stars. */
/* Subtracts --navbar-h (2026-07-08 fix): nav.js prepends the shared navbar
   directly to <body>, as a SEPARATE sibling before this page's own root —
   not inside it — so without the subtraction, total body height was always
   navbar-h taller than the viewport, forcing a redundant scroll on every
   load even when the dashboard's own content fit on one screen. dvh (not
   vh) also accounts for mobile browsers' collapsible address bar. */
/* display:flex column (2026-07-10) — lets the footer pin to the bottom of
   the viewport via margin-top:auto below when the dashboard is short. */
.my-page { position: relative; z-index: 1; display: flex; flex-direction: column; min-height: calc(100vh - var(--navbar-h)); min-height: calc(100dvh - var(--navbar-h)); }
.my-content { max-width: 720px; padding: 16px; display: flex; flex-direction: column; gap: 16px; }

/* Legal footer — appended as a sibling AFTER .my-content closes. margin-top:
   auto (2026-07-10) pins it to the bottom of the viewport when content is
   short, replacing the old fixed/negative margin-top tuning that only
   mattered for the overflow case (kept below via padding-bottom only). */
.my-page .lg-footer-link { margin-top: auto; padding-bottom: 20px; }

/* Layout groups — on mobile these are transparent (display:contents), so the
   section order/spacing is identical to a single flex column. */
.my-group, .my-bento-row, .my-bento-col { display: contents; }

/* Fan Benefits / Reminders patch slots (2026-07-11 sectional loading) — pure
   DOM anchors for patchFanBenefits() to swap contents into later; always
   display:contents (not just mobile) so they never introduce a layout box
   of their own — their children participate directly in the parent flex
   container's gap, exactly as if the slot wrapper didn't exist. */
.my-benefits-slot, .my-reminders-slot { display: contents; }

/* ── Desktop bento layout (MOBILE_STRATEGY §B2 breakpoint: 768px) ────────── */
@media (min-width: 768px) {
  .my-content {
    max-width: 1400px;
    margin: 0 auto;
    padding: 28px 24px;
    display: grid;
    /* Sidebar is a proportional %, clamped so it never gets unreadably
       narrow or unreasonably wide as the window resizes. (299px/30% = +15%) */
    grid-template-columns: minmax(299px, 30%) 1fr;
    align-items: start;
    gap: 24px;
  }
  .my-group--left {
    display: flex; flex-direction: column; gap: 16px;
    position: sticky; top: 24px;
    container-type: inline-size;   /* lets wallet cards react to actual column width */
  }
  /* Sidebar too cramped for the peek price chip — hide it there only.
     The expanded detail's price (.my-meta__price) is a different element
     and is never affected. */
  @container (max-width: 320px) {
    .my-ticket__price { display: none; }
  }
  .my-group--right { display: flex; flex-direction: column; gap: 24px; }
  .my-bento-col { display: flex; flex-direction: column; gap: 16px; min-width: 0; }
  /* Divider above Fan Benefits is redundant once columns visually separate it */
  .my-divider--benefits { display: none; }

  /* 768–1099px: sidebar + one stacked content column = 2 columns total.
     MD and Timeline stack (not enough room to sit side by side). */
  .my-bento-row { display: grid; grid-template-columns: 1fr; gap: 24px; }
}

/* >=1100px: enough room for sidebar + MD + Timeline side by side = 3 columns. */
@media (min-width: 1100px) {
  .my-content { grid-template-columns: minmax(322px, 25%) 1fr; }   /* 280px/22% +15% */
  .my-bento-row { grid-template-columns: 1fr 1fr; }
}

.my-state { padding: 48px 16px; text-align: center; color: var(--text-secondary); font-size: 13px; }
.my-state__link { display: inline-block; margin-top: 6px; color: var(--accent-gold); text-decoration: underline; cursor: pointer; }

/* Shown instead of the dashboard when the member has 0 tickets — centered
   dead in the middle of the page (not just top-of-page). */
.my-empty-wrap {
  min-height: calc(100vh - var(--navbar-h));
  min-height: calc(100dvh - var(--navbar-h));
  display: flex; align-items: center; justify-content: center;
  padding: 24px;
}
.my-empty-tickets {
  display: flex; flex-direction: column; align-items: center; gap: 14px;
  text-align: center; padding: 48px 36px; max-width: 420px;
  background:
    radial-gradient(circle at 50% 0%, rgba(var(--accent-gold-rgb),0.16), transparent 60%),
    var(--bg-secondary);
  border: 1px solid var(--border);
  border-radius: 22px;
  box-shadow: 0 24px 60px rgba(var(--shadow-rgb), calc(0.35 * var(--shadow-mult)));
}
.my-empty-tickets__icon {
  width: 84px; height: 84px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 50%;
  font-size: 42px;
  background: rgba(var(--accent-gold-rgb),0.14);
  box-shadow: 0 0 34px rgba(var(--accent-gold-rgb),0.28);
}
.my-empty-tickets__title { font-size: 21px; font-weight: 800; color: var(--text-primary); }
.my-empty-tickets__msg { font-size: 14px; color: var(--text-secondary); line-height: 1.6; }
.my-empty-tickets__cta {
  margin-top: 6px;
  display: inline-flex; align-items: center; gap: 6px;
  padding: 13px 26px;
  border: none; border-radius: 12px;
  /* Fixed gradient, not theme-following (2026-08-08 fix) — same drift-from-
     reference bug as p/homesafe.css's .hs-auth-gate__cta. */
  background: linear-gradient(120deg, #ffb852, #ed8420);
  color: #12121A;
  font: 700 14px var(--font-thai);
  text-decoration: none;
  cursor: pointer;
  box-shadow: 0 10px 24px rgba(var(--accent-gold-rgb),0.3);
  transition: filter 0.15s ease;
}
.my-empty-tickets__cta:hover { filter: brightness(1.06); }

/* Section divider — inserted before each new section heading. Constellation
   style: a soft center glow fading to transparent at both ends, plus a
   single star dot in the middle, instead of a flat uniform line. */
.my-divider {
  position: relative;
  height: 0;
  margin: 6px 0;
  border-top: 1px solid transparent;
  border-image: linear-gradient(90deg, transparent, var(--border) 20%, rgba(var(--accent-teal-rgb),0.35) 50%, var(--border) 80%, transparent) 1;
}
.my-divider::after {
  content: '';
  position: absolute;
  left: 50%; top: 50%;
  width: 3px; height: 3px;
  border-radius: 50%;
  background: var(--accent-teal);
  box-shadow: 0 0 6px 1px rgba(var(--accent-teal-rgb),0.7);
  transform: translate(-50%, -50%);
}
:root[data-theme="light"] .my-divider::after { background: #13CDCB; }

/* Greeting */
.my-greet { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.my-greet__avatar {
  position: relative;
  width: 40px; height: 40px; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  border-radius: 50%; font-size: 21px;   /* 18px +15% */
  background: var(--bg-elevated); border: 3px solid var(--accent-teal);
}
/* Slow-spinning dashed "orbit ring" around the avatar — purely decorative,
   sits just outside the avatar's own border, tinted to the user's identity
   color (falls back to teal like the avatar border itself). */
.my-greet__avatar::before {
  content: '';
  position: absolute;
  inset: -6px;
  border-radius: 50%;
  border: 1px dashed color-mix(in srgb, var(--avatar-color, var(--accent-teal)) 55%, transparent);
  animation: p-orbit-spin 14s linear infinite;
}
@keyframes p-orbit-spin { to { transform: rotate(360deg); } }
.my-greet__title { font-size: 18px; font-weight: 700; }   /* 16px +15% */
.my-greet__sub { font-size: 11px; color: var(--text-secondary); }

/* Edit Profile button — tinted to the user's own identity color (set inline
   via JS from profile.color). Sits inline (top-right) on mobile.
   Desktop layout (via `order` + a flex-wrap break): row 1 = avatar (left) +
   button (right, pushed by margin-left:auto); row 2 = greeting text, filling
   the space below the avatar. */
.my-edit-profile-btn {
  display: inline-flex; align-items: center; gap: 4px;
  align-self: flex-start;
  margin-left: auto;
  padding: 5px 13px;         /* 6px/14px -10% */
  border: none;
  border-radius: 999px;
  background: rgba(var(--accent-gold-rgb),0.14);
  color: var(--accent-gold);
  font: 600 11px var(--font-thai);   /* 12px -10% */
  cursor: pointer;
  white-space: nowrap;
}
/* Forces a flex-wrap line break right after the button — width:0 keeps it
   from taking visual space; only active at the desktop breakpoint. */
/* Row 1 = avatar + edit button, row 2 = greeting text (full width) — same
   layout on mobile as desktop (2026-08-08 ask) since long usernames wrapped
   awkwardly next to the avatar/button when this break was desktop-only. */
.my-greet__break { flex-basis: 100%; width: 0; height: 0; display: block; order: 2; }
.my-greet__avatar { order: 0; }
.my-edit-profile-btn { order: 1; margin-left: auto; }
.my-greet__text { order: 3; flex-basis: 100%; }

/* Card base — subtle nebula glow ties every card into the same space theme
   as the lucky-draw hero cards / reminders card (previously only those two
   had any gradient; plain cards looked like a generic dashboard). */
.my-card {
  position: relative;
  background:
    radial-gradient(ellipse 260px 140px at 100% 0%, rgba(var(--accent-teal-rgb),0.07), transparent 65%),
    radial-gradient(ellipse 220px 160px at 0% 100%, rgba(var(--accent-gold-rgb),0.05), transparent 65%),
    var(--bg-secondary);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 14px 16px;
}

/* Budget */
.my-budget__head { display: flex; justify-content: space-between; align-items: center; }
.my-budget__label { font-size: 13px; font-weight: 700; color: var(--text-secondary); }
.my-budget__edit { font-size: 12px; color: var(--accent-gold); cursor: pointer; text-decoration: none; }
.my-budget__total {
  font-size: 34px; font-weight: 700; margin: 0 0 8px;   /* 30px +15% */
  color: #2EE6C5;
  text-shadow: 0 0 18px rgba(46, 230, 197, 0.45);
}
/* Light mode (2026-08-07 ask: "ปรับสีข้อความและเงาให้เห็นชัดเจน ... แต่
   ยังสดใสอยู่") — the dark-mode teal-emerald measured 1.25:1 contrast
   against the light backgrounds, effectively invisible. Same hue/saturation,
   darkened to clear ~3.5:1 (the same floor used sitewide, see
   LIGHT_MODE_COLOR_BIBLE.md), not a token since this figure's color is
   unique to this one element. */
:root[data-theme="light"] .my-budget__total {
  color: #108772;
  text-shadow: 0 0 18px rgba(16, 135, 114, 0.35);
}
.my-budget__hr { height: 0; border-top: 1px solid var(--border); margin: 8px 0; }
.my-budget__row { display: flex; justify-content: space-between; font-size: 13px; padding: 3px 0; }
.my-budget__row-label { color: var(--text-secondary); }
.my-budget__note { font-size: 9.9px; color: var(--text-secondary); margin-top: 8px; }

/* Eye toggle (2026-07-09) — a normal flex item inside .my-budget__head
   (which is already justify-content:space-between), not absolutely
   positioned, so it always stays inside the card's own padding instead of
   floating past its edge. Teal-emerald tone (2026-07-09 follow-up) matches
   .my-budget__total's own color, tying the button visually to the figure
   it controls instead of the unrelated gold CTA tone. */
.my-budget__eye-btn {
  flex-shrink: 0;
  width: 30px; height: 30px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 50%;
  background: linear-gradient(135deg, #2EE6C5, #7bf5df);
  border: none;
  box-shadow: 0 3px 10px rgba(46, 230, 197, 0.45);
  font-size: 14px;
  cursor: pointer;
  transition: filter 0.15s ease, transform 0.15s ease;
}
.my-budget__eye-btn:hover { filter: brightness(1.08); transform: scale(1.06); }

/* Section header — scroll-margin-top (2026-07-27 fix) so the jump-nav's
   scrollIntoView() lands below the fixed navbar instead of tucking the
   section title underneath it. */
.my-sec-head { display: flex; justify-content: space-between; align-items: center; scroll-margin-top: calc(var(--navbar-h) + 12px); }
.my-sec-title { font-size: 17px; font-weight: 700; }   /* 15px +15% */
.my-sec-link { font-size: 12px; color: var(--accent-gold); cursor: pointer; text-decoration: none; }
.my-hint { font-size: 10.8px; text-align: right; color: var(--text-secondary); }   /* 12px -10% */

/* "ดูผังที่นั่งของฉัน" — jumps to Find My PPW anchored on the primary ticket's
   zone (2026-07-08 ask). Sits between the wallet and the tap-hint line. */
.my-seatmap-btn {
  display: flex; align-items: center; justify-content: center; gap: 6px;
  margin-top: 0;
  padding: 10px 14px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: rgba(var(--surface-rgb),0.04);
  color: var(--text-primary);
  font: 600 13px var(--font-thai);
  text-decoration: none;
  cursor: pointer;
}
.my-seatmap-btn:hover { background: rgba(var(--surface-rgb),0.08); border-color: var(--accent-teal); }

/* Wallet stack (ticket cards) — solid cards, gentle overlap, click to expand */
.my-wallet { display: flex; flex-direction: column; }

.my-ticket {
  position: relative;
  display: flex;
  background: var(--bg-secondary);
  border: 1px solid var(--border);
  /* Colored border/ring is reserved for verified tickets (--ticket-border is
     only set by JS when verified); everything else falls back to the same
     plain border every other card uses. */
  border-top: 2px solid var(--ticket-border, var(--border));
  border-radius: 14px;
  overflow: hidden;
  cursor: pointer;
  margin-top: -14px;                 /* wallet overlap */
  /* Layered shadow stack (2026-07-27, Feel Lab ref) — tight/dark + medium +
     wide/faint, instead of one flat shadow; reads with more depth at the
     same visual weight. */
  box-shadow:
    0 1px 3px rgba(0, 0, 0, 0.3),
    0 6px 14px rgba(0, 0, 0, 0.26),
    0 18px 36px rgba(0, 0, 0, 0.22);
  transition: margin 0.2s ease, filter 0.15s ease;
}
.my-ticket:first-child { margin-top: 0; }
/* First-render fade-in stagger — only plays once on mount (index-based
   delay set inline via my.js), not on every re-render/expand. Shared by
   the ticket wallet and every other card/tile that fades in on load
   (budget, reminders, benefit tiles, MD rows, timeline). Pure transform +
   opacity so it's compositor-only — cheap even with many staggered items. */
/* animation-fill-mode: both keeps the "to" state applied after the animation
   ends, so an element whose resting opacity isn't 1 (e.g. a lost-raffle
   card at 0.5) settles there directly instead of finishing the animation at
   opacity:1 and then visibly snapping down once the animation stops
   controlling the property. */
.my-enter { animation: p-card-enter 0.4s ease both; --enter-opacity: 1; }
@keyframes p-card-enter {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: var(--enter-opacity); transform: translateY(0); }
}
.my-lucky-card--lost.my-enter { --enter-opacity: 0.5; }
.my-ticket:hover { filter: brightness(1.08); }   /* no lift/gap, no dark shadow */
.my-ticket.is-open {
  margin-top: 12px;
  box-shadow: 0 8px 24px rgba(var(--shadow-rgb), calc(0.4 * var(--shadow-mult))), 0 0 0 1px var(--ticket-border, var(--border));
}
.my-ticket:first-child.is-open { margin-top: 0; }
.my-ticket.is-open + .my-ticket { margin-top: 12px; }
/* soft tier glow along the rail edge — rail stretches (flex default) to match
   .my-ticket__body's full height, padding included, so it always reaches the bottom */
.my-ticket__rail { width: 8px; flex-shrink: 0; box-shadow: 0 0 16px 2px var(--ticket-glow, transparent); }
.my-ticket__body { flex: 1; padding: 12px 14px; }
/* Stack order: first = backmost, last = frontmost (natural paint order).
   Back cards get bottom padding on the body (not the container) so the rail
   stretches to cover it too — front-most (last) card has none. */
.my-ticket:not(:last-child) .my-ticket__body { padding-bottom: 26px; }
.my-ticket.is-open .my-ticket__body { padding-bottom: 12px; }

/* Collapsed (peek) row */
.my-ticket__peek { display: flex; align-items: center; justify-content: space-between; position: relative; touch-action: pan-y; }
/* Long-press progress fill (2026-07-27, Feel Lab "long press" ref — 500ms
   hold with a visual progress fill) — a thin bar under the peek row that
   grows over the hold duration, tinted to the ticket's own tier color.
   Snaps back to 0 instantly (no transition) on release/cancel; only fills
   smoothly while actively pressed, via the .is-pressing override below. */
.my-ticket__peek::after {
  content: ''; position: absolute; left: 0; right: 0; bottom: -1px;
  height: 2px; width: 0%; border-radius: 2px;
  background: var(--ticket-color, var(--accent-gold));
  pointer-events: none;
}
.my-ticket__peek.is-pressing::after { width: 100%; transition: width 0.5s linear; }
.my-ticket__facts { display: flex; align-items: flex-end; gap: 12px; }
.my-fact { display: flex; flex-direction: column; }
.my-fact__k { font-size: 9px; color: var(--text-secondary); }
.my-fact__v { font-size: 18px; font-weight: 700; line-height: 1; }
.my-ticket__status { display: flex; align-items: center; gap: 6px; font-size: 13px; }
.my-ticket__price {
  padding: 3px 8px; border-radius: 8px; margin-right: 2px;
  background: var(--ticket-glow, rgba(var(--surface-rgb),0.06));
  color: var(--ticket-color, var(--text-primary));
  font-size: 12px; font-weight: 700; white-space: nowrap;
}
.my-status-badge {
  width: 22px; height: 22px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 11px; font-size: 12px;
  background: rgba(var(--surface-rgb),0.08);
}

/* Expanded detail — hide the peek row to avoid duplicate zone/row/seat */
.my-ticket__detail { display: none; }
.my-ticket.is-open .my-ticket__peek { display: none; }
.my-ticket.is-open .my-ticket__detail { display: block; }
.my-ticket__detail-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; }
.my-ticket__detail-badges { display: flex; align-items: center; gap: 6px; }
.my-ticket__num { font-size: 13px; color: var(--text-secondary); }
.my-badge {
  padding: 4px 10px; border-radius: 10px; font-size: 11px; font-weight: 600;
}
.my-badge--verified { background: rgba(79, 209, 135, 0.16); color: var(--success-text); }
.my-badge--pending  { background: rgba(250, 191, 89, 0.16); color: #FABF59; }
/* 2026-08-08 fix — same badge-pattern bug already fixed in market.css's
   .mk-badge--pending, missed here (measured 1.44:1 in light mode). */
:root[data-theme="light"] .my-badge--pending { color: #9F6605; }
.my-badge--unverified { background: rgba(var(--surface-rgb),0.08); color: var(--text-secondary); }
.my-badge--rejected { background: rgba(240, 107, 107, 0.16); color: var(--danger-text); }
.my-badge--forsale { background: rgba(var(--accent-teal-rgb),0.16); color: var(--accent-teal); }

.my-ticket__big { display: flex; gap: 22px; margin-bottom: 12px; }
.my-big { display: flex; flex-direction: column; }
.my-big__k { font-size: 11px; color: var(--text-secondary); }
.my-big__v { font-size: 30px; font-weight: 700; line-height: 1.1; }

.my-ticket__meta { display: flex; justify-content: space-between; align-items: center; }
.my-meta__round { font-size: 13px; }
.my-meta__round b { font-size: 18px; }
.my-meta__date { font-size: 13px; }
.my-meta__date small { display: block; font-size: 11px; color: var(--text-secondary); }
.my-meta__price { font-size: 14px; font-weight: 700; color: var(--accent-gold); }

/* Lucky-draw hero cards (prominent random benefits) */
.my-lucky { display: flex; flex-direction: column; gap: 12px; }
.my-lucky__label { display: flex; align-items: center; gap: 8px; font-size: 13px; font-weight: 700; }
.my-lucky__badge {
  padding: 3px 9px; border-radius: 999px;
  /* Fixed gradient, not theme-following (2026-08-08 fix) — same drift bug. */
  background: linear-gradient(120deg, #ffb852, #ed8420);
  color: #12121A; font-size: 10px; font-weight: 800; letter-spacing: 0.5px;
}
.my-lucky__grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.my-lucky-card {
  position: relative; overflow: hidden;
  display: flex; flex-direction: column; gap: 10px;
  padding: 16px 14px;
  border-radius: 18px;
  background:
    radial-gradient(circle at 100% 0%, rgba(var(--accent-teal-rgb),0.22), transparent 55%),
    linear-gradient(135deg, rgba(var(--accent-gold-rgb),0.30), var(--bg-secondary) 75%);
  border: 1px solid rgba(var(--accent-gold-rgb),0.55);
  box-shadow: 0 10px 26px rgba(var(--shadow-rgb), calc(0.35 * var(--shadow-mult)));
}
.my-lucky-card__spark {
  width: 34px; height: 34px; display: flex; align-items: center; justify-content: center;
  border-radius: 10px; font-size: 17px;
  background: rgba(var(--surface-rgb),0.14);
}
.my-lucky-card__name { font-size: 15px; font-weight: 800; line-height: 1.25; }
.my-lucky-card__excl { font-size: 11px; color: rgba(var(--surface-rgb),0.7); margin-top: 2px; line-height: 1.4; }

/* Pending draw — normal (non-celebratory) mode: plain card, no gradient/glow */
.my-lucky-card--pending {
  background: var(--bg-secondary);
  border: 1px solid var(--border);
  box-shadow: none;
}
.my-lucky-card--pending .my-lucky-card__spark { background: rgba(var(--surface-rgb),0.06); }
.my-lucky-card--pending .my-lucky-card__name { font-weight: 600; color: var(--text-secondary); }
.my-lucky-card__waiting { font-size: 12px; color: var(--text-secondary); }

/* Lost raffle — dim the whole card, de-emphasized vs. a still-pending one */
.my-lucky-card--lost { opacity: 0.5; }

/* Seat chips (all seats grouped inside a single benefit box) */
.my-seat-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 2px; }

/* Chips — seat identifiers (e.g. "R2 · A2 · B05"). Bordered pill instead of
   a flat gray fill, with the round prefix ("R2") picked out in teal so the
   round is scannable at a glance across a grid of chips. */
.my-chip {
  padding: 4px 9px; border-radius: 8px;
  background: rgba(var(--surface-rgb),0.05);
  border: 1px solid rgba(var(--surface-rgb),0.10);
  font-size: 10px; font-variant-numeric: tabular-nums;
  color: var(--text-secondary); white-space: nowrap;
}
.my-chip__round { color: var(--accent-teal); font-weight: 700; }
/* Awarded/won seat — gold glow border marks it as the special one among
   plain seat chips (previously identical styling to a losing/pending seat). */
.my-chip--win {
  align-self: flex-start;
  background: rgba(var(--accent-gold-rgb),0.14);
  border: 1px solid rgba(var(--accent-gold-rgb),0.55);
  box-shadow: 0 0 10px rgba(var(--accent-gold-rgb),0.25);
  color: var(--text-primary); font-weight: 700;
}
.my-chip--win .my-chip__round { color: var(--accent-gold); }

/* Guaranteed benefits — tile grid */
.my-guar-head { font-size: 12px; font-weight: 700; color: var(--text-secondary); margin: 2px 0; }
.my-guar-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.my-guar-tile {
  display: flex; gap: 10px;
  padding: 12px;
  border-radius: 12px;
  background: var(--bg-secondary);
  border: 1px solid var(--border);
}
.my-guar-tile__crest {
  width: 24px; height: 24px; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  border-radius: 8px; font-size: 12px; font-weight: 700;
  background: rgba(var(--accent-teal-rgb),0.16); color: var(--accent-teal);
}
.my-guar-tile__mid { min-width: 0; }
.my-guar-tile__name { font-size: 13px; font-weight: 600; line-height: 1.3; }
/* Name-to-chips gap (2026-07-10 fix) — the actual chip row here is
   `.my-seat-chips` (built by seatChips() in my.js), not `.my-guar-tile__chips`
   — that class was never applied to anything, so its margin-top never took
   effect and the chips sat right up against the name. Scoped to this tile
   only; `.my-seat-chips`'s own base margin-top elsewhere (e.g. Lucky cards)
   is untouched. */
.my-guar-tile__mid .my-seat-chips { margin-top: 6px; }

/* Fan Benefits (legacy list — kept for fallback) */
.my-benefits { display: flex; flex-direction: column; gap: 4px; }
.my-benefits__head { font-size: 11px; color: var(--text-secondary); margin-bottom: 4px; }
.my-benefit__chips { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 4px; }
.my-benefit { display: flex; align-items: center; gap: 12px; padding: 8px 0; }
.my-benefit__icon {
  width: 26px; height: 26px; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  border-radius: 8px; font-size: 13px;
  background: rgba(var(--accent-gold-rgb),0.14);
}
.my-benefit__check {
  width: 20px; height: 20px; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  border-radius: 6px; font-size: 11px;
  background: rgba(79, 209, 135, 0.16); color: var(--success-text);
}
.my-benefit__mid { flex: 1; min-width: 0; }
.my-benefit__name { font-size: 14px; font-weight: 600; }
.my-benefit--all .my-benefit__name { flex: 1; font-weight: 500; }
.my-benefit__sub { font-size: 11px; color: var(--text-secondary); margin-top: 2px; }
.my-benefit__tag {
  flex-shrink: 0;
  padding: 4px 8px; border-radius: 8px;
  background: rgba(var(--surface-rgb),0.06);
  font-size: 11px; color: var(--text-secondary);
}

/* MD summary */
.my-md { display: flex; flex-direction: column; }
.my-md__row { display: flex; align-items: center; gap: 12px; padding: 8px 0; border-bottom: 1px solid var(--border); }
.my-md__thumb {
  width: 44px; height: 44px; flex-shrink: 0;
  border-radius: 8px; overflow: hidden;
  background: linear-gradient(135deg, #292b4d, #171a30);
}
/* Theme-aware placeholder (2026-08-08 fix) — see .my-gv-card__thumb's
   identical comment below; this fixed dark gradient shows briefly before
   the real photo loads and read as out of place on an otherwise-light page. */
:root[data-theme="light"] .my-md__thumb { background: linear-gradient(135deg, var(--bg-elevated), var(--bg-secondary)); }
.my-md__thumb img { width: 100%; height: 100%; object-fit: cover; }
.my-md__thumb.is-clickable { cursor: pointer; }
.my-md__mid { flex: 1; min-width: 0; }
/* Single-line truncate, not 2-line clamp (2026-08-08 fix) — clamping to 2
   lines was still tall enough that a cart with most real product names
   (which run long — see below) dragged the whole /p/my page down badly
   once more than a few items were planned. Name is now purely identifying
   text (hence the lighter weight); the price carries the visual weight. */
.my-md__name {
  font-size: 12.5px; font-weight: 500;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.my-md__qty { font-size: 11px; color: var(--text-secondary); margin-top: 2px; }
/* Per-unit price appended here (qty > 1 only — see renderMdSummary in
   my.js) instead of its own line, so a row with real per-item pricing
   stays 2 lines total, same as a plain qty-only row. Redundant when
   qty === 1 (per-unit === row total, shown at right already), so that
   case is left as a bare qty line by the caller. */
.my-md__price { font-size: 13.5px; font-weight: 600; color: rgba(var(--accent-gold-rgb),0.75); }
/* Summary style (2026-08-08 fix) — was a flat "รวมสินค้า MD" label + price,
   read like just another row. Now states the count/qty explicitly ("รวม
   ทั้งหมด 2 รายการ · 3 ชิ้น") so it reads as a receipt-style total line,
   distinct from the item rows above it. */
.my-md__total { display: flex; justify-content: space-between; align-items: baseline; padding-top: 14px; }
.my-md__total-label { font-size: 12.5px; color: var(--text-secondary); }
.my-md__total-count { color: var(--text-primary); font-weight: 600; }
.my-md__total-val { font-size: 16px; font-weight: 700; color: var(--accent-gold); }

/* แพลนกลับบ้าน (Star Buddy) summary — card-per-round, same visual recipe as
   Star Buddy's own wallet (gohome.css .gh-wallet-card): teal top-border
   while planned, gold once matched, dashed gold CTA card when empty. */
.my-gh-list { display: flex; flex-direction: column; gap: 10px; }
.my-gh-card {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  border-radius: 14px; border: 1px solid var(--border); background: var(--bg-secondary);
  padding: 14px; color: var(--text-primary); text-decoration: none;
}
.my-gh-card--planned { border-top: 2px solid var(--accent-teal); }
.my-gh-card--planned.is-success { border-top-color: var(--accent-gold); }
/* De-emphasized (2026-07-27 ask) — a not-yet-planned round shouldn't compete
   visually with one that's actually planned. --enter-opacity (not opacity
   directly) is required — .my-enter's fade-in animation is `both`-filled, so
   its own 100% keyframe (opacity: var(--enter-opacity), default 1) would
   otherwise win over a plain opacity rule once the animation settles; same
   pattern as .my-lucky-card--lost above. */
.my-gh-card--empty { border-style: dashed; opacity: 0.55; --enter-opacity: 0.55; transition: opacity 0.15s ease, border-color 0.15s ease; }
.my-gh-card--empty:hover { opacity: 0.85; border-color: var(--accent-gold); }
/* Round/date — small caption (2026-07-27 ask, was the dominant line);
   destination below is now the emphasized element instead. */
.my-gh-round { font-size: 11px; font-weight: 600; color: var(--text-secondary); }
.my-gh-dest { display: flex; align-items: center; gap: 6px; font-size: 14px; font-weight: 600; color: var(--text-primary); margin-top: 3px; } /* matches .my-benefit__name (2026-07-27 ask) */
.my-gh-cta { color: var(--text-secondary); font-size: 13px; font-weight: 600; flex-shrink: 0; }
.my-gh-success { font-size: 18px; flex-shrink: 0; }

/* Giveaway ของฉัน (Nova Craft) summary — card-per-entry, echoing Nova
   Craft's own .nt-card (rounded photo thumb + title + status) scaled down
   for a dashboard list instead of a browse grid. */
.my-gv-list { display: flex; flex-direction: column; gap: 10px; margin-bottom: 12px; }
.my-gv-card {
  display: flex; align-items: center; gap: 12px;
  border-radius: 14px; border: 1px solid var(--border); background: var(--bg-secondary);
  padding: 10px; color: var(--text-primary); text-decoration: none;
  transition: border-color 0.15s ease;
}
.my-gv-card--approved:hover { border-color: var(--accent-teal); }
.my-gv-card__thumb {
  width: 52px; height: 52px; flex-shrink: 0;
  border-radius: 10px; overflow: hidden;
  background: linear-gradient(135deg, #292b4d, #171a30);
}
/* Theme-aware placeholder (2026-08-08 fix) — shows briefly before the real
   giveaway photo loads; a fixed dark box read as out of place on an
   otherwise-light page. */
:root[data-theme="light"] .my-gv-card__thumb { background: linear-gradient(135deg, var(--bg-elevated), var(--bg-secondary)); }
.my-gv-card__thumb img { width: 100%; height: 100%; object-fit: cover; }
.my-gv-card__mid { flex: 1; min-width: 0; }
.my-gv-card__title { font-size: 14px; font-weight: 600; }
.my-gv-card__reason { font-size: 11px; color: var(--danger-text); margin-top: 2px; }

/* Reminders — "เรื่องสำคัญ": high-visibility alert so people stop and read */
.my-card.my-reminders {
  background: linear-gradient(135deg, rgba(var(--like-red-rgb), 0.14), var(--bg-secondary));
  border: 1px solid rgba(var(--like-red-rgb), 0.5);
  border-left: 4px solid var(--like-red);
  box-shadow: 0 6px 20px rgba(var(--like-red-rgb), 0.12);
}
.my-reminders__title { display: flex; align-items: center; gap: 8px; font-size: 15px; font-weight: 800; margin-bottom: 10px; color: #ff8a9b; }
/* 2026-08-08 fix — #ff8a9b measured ~1.95:1 in light mode (this card's
   background blends into the theme-aware --bg-secondary). Same hue,
   darkened to ~3.3:1. */
:root[data-theme="light"] .my-reminders__title { color: #E70022; }
.my-reminders__title span:first-child { font-size: 18px; }
.my-reminder { display: flex; gap: 10px; font-size: 12.5px; color: var(--text-primary); padding: 7px 0; line-height: 1.55; }
.my-reminder__dot { width: 7px; height: 7px; border-radius: 50%; background: var(--like-red); margin-top: 6px; flex-shrink: 0; box-shadow: 0 0 8px rgba(var(--like-red-rgb), 0.8); }
:root[data-theme="light"] .my-reminder__dot { background: #E8435A; }
.my-reminder__link { color: var(--accent-gold); text-decoration: underline; cursor: pointer; }
.my-reminder__date { color: var(--accent-gold); font-weight: 700; }

/* Health Status Check-In banner (§3.1) — 3 states, all built on .my-card.
   Only the "sick" answered state (covid/flu/other) reuses the reminders
   card's alert-red language; healthy uses teal, not_tested uses a neutral
   grey so it never reads as "confirmed healthy" (Decision #17). */
.my-health-wrap { display: contents; }
/* .my-health-wrap is display:contents, so its 2 children (the card + the
   consent notice below it) fall directly into the parent flex column's own
   gap: 16px (.my-content / .my-group--left) — the same spacing used between
   unrelated dashboard sections. Pulled back in here (2026-07-26 fix) so the
   notice reads as attached to the banner right above it, not a separate
   section of its own. */
.my-health-wrap .health-notice { margin-top: -8px; text-align: left; }
.my-health__title { font-size: 13px; font-weight: 700; margin-bottom: 10px; }
.my-health__pills { display: flex; flex-wrap: wrap; gap: 8px; }
.my-health__pill {
  display: flex; align-items: center; gap: 6px;
  padding: 9px 14px;
  background: rgba(var(--surface-rgb),0.05);
  border: 1px solid rgba(var(--surface-rgb),0.15);
  border-radius: 999px;
  color: var(--text-primary);
  font: 600 12.5px var(--font-thai);
  cursor: pointer;
  transition: border-color 0.12s ease, background 0.12s ease;
}
.my-health__pill:hover { border-color: var(--accent-gold); background: rgba(var(--accent-gold-rgb),0.1); }
.my-health__pill:disabled { opacity: 0.5; cursor: default; }
.my-card.my-health--unanswered { border-left: 4px solid var(--accent-gold); }

.my-health__row { display: flex; align-items: center; gap: 10px; }
.my-health__icon { font-size: 20px; }
.my-health__label { flex: 1; font-size: 13.5px; font-weight: 700; }
.my-health__edit {
  background: none; border: none; padding: 4px 0;
  color: var(--accent-gold);
  font: 600 12px var(--font-thai);
  cursor: pointer;
}
.my-card.my-health--not-tested { border-left: 4px solid var(--text-secondary); }
.my-card.my-health--not-tested .my-health__label { color: var(--text-secondary); }
.my-card.my-health--answered.is-healthy { border-left: 4px solid var(--accent-teal); }
.my-card.my-health--answered.is-healthy .my-health__label { color: var(--accent-teal); }
.my-card.my-health--answered.is-sick {
  background: linear-gradient(135deg, rgba(var(--like-red-rgb), 0.14), var(--bg-secondary));
  border: 1px solid rgba(var(--like-red-rgb), 0.5);
  border-left: 4px solid var(--like-red);
}
.my-card.my-health--answered.is-sick .my-health__label { color: #ff8a9b; }
/* 2026-08-08 fix — same #ff8a9b bug as .my-reminders__title above. */
:root[data-theme="light"] .my-card.my-health--answered.is-sick .my-health__label { color: #E70022; }

/* Concert Timeline — tabbed per day */
.my-tl-tabs { display: flex; gap: 8px; margin-bottom: 16px; }
.my-tl-tab {
  flex: 1; display: flex; flex-direction: column; gap: 3px; align-items: center; justify-content: center;
  padding: 10px 6px; border-radius: 12px;
  background: rgba(var(--surface-rgb),0.04);
  border: 1px solid var(--border);
  cursor: pointer; color: var(--text-secondary);
  font-family: var(--font-thai);
  text-align: center; line-height: 1.25;
  transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}
.my-tl-tab.is-active {
  background: rgba(var(--accent-gold-rgb),0.16);
  border-color: rgba(var(--accent-gold-rgb),0.7);
  color: var(--text-primary);
}
.my-tl-tab__day { font-size: 13px; font-weight: 700; line-height: 1.2; }
.my-tl-tab__date { font-size: 10px; line-height: 1.2; }
.my-tl-date { font-size: 12px; color: var(--accent-gold); font-weight: 600; margin-bottom: 12px; }

/* Concert Timeline */
.my-timeline { display: flex; flex-direction: column; }
.my-tl-round { display: flex; align-items: center; gap: 14px; padding: 14px 0 10px; font-size: 13px; font-weight: 600; }
.my-tl-round:first-child { padding-top: 0; }
.my-tl-round__dot { width: 8px; height: 8px; border-radius: 50%; background: var(--accent-gold); flex-shrink: 0; }
:root[data-theme="light"] .my-tl-round__dot { background: #F4A140; }
.my-tl-item { display: flex; gap: 12px; }
.my-tl-rail { position: relative; width: 12px; flex-shrink: 0; }
.my-tl-rail .my-tl-line { position: absolute; left: 5px; top: 3px; bottom: -8px; width: 2px; background: var(--border); }
.my-tl-item.is-last .my-tl-line { display: none; }
.my-tl-rail .my-tl-dot { position: absolute; left: 1px; top: 3px; width: 10px; height: 10px; border-radius: 50%; background: var(--accent-teal); }
:root[data-theme="light"] .my-tl-rail .my-tl-dot { background: #13CDCB; }
.my-tl-body { flex: 1; padding-bottom: 14px; }
/* Time vs label contrast (2026-08-12): time carries the accent color/weight,
   label stays regular-weight white — differentiates the two without resizing
   either (kept at the original 14px/13px). */
.my-tl-time { font-size: 14px; font-weight: 700; color: var(--accent-teal); }
.my-tl-label { font-size: 13px; font-weight: 400; color: var(--text-primary); margin-top: 2px; }
/* Concert-start (last item): bold, vibrant electric-blue label matching its
   own dot (glow added for extra pop against the dark bg) — every other item
   stays teal. #0170FE (2026-07-08) read as too muted/low-contrast against
   --bg-primary; swapped for a brighter cyan-blue (2026-07-08 follow-up). */
.my-tl-item.is-last .my-tl-dot { background: #00D4FF; box-shadow: 0 0 8px rgba(0, 212, 255, 0.7); }
.my-tl-item.is-last .my-tl-label { color: #00D4FF; font-weight: 700; }
/* This row's time stays plain white, not teal — the label already carries
   the special blue treatment above, so the time doesn't need the new accent
   color too (2026-08-12 ask). */
.my-tl-item.is-last .my-tl-time { color: var(--text-primary); }
/* Text-only override (2026-08-08 fix, found in a codebase-wide literal-hex
   sweep, not by the earlier systematic passes — this was another one-off
   dark-mode-tuned color nobody had tokenized) — #00D4FF measured 1.54:1 in
   light mode. Same hue, darkened to ~3.3:1. The dot itself stays #00D4FF in
   both themes (a small color swatch, not read as text). */
:root[data-theme="light"] .my-tl-item.is-last .my-tl-label { color: #007D96; }
/* Time already passed — faded like the Concert Guide modal's schedule tab
   (.guide-tl-past in style.css), not recolored, so it still reads in both
   themes and doesn't fight the is-last teal/blue treatment above. */
.my-tl-item.is-past { opacity: 0.4; }

/* ── Floating jump-nav (2026-07-27 ask, revised) — bottom-LEFT shortcut to
   jump between dashboard sections (both mobile and desktop). Left side keeps
   it clear of the shared "back to top" button (.ppw-back-to-top, tokens.css
   — right:16px). Menu uses the same glass/blur elevation as the navbar's own
   dropdowns (nav.css .ppw-nav__dropdown) so it doesn't read as a flat gold
   box against the space background. */
.my-jump-nav { position: fixed; left: 16px; bottom: 20px; z-index: 900; }
.my-jump-nav__btn {
  width: 44px; height: 44px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  /* Fixed gradient, not theme-following (2026-08-08 fix) — same drift bug. */
  background: linear-gradient(135deg, #ffb852, #ed8420);
  border: none; box-shadow: 0 4px 14px rgba(var(--accent-gold-rgb),0.45);
  color: #12121A; font-size: 18px; cursor: pointer;
  opacity: 0.6; transition: opacity 0.15s ease, transform 0.15s ease;
}
.my-jump-nav__btn:hover { opacity: 1; }
.my-jump-nav__btn:active { transform: scale(0.94); }
/* Stays fully visible while the menu itself is open (2026-07-27 ask —
   fading is only meant to de-emphasize the idle button). */
.my-jump-nav.is-open .my-jump-nav__btn { opacity: 1; }
.my-jump-nav__menu {
  position: absolute; left: 0; bottom: 54px;
  /* Shrink-to-fit content (2026-07-27 fix) — a flat min-width left a lot of
     dead space to the right of short labels like "Timeline"; max-width caps
     it so a very long label still wraps instead of overflowing the screen. */
  width: max-content; max-width: min(240px, calc(100vw - 32px));
  border: 1px solid rgba(var(--surface-rgb),0.14);
  border-radius: 14px;
  padding: 6px;
  /* Hardcoded per-theme, not a token (2026-08-07 fix) — same reason as
     nav.css's .ppw-nav__dropdown glass-fill: CSS vars can't carry their own
     alpha, and this was never given the same light-mode branch, so it
     stayed a dark glass pill even in light mode. */
  background: rgba(14, 14, 34, 0.72);
  backdrop-filter: blur(20px) saturate(1.3);
  -webkit-backdrop-filter: blur(20px) saturate(1.3);
  box-shadow: 0 12px 28px rgba(var(--shadow-rgb), calc(0.45 * var(--shadow-mult)));
  display: flex; flex-direction: column; gap: 2px;
  opacity: 0; transform: translateY(6px) scale(0.98); pointer-events: none;
  transition: opacity 0.15s ease, transform 0.15s var(--ease-spring, ease);
}
.my-jump-nav.is-open .my-jump-nav__menu { opacity: 1; transform: translateY(0) scale(1); pointer-events: auto; }
:root[data-theme="light"] .my-jump-nav__menu { background: rgba(219, 212, 233, 0.85); }
:root[data-theme="dark"] .my-jump-nav__menu { background: rgba(14, 14, 34, 0.72); }
.my-jump-nav__item {
  display: flex; align-items: center; gap: 8px;
  padding: 9px 10px; border-radius: 9px;
  background: none; border: none; color: var(--text-primary);
  font-family: inherit; font-size: 13px; text-align: left; cursor: pointer;
}
.my-jump-nav__item:hover, .my-jump-nav__item:active { background: rgba(var(--surface-rgb),0.08); }
/* Locked item (2026-08-11 ask) — section hidden because the member has 0
   tickets. Stays in the menu (not removed) but faded and non-interactive
   beyond the click handler itself, which shows a toast instead of scrolling. */
.my-jump-nav__item--locked { opacity: 0.45; cursor: not-allowed; }
.my-jump-nav__item--locked:hover, .my-jump-nav__item--locked:active { background: none; }

/* Top-of-dashboard warning banner shown when the member has 0 tickets
   (2026-08-11 ask) — replaces the old hard gate that hid the whole page. */
.my-noticket-banner {
  display: flex; align-items: center; gap: 8px;
  padding: 12px 14px; border-radius: 14px;
  background: rgba(var(--accent-gold-rgb),0.12);
  border: 1px solid rgba(var(--accent-gold-rgb),0.35);
  font-size: 13px; line-height: 1.5; color: var(--text-primary);
}
/* line-height:1 (2026-08-11 fix) — without it the icon's own line box
   inherited the banner's 1.5 line-height at font-size:16px (=24px), floating
   the 16px icon inside a taller invisible box and reading as "puffy" next to
   the tighter text; align-items:center above centers it against the text
   block once that padding is gone, instead of pinning it to the top. */
.my-noticket-banner__icon { flex: none; font-size: 16px; line-height: 1; display: inline-flex; }
.my-noticket-banner__link { color: var(--accent-gold); font-weight: 700; text-decoration: underline; }
@media (min-width: 768px) {
  /* Spans both bento columns instead of sitting only in the grid's first
     column (its default auto-placement slot). */
  .my-noticket-banner { grid-column: 1 / -1; }
}
.my-tl-sub { font-size: 11px; color: var(--text-secondary); opacity: 0.8; margin-top: 2px; line-height: 1.4; }
