/* =====================================================================
   Mereka v2 — Gamification module (Points & Rewards) — UPDATED to UXA
   Dark-header summary cards, illustrated quests, 3-tab Points & Rewards
   with Add-a-voucher, redesigned My Badges (seals + For you), Membership
   tiers (Unlocked/Locked + progress), GrabGifts reward flow, Add-voucher
   modal, quest-detail illustrations, weekly-streak popup, sidebar block,
   public Learner profile. Global on/off flag (bottom-right toggle).
   ===================================================================== */

/* ---------- Global on/off flag ---------- */
let _gamiOn = (() => { try { const v = localStorage.getItem('mereka_gami'); return v === null ? true : v === '1'; } catch (e) { return true; } })();
const _gamiSubs = new Set();
function setGamiOn(v) { _gamiOn = !!v; try { localStorage.setItem('mereka_gami', _gamiOn ? '1' : '0'); } catch (e) {} _gamiSubs.forEach(fn => fn(_gamiOn)); }
function useGamiOn() {
  const [v, setV] = React.useState(_gamiOn);
  React.useEffect(() => { const fn = (x) => setV(x); _gamiSubs.add(fn); setV(_gamiOn); return () => _gamiSubs.delete(fn); }, []);
  return v;
}

/* ---------- Data ---------- */
const GAMI = {
  user: 'Faiz Fadhillah', balance: 780, expiry: '31 Dec 2026', referral: 'NZWKLR20',
  rankWeek: 15, rankWeekDelta: 2, rankAll: 22, rankAllDelta: 4, streakWeeks: 2,
  tier: 'Explorer', tierPoints: 350, tierNext: 500, tierNextName: 'Achiever',
};

const GAMI_FEATURED_QUESTS = [
  { group: 'QUEST GROUP NAME', title: 'Quest name', pts: 10, cta: 'Start', state: 'active', expires: '12:59:00', art: 0 },
  { group: 'LEARN AND EARN', title: 'Join AI4U learning path', pts: 10, cta: 'Start', state: 'active', expires: '12:59:00', art: 1 },
  { group: 'LEARN AND EARN', title: 'Join a course', pts: 10, cta: 'Start', state: 'active', repeatable: true, prog: '1/5 complete', art: 2 },
  { group: 'WELCOME', title: 'Complete profile', pts: 10, cta: 'Continue', state: 'active', art: 3 },
];

const GAMI_QUEST_GROUPS = [
  { name: 'Welcome', done: 1, total: 3, bonus: 50, rate: 50, quests: [
    { title: 'Complete profile', pts: 10, desc: 'Complete your profile to earn points!', state: 'progress', prog: '1/5 complete', art: 0, cta: 'Continue' },
    { title: 'Sign up bonus', pts: 10, desc: 'Welcome! Create an account to earn your first 10 pts.', state: 'completed', art: 0 },
    { title: 'Complete profile', pts: 10, desc: 'Complete your profile to earn points!', state: 'completed', prog: '5/5 complete', art: 0 },
  ] },
  { name: 'Learn and earn', done: 0, total: 4, bonus: 40, rate: 15, quests: [
    { title: 'Join a course', pts: 10, desc: 'Expand your knowledge and earn rewards by enrolling in a course. Each course you join earns points toward exclusive perks and achievements.', state: 'active', repeatable: true, cta: 'Start', art: 2 },
    { title: 'Join a learning path', pts: 10, desc: 'Join a learning path to earn points. The more you explore, the more you earn!', state: 'active', repeatable: true, cta: 'Start', art: 1 },
    { title: 'Join an experience', pts: 10, desc: 'Sign up and attend an experience to earn points. The more you explore, the more you earn!', state: 'active', repeatable: true, cta: 'Start', art: 2 },
    { title: 'Join an expertise session', pts: 10, desc: 'Sign up and attend an expertise session to earn points. The more you explore, the more you earn!', state: 'active', repeatable: true, cta: 'Start', art: 1 },
  ] },
  { name: 'Standalone', done: 0, total: 3, bonus: 30, rate: 15, quests: [
    { title: 'Join AI4U learning path', pts: 10, desc: 'Limited time quest! Join AI4U learning path now to earn bonus points.', state: 'active', cta: 'Start', expires: '12:59:00', art: 3, featured: true },
    { title: 'Refer and earn', pts: 10, desc: 'Spread the word! Earn 10 pts for every new friend who signs up using your referral code.', state: 'active', repeatable: true, cta: 'Continue', art: 3 },
    { title: 'Join AI4U learning path', pts: 10, desc: 'Limited time quest! Join AI4U learning path now to earn bonus points.', state: 'expired', art: 3 },
  ] },
];

const GAMI_REWARDS = [
  { id: 'grab10', title: 'Grab RM10 Voucher', desc: 'RM10 Grab e-voucher – rides or food.', pts: 100, cat: 'Vouchers', fav: true },
  { id: 'grab25', title: 'Grab RM25 Voucher', desc: 'RM25 Grab e-voucher – rides or food.', pts: 250, cat: 'Vouchers' },
  { id: 'grab50', title: 'Grab RM50 Voucher', desc: 'RM50 Grab e-voucher – rides or food.', pts: 500, cat: 'Vouchers' },
  { id: 'grab50b', title: 'Grab RM50 Voucher', desc: 'RM50 Grab e-voucher – rides or food.', pts: 500, cat: 'Vouchers' },
  { id: 'credit25', title: 'RM25 Course Credit', desc: 'RM25 off any course or experience.', pts: 250, cat: 'Discounts', mereka: true, fav: true },
  { id: 'credit50', title: 'RM50 Course Credit', desc: 'RM50 off any course or learning path.', pts: 500, cat: 'Discounts', mereka: true },
  { id: 'session', title: 'Free 30-min expertise session', desc: 'One complimentary expertise mini-session.', pts: 400, cat: 'Gift cards', mereka: true },
  { id: 'grab10b', title: 'Grab RM10 Voucher', desc: 'RM10 Grab e-voucher – rides or food.', pts: 100, cat: 'Vouchers' },
  { id: 'credit10', title: 'RM10 Course Credit', desc: 'RM10 off your next booking.', pts: 100, cat: 'Discounts', mereka: true },
];

const GAMI_REDEMPTIONS = [
  { id: 'r1', title: 'Grab RM10 Voucher', desc: 'RM10 Grab e-voucher – rides or food.', cat: 'Vouchers', used: false, fav: true },
  { id: 'r2', title: 'Grab RM25 Voucher', desc: 'RM25 Grab e-voucher – rides or food.', cat: 'Vouchers', used: false },
  { id: 'r3', title: 'Grab RM10 Voucher', desc: 'RM10 Grab e-voucher – rides or food.', cat: 'Vouchers', used: true },
];

const GAMI_HISTORY = [
  { pts: 10, detail: 'Complete profile quest', date: '12 Jan 2026', time: '14:30', type: 'Earning' },
  { pts: 10, detail: 'Complete profile quest', date: '12 Jan 2026', time: '14:30', type: 'Earning' },
  { pts: -100, detail: 'Grab RM10 Voucher', date: '12 Jan 2026', time: '14:30', type: 'Spending' },
];

const GAMI_LEADERBOARD = {
  'All time': [
    { name: 'Florence Jones', state: 'Perak', pts: 100, reward: 50 },
    { name: 'Adam Wilson', state: 'Kuala Lumpur', pts: 95, reward: 25 },
    { name: 'Jackson Lee', state: 'Kuching', pts: 95, reward: 10 },
    { name: 'Jennifer Tay', state: 'Johor', pts: 85, reward: 5 },
    { name: 'Michael Chen', state: 'Kuala Lumpur', pts: 80, reward: 5 },
    { name: 'Aisha Rahman', state: 'Penang', pts: 80, reward: 5 },
    { name: 'Ravi Kumar', state: 'Malacca', pts: 75, reward: 5 },
    { name: 'Siti Nurhaliza', state: 'Putrajaya', pts: 75, reward: 5 },
    { name: 'Faiz Fadhillah', state: 'Kuala Lumpur', pts: 75, reward: 5, me: true },
    { name: 'Fiona Lim', state: 'Langkawi', pts: 70, reward: 5 },
  ],
  'Monthly': [
    { name: 'Aisha Rahman', state: 'Penang', pts: 62, reward: 50 },
    { name: 'Faiz Fadhillah', state: 'Kuala Lumpur', pts: 58, reward: 25, me: true },
    { name: 'Florence Jones', state: 'Perak', pts: 55, reward: 10 },
    { name: 'Ravi Kumar', state: 'Malacca', pts: 52, reward: 5 },
    { name: 'Adam Wilson', state: 'Kuala Lumpur', pts: 50, reward: 5 },
    { name: 'Fiona Lim', state: 'Langkawi', pts: 48, reward: 5 },
    { name: 'Jennifer Tay', state: 'Johor', pts: 45, reward: 5 },
    { name: 'Jackson Lee', state: 'Kuching', pts: 44, reward: 5 },
    { name: 'Michael Chen', state: 'Kuala Lumpur', pts: 40, reward: 5 },
    { name: 'Siti Nurhaliza', state: 'Putrajaya', pts: 38, reward: 5 },
  ],
  'Weekly': [
    { name: 'Jackson Lee', state: 'Kuching', pts: 30, reward: 50 },
    { name: 'Siti Nurhaliza', state: 'Putrajaya', pts: 28, reward: 25 },
    { name: 'Faiz Fadhillah', state: 'Kuala Lumpur', pts: 26, reward: 10, me: true },
    { name: 'Michael Chen', state: 'Kuala Lumpur', pts: 24, reward: 5 },
    { name: 'Florence Jones', state: 'Perak', pts: 22, reward: 5 },
    { name: 'Jennifer Tay', state: 'Johor', pts: 20, reward: 5 },
    { name: 'Fiona Lim', state: 'Langkawi', pts: 18, reward: 5 },
    { name: 'Adam Wilson', state: 'Kuala Lumpur', pts: 16, reward: 5 },
    { name: 'Aisha Rahman', state: 'Penang', pts: 14, reward: 5 },
    { name: 'Ravi Kumar', state: 'Malacca', pts: 12, reward: 5 },
  ],
};
const GAMI_LEADERS = GAMI_LEADERBOARD['All time'];

const GAMI_TIERS = [
  { name: 'Explorer', sub: 'Default', unlock: 0, medal: '#C77B3B', perks: [
    ['Standard features', 'Access to standard public experiences, courses, and gigs.'],
    ['Base point earning rate', 'Earn 1x points on actions.'],
    ['Dashboard access', 'Standard dashboard features and progress tracking.'],
  ] },
  { name: 'Achiever', sub: '', unlock: 500, medal: '#AEB4BE', perks: [
    ['1.2x Point Multiplier', 'Earn points 20% faster on all activities.'],
    ['Priority Booking Window', '24-hour early access to high-demand workshops/experiences before public release.'],
    ['Monthly Discount Voucher', 'Automated 5-10% off code applied at checkout once per month.'],
    ['Profile Badge', 'Achiever badge displayed on public profile and forum posts.'],
  ] },
  { name: 'Innovator', sub: '', unlock: 2000, medal: '#E8B23A', perks: [
    ['1.5x Point Multiplier', 'Earn points 50% faster.'],
    ['VIP Event Access', 'Exclusive access to members-only webinars, expert Q&As, or networking sessions.'],
    ['Zero Booking Fee / Extra Discounts', 'Standard platform convenience fees waived or higher flat discounts on paid experiences.'],
    ['Featured Profile Status', 'Highlighted placement in "Find Experts" or community directory.'],
  ] },
];

const GAMI_BADGES = [
  { name: 'AI Explorer', req: 'Complete 3 AI courses', date: '12/08/2026', earned: true, hue: '#2F6BFF', letter: 'A' },
  { name: 'AI Fluent', req: 'Complete an AI learning pathway', date: '12/08/2026', earned: true, hue: '#1FA85B', letter: 'A' },
  { name: 'Lifelong Learner', req: 'Complete 10 courses', date: '12/08/2026', earned: true, hue: '#E4572E', letter: 'A' },
  { name: 'Knowledge Seeker', req: 'Complete 5 courses', date: '12/08/2026', earned: true, hue: '#E8B23A', letter: 'A' },
];
const GAMI_BADGES_FORYOU = [
  { name: 'Fast Learner', req: 'Complete 5 courses', done: 3, total: 5, hue: '#2F6BFF' },
  { name: 'Path Completer', req: 'Finish a learning path', done: 3, total: 5, hue: '#1FA85B' },
  { name: 'Top Reviewer', req: 'Leave 10 reviews', done: 3, total: 5, hue: '#E4572E' },
  { name: 'Streak Master', req: 'Hold a 4-week streak', done: 3, total: 5, hue: '#E8B23A' },
  { name: 'Category Explorer', req: 'Book all 4 categories', done: 3, total: 5, hue: '#2F6BFF' },
  { name: 'Community Helper', req: 'Answer 5 questions', done: 3, total: 5, hue: '#1FA85B' },
  { name: 'First Certificate', req: 'Earn a certificate', done: 3, total: 5, hue: '#E4572E' },
];

/* ---------- Small helpers ---------- */
function GamiStar({ size = 22 }) {
  return <span aria-hidden style={{ color: '#E8B23A', display: 'inline-flex' }}><Icon name="star" size={size} fill="#E8B23A" strokeWidth={0} /></span>;
}
function GamiRing({ value = 0, size = 34 }) {
  const r = (size - 6) / 2, c = 2 * Math.PI * r, off = c - (value / 100) * c;
  return (
    <svg width={size} height={size} className="flex-shrink-0" style={{ transform: 'rotate(-90deg)' }}>
      <circle cx={size/2} cy={size/2} r={r} fill="none" stroke="#E5E7EB" strokeWidth="3" />
      <circle cx={size/2} cy={size/2} r={r} fill="none" stroke="#1FA85B" strokeWidth="3" strokeDasharray={c} strokeDashoffset={off} strokeLinecap="round" />
    </svg>
  );
}
function GamiChips({ chips, value, onChange }) {
  return (
    <div className="flex items-center gap-2 flex-wrap">
      {chips.map(c => (
        <button key={c} onClick={() => onChange(c)}
          className={`px-3.5 h-8 inline-flex items-center rounded-full text-[13px] font-semibold transition-colors ${value === c ? 'bg-ink text-white' : 'border border-neutral-200 text-neutral-600 hover:border-neutral-300'}`}>{c}</button>
      ))}
    </div>
  );
}

/* Badge seal (two rotated squircles + letter/sparkle) */
function GamiBadgeSeal({ hue = '#2F6BFF', locked = false, size = 56, letter = 'A' }) {
  const c = locked ? '#AEB4BE' : hue;
  const sq = (bg, scale, rot) => <span className="absolute" style={{ inset: 0, background: bg, borderRadius: '30%', transform: `rotate(${rot}deg) scale(${scale})` }} />;
  return (
    <span className="relative inline-block flex-shrink-0" style={{ width: size, height: size }}>
      {sq(c, 1, 0)}{sq(c, 1, 45)}{sq('#15121C', 0.78, 0)}{sq('#15121C', 0.78, 45)}
      <span className="absolute inset-0 flex items-center justify-center font-extrabold" style={{ color: c, fontSize: size * 0.36, fontFamily: _pp }}>
        {letter}<span style={{ fontSize: size * 0.2, marginLeft: 1, alignSelf: 'flex-start', marginTop: size * 0.14 }}>✦</span>
      </span>
    </span>
  );
}
/* Tier medal (star seal) */
function GamiTierMedal({ color = '#C77B3B', locked = false, size = 80 }) {
  const c = locked ? '#AEB4BE' : color;
  const sq = (bg, scale, rot) => <span className="absolute" style={{ inset: 0, background: bg, borderRadius: '32%', transform: `rotate(${rot}deg) scale(${scale})` }} />;
  return (
    <span className="relative inline-block flex-shrink-0" style={{ width: size, height: size }}>
      {sq(c, 1, 0)}{sq(c, 1, 45)}
      <span className="absolute inset-0 flex items-center justify-center"><Icon name="star" size={size * 0.42} fill="#ffffff" strokeWidth={0} className="text-white" /></span>
    </span>
  );
}
/* Real Figma quest illustration (Mereka V2 — UXA, node 5351-97646) */
function GamiQuestArt({ seed = 0, size = 56, rounded = 'rounded-xl', full = false }) {
  return (
    <span className={`relative flex items-center justify-center ${rounded} overflow-hidden flex-shrink-0`} style={{ width: full ? '100%' : size, height: full ? undefined : size, aspectRatio: full ? '16 / 9' : '1 / 1', background: '#DCEAF2' }}>
      <img src="/quest-illustration.png" alt="" loading="lazy" className="w-full h-full object-cover" />
    </span>
  );
}
/* GrabGifts / Mereka reward logo */
function GamiRewardLogo({ mereka = false, size = 44 }) {
  return (
    <span className="rounded-xl flex flex-col items-center justify-center text-white flex-shrink-0" style={{ width: size, height: size, background: mereka ? '#1FA3A6' : '#00B14F' }}>
      {mereka ? <span className="font-extrabold" style={{ fontSize: size * 0.5 }}>M</span>
              : <><span className="font-extrabold leading-none" style={{ fontSize: size * 0.26 }}>Grab</span><span className="leading-none tracking-wide" style={{ fontSize: size * 0.18 }}>eGIFT</span></>}
    </span>
  );
}

/* ---------- GrabGifts / Mereka reward banner artwork ---------- */
function GamiRewardBanner({ mereka = false, h = 150, fav, onFav, rounded = 'rounded-t-2xl' }) {
  const a = mereka ? '#12A5A5' : '#00B14F', b = mereka ? '#0A7E7E' : '#00933F';
  const confetti = [['12%', '20%', '#FFD54A', 8], ['30%', '60%', '#FF6F61', 7], ['66%', '16%', '#FFFFFF', 6], ['84%', '44%', '#FFD54A', 7], ['50%', '30%', '#8CE0B4', 6]];
  return (
    <div className={`relative overflow-hidden ${rounded}`} style={{ height: h, background: `linear-gradient(135deg, ${a}, ${b})` }}>
      {confetti.map(([l, t, c, sz], i) => <span key={i} className="absolute rounded-[1px]" style={{ left: l, top: t, width: sz, height: sz, background: c, transform: `rotate(${i * 33}deg)`, opacity: 0.92 }} />)}
      <svg viewBox="0 0 200 120" className="absolute bottom-0 left-0 w-full" style={{ height: h * 0.5 }} preserveAspectRatio="none"><path d="M0 120 L0 94 Q18 80 40 90 Q52 68 78 82 Q96 64 120 80 Q150 72 200 98 L200 120 Z" fill="#ffffff" opacity="0.92" /></svg>
      <svg viewBox="0 0 64 64" className="absolute" style={{ right: 12, bottom: 8, width: h * 0.44, height: h * 0.44 }}>
        <rect x="10" y="27" width="44" height="29" rx="3" fill="#FFC93C" /><rect x="10" y="27" width="44" height="9" rx="2" fill="#FFB800" /><rect x="28" y="27" width="8" height="29" fill="#E85D2A" />
        <path d="M32 27 C20 27 18 13 28 13 C34 13 33 23 32 27 Z" fill="#E85D2A" /><path d="M32 27 C44 27 46 13 36 13 C30 13 31 23 32 27 Z" fill="#E85D2A" />
      </svg>
      <div className="absolute left-4 top-3.5 text-white leading-none">
        <div className="font-extrabold" style={{ fontSize: h < 120 ? 12 : 15 }}>{mereka ? 'Mereka' : 'GrabGifts'}</div>
        <div className="font-extrabold tracking-tight mt-1" style={{ fontSize: h < 120 ? 24 : 34, lineHeight: '1' }}>{mereka ? 'eCREDIT' : 'eGIFT'}</div>
      </div>
      {fav !== undefined && <button onClick={(e) => { e.stopPropagation(); onFav && onFav(); }} className="absolute top-3 right-3 w-8 h-8 rounded-full bg-black/60 text-white flex items-center justify-center hover:bg-black/75"><Icon name="heart" size={15} fill={fav ? '#ffffff' : 'none'} /></button>}
    </div>
  );
}

/* ---------- Shared summary header (dark-header cards) ---------- */
function GamiSummaryCard({ crumb }) {
  const earned = GAMI_BADGES.filter(b => b.earned);
  const card = "bg-white border border-neutral-200 rounded-2xl overflow-hidden";
  const Head = ({ label, to, link, info }) => (
    <div className="flex items-center justify-between px-4 py-2.5" style={{ background: '#3f3b47' }}>
      <span className="text-[12px] font-bold uppercase tracking-wide text-white">{label}</span>
      <Link to={to} className="text-[12px] font-semibold text-white/70 hover:text-white inline-flex items-center gap-0.5">{link} <Icon name={info ? 'info' : 'chevronRight'} size={12} /></Link>
    </div>
  );
  return (
    <div>
      {crumb && (
        <div className="flex items-center gap-1.5 text-sm text-neutral-500 mb-4">
          <Link to="/app/dashboard/points" className="hover:text-neutral-900">Points &amp; Rewards</Link>
          <Icon name="chevronRight" size={14} /> <span className="text-neutral-900 font-medium">{crumb}</span>
        </div>
      )}
      <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
        <div className={card}>
          <Head label="My balance" to="/app/dashboard/points/history" link="History" />
          <div className="p-4 flex items-center gap-2"><GamiStar size={26} /><span className="text-3xl font-bold text-ink num-tabular" style={{ fontFamily: _pp }}>{GAMI.balance}</span><span className="text-sm text-neutral-500">points</span></div>
        </div>
        <div className={card}>
          <Head label="My ranking" to="/app/dashboard/points/leaderboard" link="Leaderboard" />
          <div className="p-4 space-y-1.5">
            <div className="flex items-center gap-3 text-sm"><span className="text-neutral-500 w-16">This week</span><span className="font-bold text-ink num-tabular text-lg">{GAMI.rankWeek}</span><span className="text-green-600 text-[12px] font-semibold inline-flex items-center gap-0.5"><Icon name="trending" size={12} /> {GAMI.rankWeekDelta}</span></div>
            <div className="flex items-center gap-3 text-sm"><span className="text-neutral-500 w-16">All time</span><span className="font-bold text-ink num-tabular">{GAMI.rankAll}</span><span className="text-green-600 text-[12px] font-semibold inline-flex items-center gap-0.5"><Icon name="trending" size={12} /> {GAMI.rankAllDelta}</span></div>
          </div>
        </div>
        <div className={card}>
          <Head label={`My badges (${earned.length})`} to="/app/dashboard/points/badges" link="See all" />
          <div className="p-4 flex items-start gap-3">
            {earned.slice(0, 3).map(b => (
              <div key={b.name} className="flex flex-col items-center gap-1.5 w-14">
                <GamiBadgeSeal hue={b.hue} size={38} letter={b.letter} />
                <span className="text-[10px] font-medium text-neutral-500 text-center leading-tight w-full truncate">{b.name}</span>
              </div>
            ))}
          </div>
        </div>
        <div className={card}>
          <Head label="My tier" to="/app/dashboard/points/membership" link="Tier" info />
          <div className="p-4">
            <div className="flex items-center gap-2"><GamiTierMedal color="#C77B3B" size={28} /><span className="font-bold text-ink">{GAMI.tier}</span><span className="ml-auto text-[12px] text-neutral-500 num-tabular">{GAMI.tierPoints} / {GAMI.tierNext}</span></div>
            <div className="mt-2 h-1.5 rounded-full bg-neutral-100 overflow-hidden"><div className="h-full rounded-full" style={{ width: (GAMI.tierPoints / GAMI.tierNext * 100) + '%', background: 'linear-gradient(90deg,#8B5CF6,#EC4899,#F59E0B)' }} /></div>
            <p className="mt-1.5 text-[11px] text-neutral-400">Earn {GAMI.tierNext - GAMI.tierPoints} pts to unlock next tier</p>
          </div>
        </div>
      </div>
      <div className="mt-4 bg-white border border-neutral-200 rounded-2xl px-4 py-3 flex items-center justify-between flex-wrap gap-3">
        <div className="text-sm"><span className="text-primary-700 font-semibold">Referral Code</span> <span className="font-bold text-ink tracking-wide">{GAMI.referral}</span> <button className="text-neutral-400 hover:text-neutral-700 align-middle"><Icon name="copy" size={14} /></button></div>
        <button className="h-10 px-5 rounded-full bg-ink text-white text-sm font-semibold inline-flex items-center gap-2 hover:bg-black"><Icon name="send" size={14} /> Refer now</button>
      </div>
    </div>
  );
}

/* ---------- Weekly streak card (used only in popup) ---------- */
function GamiWeeklyStreak() {
  const weeks = GAMI.streakWeeks;
  return (
    <div className="rounded-2xl border border-[#F0D9A6] bg-[#FFF7E6] p-4 flex items-center gap-4 flex-wrap">
      <div className="flex items-center gap-2"><span className="text-2xl" aria-hidden>🔥</span>
        <div><div className="font-bold text-ink" style={{ fontFamily: _pp }}>Weekly streak · {weeks} weeks</div><p className="text-[13px] text-neutral-600">Complete a course module this week to keep your streak alive.</p></div>
      </div>
    </div>
  );
}

/* ---------- Quest detail modal (gradient + stepper + T&C) ---------- */
function GamiQuestModal({ quest, onClose }) {
  if (!quest) return null;
  const done = quest.state === 'completed';
  const steps = quest.repeatable
    ? [['Join ' + quest.title.replace(/^Join /i, ''), quest.pts, 'done'], ['Complete a course in ' + quest.title.replace(/^Join /i, ''), quest.pts, 'active'], ['Complete the ' + quest.title.replace(/^Join /i, ''), quest.pts, 'locked']]
    : [[quest.title, quest.pts, done ? 'done' : 'active']];
  const total = steps.reduce((a, s) => a + s[1], 0);
  return (
    <div className="fixed inset-0 z-[120] flex items-center justify-center px-4 bg-black/50 backdrop-blur-sm" onClick={onClose}>
      <div className="w-full max-w-xl rounded-3xl shadow-2xl p-6 sm:p-7 max-h-[92vh] overflow-y-auto" style={{ background: 'linear-gradient(135deg,#FBE8F0 0%,#EAE7FB 45%,#E4F4F1 100%)' }} onClick={e => e.stopPropagation()}>
        <div className="flex items-start justify-between gap-3">
          <div className="flex items-center gap-4 flex-wrap">
            <GamiQuestArt seed={quest.art || 0} size={96} rounded="rounded-2xl" />
            <div>
              <h3 className="text-2xl font-bold text-ink" style={{ fontFamily: _pp }}>{quest.title}</h3>
              <div className="mt-2 flex items-center gap-2 flex-wrap"><span className="px-2.5 h-7 inline-flex items-center rounded-full bg-[#FDECC0] text-[#B8860B] text-[13px] font-bold">+{total} pts</span>{quest.repeatable && <span className="px-2.5 h-7 inline-flex items-center gap-1 rounded-full text-neutral-600 text-[13px] font-semibold"><Icon name="refresh" size={12} /> Repeatable</span>}</div>
            </div>
          </div>
          <button onClick={onClose} className="text-neutral-500 hover:text-neutral-800"><Icon name="close" size={22} /></button>
        </div>
        <p className="mt-4 text-[15px] text-neutral-500 leading-relaxed">{quest.desc}</p>
        <div className="mt-5 space-y-5">
          {steps.map(([label, pts, st], i) => (
            <div key={i} className="flex items-center justify-between gap-4 flex-wrap">
              <div className="min-w-0">
                <span className="px-2.5 h-6 inline-flex items-center rounded-full bg-white/70 text-neutral-600 text-[12px] font-bold mb-1.5">Step {i + 1}</span>
                <div className="text-[17px] text-ink flex items-center gap-2 flex-wrap"><span>{label}.</span><span className="px-2 h-6 inline-flex items-center rounded-full bg-[#FDECC0] text-[#B8860B] text-[12px] font-bold">+{pts} pts</span></div>
              </div>
              {st === 'done' ? <span className="text-neutral-500 text-[15px] font-semibold inline-flex items-center gap-1.5 flex-shrink-0">Completed <Icon name="check" size={17} /></span>
                : st === 'locked' ? <span className="h-11 px-6 rounded-full border border-neutral-300 text-neutral-400 text-sm font-semibold inline-flex items-center gap-2 flex-shrink-0"><Icon name="lock" size={14} /> Locked</span>
                : <button className="h-11 px-6 rounded-full bg-ink text-white text-sm font-semibold inline-flex items-center gap-1.5 hover:bg-black flex-shrink-0">Continue <Icon name="chevronRight" size={14} /></button>}
            </div>
          ))}
        </div>
        <div className="mt-6">
          <h4 className="text-[15px] font-bold text-ink mb-1.5">Terms and Conditions</h4>
          <ol className="list-decimal pl-5 space-y-1 text-[13px] text-neutral-500 leading-snug">
            <li>All points awarded are non-exchangeable for cash or credit. No refund or exchange for used or unused points and rewards is allowed.</li>
            <li>Points and rewards will be forfeited with no extension if not used within the stated time frame.</li>
            <li>Points are automatically awarded to your account once the steps are completed.</li>
          </ol>
        </div>
      </div>
    </div>
  );
}

/* ---------- Reward detail / redemption flow (GrabGifts) ---------- */
function GamiRewardModal({ reward, initialStep = 'detail', onClose }) {
  const [step, setStep] = React.useState(initialStep);
  const [used, setUsed] = React.useState(false);
  React.useEffect(() => { setStep(initialStep); setUsed(false); }, [reward, initialStep]);
  if (!reward) return null;
  const tags = reward.mereka ? ['Credit', 'Mereka'] : ['Voucher', 'Partner'];
  const Head = () => (
    <div className="flex items-start gap-4 flex-wrap">
      <div className="w-[180px] flex-shrink-0"><GamiRewardBanner mereka={reward.mereka} h={116} rounded="rounded-2xl" /></div>
      <div className="min-w-0 flex-1"><h3 className="text-2xl font-bold text-ink" style={{ fontFamily: _pp }}>{reward.title}</h3><div className="mt-2 flex items-center gap-2 flex-wrap">{tags.map(t => <span key={t} className="px-3 h-7 inline-flex items-center rounded-full bg-neutral-100 text-neutral-800 text-[13px] font-semibold">{t}</span>)}</div></div>
    </div>
  );
  return (
    <div className="fixed inset-0 z-[120] flex items-center justify-center px-4 bg-black/50 backdrop-blur-sm" onClick={onClose}>
      <div className="w-full max-w-2xl bg-white rounded-3xl shadow-2xl p-6 sm:p-7" onClick={e => e.stopPropagation()}>
        {step === 'detail' && (<>
          <div className="flex items-start justify-between gap-3"><Head /><button onClick={onClose} className="text-neutral-400 hover:text-neutral-700 flex-shrink-0"><Icon name="close" size={22} /></button></div>
          <p className="mt-5 text-[15px] text-neutral-500">{reward.desc}</p>
          <div className="mt-4 h-14 rounded-2xl bg-neutral-100 flex items-center px-5"><span className="font-mono tracking-[0.4em] text-[#4F86FF] font-bold text-lg">********</span></div>
          <p className="mt-4 text-[14px] text-neutral-500">Copy this code and paste it on the Grab voucher redemption page to activate your voucher.</p>
          <div className="mt-6 flex justify-end"><button onClick={() => setStep('confirm')} className="h-12 px-6 rounded-full bg-ink text-white text-[15px] font-semibold inline-flex items-center gap-2 hover:bg-black"><Icon name="card" size={16} /> {reward.pts} pts</button></div>
        </>)}
        {step === 'confirm' && (<>
          <div className="flex items-start justify-between"><div><h3 className="text-2xl font-bold text-ink" style={{ fontFamily: _pp }}>Redeem the following reward?</h3><p className="mt-1.5 text-[15px] text-neutral-500">Your points will be deducted immediately.</p></div><button onClick={onClose} className="text-neutral-400 hover:text-neutral-700"><Icon name="close" size={22} /></button></div>
          <div className="mt-5"><Head /></div>
          <div className="mt-6 flex gap-3 justify-end flex-wrap">
            <button onClick={onClose} className="h-12 px-6 rounded-full border border-neutral-200 text-[15px] font-semibold hover:bg-neutral-50 inline-flex items-center gap-2"><Icon name="close" size={16} className="text-neutral-400" /> Cancel</button>
            <button onClick={() => setStep('success')} className="h-12 px-6 rounded-full bg-ink text-white text-[15px] font-semibold hover:bg-black inline-flex items-center gap-2"><Icon name="card" size={16} /> Redeem for {reward.pts} pts</button>
          </div>
        </>)}
        {step === 'success' && (<>
          <div className="text-center py-4">
            <div className="w-16 h-16 rounded-full bg-green-50 text-green-600 flex items-center justify-center mx-auto"><Icon name="check" size={30} /></div>
            <h3 className="mt-4 text-2xl font-bold text-ink" style={{ fontFamily: _pp }}>Reward redeemed!</h3>
            <p className="mt-2 text-[15px] text-neutral-500">Congratulations! You have successfully redeemed the reward.</p>
            <div className="mt-6 flex gap-3 justify-center flex-wrap">
              <button onClick={() => setStep('claimed')} className="h-12 px-6 rounded-full bg-ink text-white text-[15px] font-semibold hover:bg-black">View reward</button>
              <Link to="/app/dashboard/points/redemptions" onClick={onClose} className="h-12 px-6 rounded-full border border-neutral-200 text-[15px] font-semibold inline-flex items-center hover:bg-neutral-50">My redemptions</Link>
            </div>
          </div>
        </>)}
        {step === 'claimed' && (<>
          <div className="flex items-start justify-between gap-3"><Head /><button onClick={onClose} className="text-neutral-400 hover:text-neutral-700 flex-shrink-0"><Icon name="close" size={22} /></button></div>
          <p className="mt-5 text-[15px] text-neutral-500">{reward.desc}</p>
          <div className="mt-4 h-14 rounded-2xl bg-neutral-100 flex items-center justify-between px-5"><span className="font-mono tracking-[0.15em] text-[#4F86FF] font-bold text-lg">{reward.mereka ? 'MRKA-4821' : 'GRB-93F2XA'}</span><button className="h-9 px-4 rounded-full bg-ink text-white text-[13px] font-semibold inline-flex items-center gap-1 hover:bg-black"><Icon name="copy" size={13} /> Copy</button></div>
          <p className="mt-4 text-[14px] text-neutral-500">Copy this code and paste it on the Grab voucher redemption page to activate your voucher.</p>
          <div className="mt-6 flex justify-end">
            {used ? <span className="h-11 px-5 rounded-full border border-neutral-200 text-neutral-400 text-sm font-semibold inline-flex items-center gap-1">Reward used <Icon name="check" size={14} /></span>
                  : <button onClick={() => setUsed(true)} className="h-11 px-5 rounded-full border border-neutral-300 text-sm font-semibold inline-flex items-center gap-2 hover:bg-neutral-50"><Icon name="check" size={15} /> Mark as used</button>}
          </div>
        </>)}
      </div>
    </div>
  );
}

/* ---------- Add a voucher modal (with states) ---------- */
function GamiVoucherModal({ onClose }) {
  const [code, setCode] = React.useState('');
  const [status, setStatus] = React.useState(null); // null | invalid | success | used
  const submit = () => {
    const c = code.trim().toUpperCase();
    if (!c) return;
    if (c === 'USED123') setStatus('used');
    else if (/^[A-Z0-9]{6}$/.test(c)) setStatus('success');
    else setStatus('invalid');
  };
  const border = status === 'invalid' || status === 'used' ? 'border-red-400 focus:border-red-500' : status === 'success' ? 'border-green-400' : 'border-neutral-300 focus:border-primary';
  return (
    <div className="fixed inset-0 z-[120] flex items-center justify-center px-4 bg-black/50 backdrop-blur-sm" onClick={onClose}>
      <div className="w-full max-w-md bg-white rounded-2xl shadow-2xl p-6" onClick={e => e.stopPropagation()}>
        <div className="flex items-start justify-between"><h3 className="text-lg font-bold text-ink" style={{ fontFamily: _pp }}>Add a voucher</h3><button onClick={onClose} className="text-neutral-400 hover:text-neutral-700"><Icon name="close" size={20} /></button></div>
        <label className="block text-[13px] font-semibold text-neutral-700 mt-4 mb-1.5">Voucher code</label>
        <input value={code} onChange={e => { setCode(e.target.value); setStatus(null); }} placeholder="E.g: ABC123" className={`w-full h-11 px-4 rounded-xl border ${border} focus:outline-none text-[15px]`} />
        {status === 'invalid' && <p className="mt-1.5 text-[13px] font-semibold text-red-500 text-right">Invalid code</p>}
        {status === 'used' && <p className="mt-1.5 text-[13px] font-semibold text-red-500 text-right">Voucher has already been used</p>}
        {status === 'success' && <p className="mt-1.5 text-[13px] font-semibold text-green-600 text-right">Voucher added to your redemptions!</p>}
        <div className="mt-5 flex justify-end">
          {status === 'success'
            ? <Link to="/app/dashboard/points/redemptions" onClick={onClose} className="h-10 px-4 rounded-full bg-ink text-white text-sm font-semibold inline-flex items-center gap-2 hover:bg-black"><Icon name="card" size={15} /> My Redemptions</Link>
            : <button onClick={submit} className="h-10 px-5 rounded-full bg-ink text-white text-sm font-semibold hover:bg-black">Claim</button>}
        </div>
      </div>
    </div>
  );
}

/* ---------- Reward card ---------- */
function GamiRewardCard({ r, onRedeem }) {
  const [fav, setFav] = React.useState(!!r.fav);
  return (
    <div className="bg-white border border-neutral-200 rounded-2xl overflow-hidden hover:shadow-sm transition-all flex flex-col">
      <GamiRewardBanner mereka={r.mereka} fav={fav} onFav={() => setFav(v => !v)} />
      <div className="p-4 flex-1 flex flex-col">
        <h3 className="font-bold text-ink leading-snug">{r.title}</h3>
        <p className="text-[13px] text-neutral-500 mt-1 flex-1">{r.desc}</p>
        <div className="mt-4 flex justify-center">
          <button onClick={() => onRedeem(r)} className="h-10 px-6 rounded-full border border-neutral-200 text-ink text-[13px] font-bold inline-flex items-center gap-2 hover:bg-neutral-50"><Icon name="card" size={15} className="text-neutral-500" /> {r.pts} pts</button>
        </div>
      </div>
    </div>
  );
}

/* ---------- Redemption card ---------- */
function GamiRedemptionCard({ r, onUse }) {
  return (
    <div className="bg-white border border-neutral-200 rounded-2xl overflow-hidden flex flex-col">
      <GamiRewardBanner mereka={r.mereka} fav={false} />
      <div className="p-4 flex-1 flex flex-col">
        <h3 className="font-bold text-ink leading-snug">{r.title}</h3>
        <p className="text-[13px] text-neutral-500 mt-1 flex-1">{r.desc}</p>
        <div className="mt-4 flex justify-end">
          {r.used ? <span className="h-10 px-4 rounded-full border border-neutral-200 text-neutral-400 text-[13px] font-semibold inline-flex items-center gap-1">Reward used <Icon name="check" size={14} /></span>
                  : <button onClick={() => onUse(r)} className="h-10 px-5 rounded-full bg-ink text-white text-[13px] font-semibold inline-flex items-center gap-1 hover:bg-black">Use now <Icon name="chevronRight" size={13} /></button>}
        </div>
      </div>
    </div>
  );
}

/* ---------- 1) Points & Rewards (Earn / Redeem / My redemptions) ---------- */
function GamiPointsRewardsPage() {
  const [tab, setTab] = React.useState('earn');
  const [qfilter, setQfilter] = React.useState('All');
  const [rcat, setRcat] = React.useState('All');
  const [dcat, setDcat] = React.useState('All');
  const [quest, setQuest] = React.useState(null);
  const [reward, setReward] = React.useState(null);
  const [rewardStep, setRewardStep] = React.useState('detail');
  const [voucherOpen, setVoucherOpen] = React.useState(false);

  const openReward = (r, step) => { setReward(r); setRewardStep(step || 'detail'); };
  const filteredGroups = GAMI_QUEST_GROUPS.map(g => ({
    ...g,
    quests: g.quests.filter(q => qfilter === 'All'
      || (qfilter === 'Active' && (q.state === 'active' || q.state === 'progress'))
      || (qfilter === 'Completed' && q.state === 'completed')
      || (qfilter === 'Expired' && q.state === 'expired')),
  })).filter(g => g.quests.length);
  const store = GAMI_REWARDS.filter(r => rcat === 'All' ? true : rcat === 'Favorites' ? r.fav : rcat === 'Grab' ? /Grab/i.test(r.title) : r.cat === rcat);
  const redeems = GAMI_REDEMPTIONS.filter(r => dcat === 'All' ? true : dcat === 'Favorites' ? r.fav : dcat === 'Grab' ? /Grab/i.test(r.title) : dcat === 'Used' ? r.used : r.cat === dcat);

  const pill = (k, label) => (
    <button onClick={() => setTab(k)} className={`h-10 px-5 rounded-full text-sm font-semibold transition-colors ${tab === k ? 'bg-ink text-white' : 'border border-neutral-200 text-neutral-600 hover:border-neutral-300'}`}>{label}</button>
  );

  return (
    <UserShell route="/app/dashboard/points">
      <div className="max-w-[1200px]">
        <div className="flex items-start justify-between gap-4 flex-wrap mb-5">
          <div>
            <h1 className="text-3xl font-bold text-ink" style={{ fontFamily: _pp }}>Points &amp; Rewards</h1>
            <p className="text-neutral-500 mt-1">Manage and monitor all your points and rewards.</p>
          </div>
          <div className="relative w-full sm:w-72">
            <Icon name="search" size={16} className="absolute left-3.5 top-1/2 -translate-y-1/2 text-neutral-400" />
            <input placeholder="Search for..." className="w-full h-10 pl-10 pr-4 rounded-full border border-neutral-200 text-sm focus:outline-none focus:border-primary" />
          </div>
        </div>
        <GamiSummaryCard />

        {/* tabs + add voucher */}
        <div className="mt-6 flex items-center justify-between gap-3 flex-wrap">
          <div className="flex items-center gap-2 flex-wrap">{pill('earn', 'Earn points')}{pill('redeem', 'Redeem rewards')}{pill('redemptions', 'My redemptions')}</div>
          <button onClick={() => setVoucherOpen(true)} className="h-10 px-4 rounded-full border border-neutral-200 text-sm font-semibold inline-flex items-center gap-2 hover:bg-neutral-50"><Icon name="card" size={15} /> Add a voucher</button>
        </div>

        {tab === 'earn' && (
          <div className="mt-6 space-y-8">
            <section>
              <h2 className="text-xl font-bold text-ink mb-3" style={{ fontFamily: _pp }}>Quests for you</h2>
              <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
                {GAMI_FEATURED_QUESTS.map((q, i) => (
                  <div key={i} style={i === 0 ? { background: 'linear-gradient(150deg,#FFE9A8 0%,#F7C9E0 48%,#C9D8FF 100%)' } : undefined} className={`border rounded-2xl overflow-hidden hover:shadow-sm transition-all flex flex-col ${i === 0 ? 'border-primary/60 ring-1 ring-primary/20' : 'bg-white border-neutral-200'}`}>
                    <div className="relative"><GamiQuestArt seed={q.art || 0} size={132} full rounded="rounded-none" /><span className="absolute top-2.5 right-2.5 w-7 h-7 rounded-full bg-white/85 text-neutral-500 flex items-center justify-center"><Icon name="heart" size={14} /></span></div>
                    <div className="p-3 flex-1 flex flex-col">
                      <div className="font-bold text-ink text-sm leading-snug">{q.title}</div>
                      <div className="mt-1.5 flex items-center gap-2 flex-wrap text-[11px]">
                        <span className="font-bold text-[#B8860B]">+{q.pts} pts</span>
                        {q.expires && <span className="text-red-400 inline-flex items-center gap-0.5"><Icon name="clock" size={10} /> Expires in {q.expires}</span>}
                        {q.prog && <span className="text-neutral-400 inline-flex items-center gap-0.5"><Icon name="check" size={10} /> {q.prog}</span>}
                        {q.repeatable && <span className="text-neutral-400 inline-flex items-center gap-0.5"><Icon name="refresh" size={10} /> Repeatable</span>}
                      </div>
                      <div className="mt-3 flex justify-end"><button onClick={() => setQuest(q)} className={`h-8 px-4 rounded-full text-[13px] font-semibold inline-flex items-center gap-1 ${q.cta === 'Continue' ? 'bg-ink text-white hover:bg-black' : 'border border-neutral-200 text-neutral-700 hover:bg-neutral-50'}`}>{q.cta || 'Start'} <Icon name="chevronRight" size={13} /></button></div>
                    </div>
                  </div>
                ))}
              </div>
            </section>
            <section>
              <div className="flex items-center justify-between gap-4 flex-wrap mb-3">
                <div><h2 className="text-xl font-bold text-ink" style={{ fontFamily: _pp }}>All quests</h2><p className="text-[13px] text-neutral-500">Complete the following quests to earn more points.</p></div>
                <Link to="/app/dashboard/points/history" className="h-9 px-4 rounded-full border border-neutral-200 text-[13px] font-semibold inline-flex items-center gap-1 hover:bg-neutral-50">Points history <Icon name="chevronRight" size={13} /></Link>
              </div>
              <div className="mb-4"><GamiChips chips={['All', 'Active', 'Completed', 'Expired']} value={qfilter} onChange={setQfilter} /></div>
              <div className="space-y-6">
                {filteredGroups.map(g => (
                  <div key={g.name}>
                    <div className="flex items-center justify-between mb-2">
                      <div className="flex items-center gap-2"><Icon name="chevronUp" size={16} className="text-neutral-400" /><h3 className="font-bold text-ink">{g.name} <span className="text-neutral-400 font-medium">({g.done}/{g.total})</span></h3><span className="h-6 px-2 rounded-full bg-[#FFF4D6] text-[#B8860B] text-[12px] font-bold inline-flex items-center">+{g.bonus} pts</span></div>
                      <div className="flex items-center gap-2 text-[11px] font-bold uppercase tracking-wide text-neutral-400">Completion rate <GamiRing value={g.rate} size={34} /></div>
                    </div>
                    <div className="space-y-2">
                      {g.quests.map((q, i) => (
                        <div key={i} className={`flex items-center gap-3 p-3 rounded-xl border ${q.featured ? 'border-transparent' : q.state === 'expired' ? 'border-neutral-100 bg-neutral-50/60' : 'border-neutral-200 bg-white'}`} style={q.featured ? { background: 'linear-gradient(90deg,#FDEEDC,#E7F0FA)' } : {}}>
                          <GamiQuestArt seed={q.art || 0} size={56} />
                          <div className="min-w-0 flex-1">
                            <div className="font-bold text-ink">{q.title}</div>
                            <p className="text-[13px] text-neutral-500 truncate">{q.desc}</p>
                            <div className="mt-1 flex items-center gap-2 flex-wrap text-[11px]">
                              <span className="font-bold text-[#B8860B]">+{q.pts} pts</span>
                              {q.prog && <span className="text-neutral-400 inline-flex items-center gap-0.5"><Icon name="refresh" size={10} /> {q.prog}</span>}
                              {q.repeatable && <span className="text-neutral-400 inline-flex items-center gap-0.5"><Icon name="refresh" size={10} /> Repeatable</span>}
                              {q.expires && <span className="text-red-400 inline-flex items-center gap-0.5"><Icon name="clock" size={10} /> Expires in {q.expires}</span>}
                            </div>
                          </div>
                          {q.state === 'completed' ? <span className="text-green-600 text-sm font-semibold inline-flex items-center gap-1 flex-shrink-0"><Icon name="check" size={16} /> Completed</span>
                            : q.state === 'expired' ? <span className="text-neutral-400 text-sm font-semibold inline-flex items-center gap-1 flex-shrink-0"><Icon name="clock" size={15} /> Expired</span>
                            : <button onClick={() => setQuest(q)} className={`h-9 px-4 rounded-full text-[13px] font-semibold inline-flex items-center gap-1 flex-shrink-0 ${q.cta === 'Continue' ? 'bg-ink text-white hover:bg-black' : 'border border-neutral-200 text-neutral-700 bg-white hover:bg-neutral-50'}`}>{q.cta || 'Start'} <Icon name="chevronRight" size={13} /></button>}
                        </div>
                      ))}
                    </div>
                  </div>
                ))}
              </div>
            </section>
          </div>
        )}

        {tab === 'redeem' && (
          <div className="mt-6">
            <div className="flex items-center justify-between mb-1"><h2 className="text-xl font-bold text-ink" style={{ fontFamily: _pp }}>All rewards</h2><Link to="/app/dashboard/points/rewards" className="text-sm font-semibold text-primary-700 hover:underline inline-flex items-center gap-1">View all <Icon name="chevronRight" size={14} /></Link></div>
            <p className="text-[13px] text-neutral-500 mb-3">Redeem your points for vouchers. Confirming a redemption deducts the points immediately.</p>
            <div className="mb-4"><GamiChips chips={['All', 'Favorites', 'Vouchers', 'Gift cards', 'Discounts', 'Grab']} value={rcat} onChange={setRcat} /></div>
            <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">{store.map(r => <GamiRewardCard key={r.id} r={r} onRedeem={(x) => openReward(x, 'detail')} />)}</div>
          </div>
        )}

        {tab === 'redemptions' && (
          <div className="mt-6">
            <div className="flex items-center justify-between mb-1"><h2 className="text-xl font-bold text-ink" style={{ fontFamily: _pp }}>My Redemptions</h2><Link to="/app/dashboard/points/redemptions" className="h-9 px-4 rounded-full border border-neutral-200 text-[13px] font-semibold inline-flex items-center gap-1 hover:bg-neutral-50">My Redemptions History <Icon name="chevronRight" size={13} /></Link></div>
            <p className="text-[13px] text-neutral-500 mb-3">This is where all the rewards you have redeemed are stored.</p>
            <div className="mb-4"><GamiChips chips={['All', 'Favorites', 'Vouchers', 'Gift cards', 'Discounts', 'Grab']} value={dcat} onChange={setDcat} /></div>
            <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">{redeems.map(r => <GamiRedemptionCard key={r.id} r={r} onUse={(x) => openReward(x, 'claimed')} />)}</div>
          </div>
        )}
      </div>
      <GamiQuestModal quest={quest} onClose={() => setQuest(null)} />
      <GamiRewardModal reward={reward} initialStep={rewardStep} onClose={() => setReward(null)} />
      {voucherOpen && <GamiVoucherModal onClose={() => setVoucherOpen(false)} />}
    </UserShell>
  );
}

/* ---------- 2) All rewards ---------- */
function GamiAllRewardsPage() {
  const [cat, setCat] = React.useState('All');
  const [reward, setReward] = React.useState(null);
  const view = GAMI_REWARDS.filter(r => cat === 'All' ? true : cat === 'Favorites' ? r.fav : cat === 'Grab' ? /Grab/i.test(r.title) : r.cat === cat);
  return (
    <UserShell route="/app/dashboard/points">
      <div className="max-w-[1200px]">
        <GamiSummaryCard crumb="All rewards" />
        <div className="mt-6 flex items-center justify-between gap-4 flex-wrap"><div><h2 className="text-2xl font-bold text-ink" style={{ fontFamily: _pp }}>All rewards</h2><p className="text-[13px] text-neutral-500">Redeem your points for rewards. Confirming a redemption deducts the points immediately.</p></div>
          <div className="relative w-full sm:w-72"><Icon name="search" size={16} className="absolute left-3.5 top-1/2 -translate-y-1/2 text-neutral-400" /><input placeholder="Search for..." className="w-full h-10 pl-10 pr-4 rounded-full border border-neutral-200 text-sm focus:outline-none focus:border-primary" /></div>
        </div>
        <div className="mt-4"><GamiChips chips={['All', 'Favorites', 'Vouchers', 'Gift cards', 'Discounts', 'Grab']} value={cat} onChange={setCat} /></div>
        <div className="mt-5 grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-5">{view.map(r => <GamiRewardCard key={r.id} r={r} onRedeem={setReward} />)}</div>
        <p className="mt-6 text-[13px] text-neutral-400">Showing 1-{view.length} of {view.length}</p>
      </div>
      <GamiRewardModal reward={reward} initialStep="detail" onClose={() => setReward(null)} />
    </UserShell>
  );
}

/* ---------- 3) My redemptions ---------- */
function GamiMyRedemptionsPage() {
  const [cat, setCat] = React.useState('All');
  const [reward, setReward] = React.useState(null);
  const view = GAMI_REDEMPTIONS.filter(r => cat === 'All' || (cat === 'Used' ? r.used : r.cat === cat));
  return (
    <UserShell route="/app/dashboard/points">
      <div className="max-w-[1200px]">
        <GamiSummaryCard crumb="My redemptions" />
        <div className="mt-6"><h2 className="text-2xl font-bold text-ink" style={{ fontFamily: _pp }}>My redemptions</h2><p className="text-[13px] text-neutral-500">This is where all the rewards you have redeemed are stored.</p></div>
        <div className="mt-4"><GamiChips chips={['All', 'Vouchers', 'Gift cards', 'Discounts', 'Used']} value={cat} onChange={setCat} /></div>
        <div className="mt-5 grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-5">{view.map(r => <GamiRedemptionCard key={r.id} r={r} onUse={(x) => setReward(x)} />)}</div>
        <p className="mt-6 text-[13px] text-neutral-400">Showing 1-{view.length} of {view.length}</p>
      </div>
      <GamiRewardModal reward={reward} initialStep="claimed" onClose={() => setReward(null)} />
    </UserShell>
  );
}

/* ---------- 4) Point history ---------- */
function GamiPointHistoryPage() {
  const [f, setF] = React.useState('All');
  const view = GAMI_HISTORY.filter(h => f === 'All' || h.type === f);
  return (
    <UserShell route="/app/dashboard/points">
      <div className="max-w-[1200px]">
        <GamiSummaryCard crumb="Point history" />
        <div className="mt-6 flex items-center justify-between gap-4 flex-wrap"><div><h2 className="text-2xl font-bold text-ink" style={{ fontFamily: _pp }}>Point history</h2><p className="text-[13px] text-neutral-500">Keep track of your point history earnings and spendings here.</p></div>
          <div className="flex items-center gap-2"><div className="relative w-56"><Icon name="search" size={16} className="absolute left-3.5 top-1/2 -translate-y-1/2 text-neutral-400" /><input placeholder="Search for..." className="w-full h-10 pl-10 pr-4 rounded-full border border-neutral-200 text-sm focus:outline-none focus:border-primary" /></div><button className="h-10 px-4 rounded-full border border-neutral-200 text-sm font-semibold inline-flex items-center gap-2 hover:bg-neutral-50"><Icon name="download" size={15} /> Export</button></div>
        </div>
        <div className="mt-4"><GamiChips chips={['All', 'Earning', 'Spending']} value={f} onChange={setF} /></div>
        <div className="mt-5 bg-white border border-neutral-200 rounded-2xl overflow-hidden">
          <table className="w-full text-sm">
            <thead><tr className="text-left text-[12px] font-bold uppercase tracking-wide text-neutral-400 border-b border-neutral-100"><th className="px-5 py-3">Points</th><th className="px-5 py-3">Detail</th><th className="px-5 py-3">Date &amp; time</th><th className="px-5 py-3">Type</th><th className="px-5 py-3 text-right">Action</th></tr></thead>
            <tbody>
              {view.map((h, i) => (
                <tr key={i} className="border-b border-neutral-50 last:border-0">
                  <td className="px-5 py-4 font-bold num-tabular"><span className={h.pts >= 0 ? 'text-green-600' : 'text-ink'}>{h.pts >= 0 ? '+' : ''}{h.pts} PTS</span></td>
                  <td className="px-5 py-4 text-neutral-700">{h.detail}</td>
                  <td className="px-5 py-4 text-neutral-500">{h.date} · {h.time}</td>
                  <td className="px-5 py-4 text-neutral-700">{h.type}</td>
                  <td className="px-5 py-4 text-right"><button className="h-8 px-3 rounded-full border border-neutral-200 text-[13px] font-semibold inline-flex items-center gap-1 hover:bg-neutral-50">View detail <Icon name="chevronRight" size={13} /></button></td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
        <div className="mt-4 flex items-center justify-between text-[13px] text-neutral-400"><span>Showing 1-{view.length} of {view.length}</span></div>
      </div>
    </UserShell>
  );
}

/* ---------- 5) Leaderboard ---------- */
function GamiLeaderboardPage() {
  const [range, setRange] = React.useState('All time');
  const rows = GAMI_LEADERBOARD[range] || GAMI_LEADERBOARD['All time'];
  const rowRefs = React.useRef({});
  const prevPos = React.useRef({});
  React.useLayoutEffect(function () {
    const names = Object.keys(rowRefs.current);
    names.forEach(function (n) {
      const el = rowRefs.current[n];
      if (!el) return;
      const newTop = el.getBoundingClientRect().top;
      const oldTop = prevPos.current[n];
      if (oldTop != null && Math.abs(oldTop - newTop) > 0.5) {
        el.style.transition = 'none';
        el.style.transform = 'translateY(' + (oldTop - newTop) + 'px)';
        el.getBoundingClientRect();
        requestAnimationFrame(function () {
          el.style.transition = 'transform .6s cubic-bezier(.22,.61,.36,1)';
          el.style.transform = '';
        });
      }
    });
    const p = {};
    names.forEach(function (n) { const el = rowRefs.current[n]; if (el) p[n] = el.getBoundingClientRect().top; });
    prevPos.current = p;
  }, [range]);
  const gridCols = { display: 'grid', gridTemplateColumns: '52px minmax(0,1.7fr) 1fr 1fr 92px 96px', alignItems: 'center' };
  return (
    <UserShell route="/app/dashboard/points">
      <div className="max-w-[1200px]">
        <GamiSummaryCard crumb="Leaderboard" />
        <div className="mt-6 flex items-center justify-between gap-4 flex-wrap"><div><h2 className="text-2xl font-bold text-ink" style={{ fontFamily: _pp }}>Leaderboard</h2><p className="text-[13px] text-neutral-500">See how you rank against other learners this period.</p></div><button className="h-10 px-4 rounded-full border border-neutral-200 text-sm font-semibold inline-flex items-center gap-2 hover:bg-neutral-50"><Icon name="download" size={15} /> Export</button></div>
        <div className="mt-4"><GamiChips chips={['All time', 'Monthly', 'Weekly']} value={range} onChange={setRange} /></div>
        <div className="mt-5 bg-white border border-neutral-200 rounded-2xl overflow-hidden">
          <div style={gridCols} className="text-[12px] font-bold uppercase tracking-wide text-neutral-400 border-b border-neutral-100 px-5 py-3">
            <span>#</span><span>Name</span><span>Institution</span><span>State</span><span>Points</span><span className="text-right">Reward</span>
          </div>
          <div>
            {rows.map(function (l, i) {
              const rank = i + 1;
              return (
                <div key={l.name} ref={function (el) { rowRefs.current[l.name] = el; }} style={Object.assign({}, gridCols, { willChange: 'transform' })} className={`px-5 py-3.5 border-b border-neutral-50 last:border-0 ${l.me ? 'bg-[#EAF0F8]' : 'bg-white'}`}>
                  <span><span className={`w-6 h-6 inline-flex items-center justify-center rounded-full text-[12px] font-bold ${rank <= 3 ? 'bg-ink text-white' : 'text-neutral-500'}`}>{rank}</span></span>
                  <span className="inline-flex items-center gap-2 min-w-0"><Avatar name={l.name} size={26} /><span className="font-semibold text-ink truncate">{l.name}{l.me && <span className="ml-1.5 text-[11px] font-bold text-support">You</span>}</span></span>
                  <span className="text-neutral-400">-</span>
                  <span className="text-neutral-500">{l.state}</span>
                  <span className="font-bold num-tabular">{l.pts}</span>
                  <span className="text-right text-primary-700 font-semibold">+{l.reward} pts</span>
                </div>
              );
            })}
          </div>
        </div>
        <p className="mt-4 text-[13px] text-neutral-400">Showing 1-10 of 56</p>
      </div>
    </UserShell>
  );
}


/* ---------- 6) Membership tiers (Unlocked / Locked + progress) ---------- */
function GamiMembershipPage() {
  const tiles = [
    ['Redeem Rewards', 'card', '/app/dashboard/points/rewards'],
    ['Earn More Points', 'spark', '/app/dashboard/points'],
    ['View my points history', 'refresh', '/app/dashboard/points/history'],
    ['Membership FAQs', 'message', '/app/dashboard/points/membership'],
  ];
  return (
    <UserShell route="/app/dashboard/points">
      <div className="max-w-[1200px]">
        <GamiSummaryCard crumb="My Badges" />
        <div className="mt-6 flex items-start justify-between gap-4 flex-wrap">
          <div><h2 className="text-2xl font-bold text-ink" style={{ fontFamily: _pp }}>Mereka Membership</h2><p className="text-[13px] text-neutral-500">Welcome to Mereka membership club! Find out all you can do with your membership here.</p></div>
          <div className="relative w-full sm:w-72"><Icon name="search" size={16} className="absolute left-3.5 top-1/2 -translate-y-1/2 text-neutral-400" /><input placeholder="Search for..." className="w-full h-10 pl-10 pr-4 rounded-full border border-neutral-200 text-sm focus:outline-none focus:border-primary" /></div>
        </div>
        <div className="mt-3 flex items-center gap-2"><GamiStar size={22} /><span className="text-xl font-bold text-ink num-tabular">{GAMI.balance}</span><span className="text-sm text-neutral-500">points</span></div>
        <p className="text-[13px] font-semibold text-ink mt-1">Your points will expire on {GAMI.expiry}.</p>
        <div className="mt-5 grid grid-cols-2 lg:grid-cols-4 gap-4">
          {tiles.map(([l, ic, to]) => (
            <Link key={l} to={to} className="bg-white border border-neutral-200 rounded-2xl p-4 flex flex-col gap-3 hover:border-primary hover:shadow-sm transition-all">
              <span className="w-10 h-10 rounded-xl bg-primary-light text-primary-700 flex items-center justify-center"><Icon name={ic} size={19} /></span>
              <span className="flex items-center justify-between text-sm font-semibold text-ink">{l} <Icon name="chevronRight" size={15} className="text-neutral-400" /></span>
            </Link>
          ))}
        </div>
        <h3 className="mt-8 mb-4 text-lg font-bold text-ink" style={{ fontFamily: _pp }}>Membership tiers</h3>
        <div className="space-y-5">
          {GAMI_TIERS.map((t) => {
            const locked = t.unlock > GAMI.tierPoints;
            const pct = t.unlock > 0 ? Math.min(100, Math.round(GAMI.tierPoints / t.unlock * 100)) : 100;
            return (
              <div key={t.name} className="bg-white border border-neutral-200 rounded-2xl p-6 grid md:grid-cols-[240px_1fr] gap-6">
                <div className="flex flex-col">
                  <span className={`h-6 px-2.5 rounded-full text-[11px] font-bold inline-flex items-center w-fit mb-4 ${locked ? 'bg-neutral-100 text-neutral-500' : 'bg-primary-light text-primary-700'}`}>{locked ? 'Locked' : 'Unlocked'}</span>
                  <div className="flex flex-col items-center text-center">
                    <GamiTierMedal color={t.medal} locked={locked} size={84} />
                    <div className="text-xl font-bold text-ink mt-3" style={{ fontFamily: _pp }}>{t.name}</div>
                    {!locked && <div className="text-[12px] text-neutral-500 mt-0.5">Default</div>}
                    {locked && (
                      <div className="w-full mt-3">
                        <p className="text-[12px] font-semibold text-neutral-600 mb-1">earn {t.unlock - GAMI.tierPoints} pts to unlock</p>
                        <div className="h-2 rounded-full bg-neutral-100 overflow-hidden"><div className="h-full rounded-full bg-green-500" style={{ width: pct + '%' }} /></div>
                        <p className="text-[11px] text-neutral-400 mt-1">{GAMI.tierPoints} of {t.unlock.toLocaleString()} pts earned</p>
                      </div>
                    )}
                  </div>
                </div>
                <div className="space-y-3 md:border-l md:border-neutral-100 md:pl-6">
                  {t.perks.map(([h, d]) => (<div key={h}><div className="text-sm font-bold text-ink">{h}</div><p className="text-[13px] text-neutral-500 leading-snug">{d}</p></div>))}
                  <a href="#" className="text-[13px] font-semibold text-primary-700 hover:underline inline-block">FAQ</a>
                </div>
              </div>
            );
          })}
        </div>
      </div>
    </UserShell>
  );
}

/* ---------- 7) My Badges (earned seals + For you) ---------- */
function GamiBadgesPage() {
  const earned = GAMI_BADGES.filter(b => b.earned);
  return (
    <UserShell route="/app/dashboard/points">
      <div className="max-w-[1200px]">
        <GamiSummaryCard crumb="My Badges" />
        <div className="mt-6 flex items-center justify-between gap-4 flex-wrap"><div><h2 className="text-2xl font-bold text-ink" style={{ fontFamily: _pp }}>My Badges ({earned.length})</h2><p className="text-[13px] text-neutral-500">–</p></div>
          <div className="relative w-full sm:w-72"><Icon name="search" size={16} className="absolute left-3.5 top-1/2 -translate-y-1/2 text-neutral-400" /><input placeholder="Search for..." className="w-full h-10 pl-10 pr-4 rounded-full border border-neutral-200 text-sm focus:outline-none focus:border-primary" /></div>
        </div>
        <div className="mt-5 grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-4">
          {earned.map(b => (
            <div key={b.name} className="relative bg-white border border-neutral-200 rounded-2xl p-5 text-center">
              <span className="absolute top-3 right-3 text-neutral-300"><Icon name="bookmark" size={16} /></span>
              <div className="flex justify-center"><GamiBadgeSeal hue={b.hue} size={72} letter={b.letter} /></div>
              <div className="font-bold text-ink text-sm mt-3">{b.name}</div>
              <p className="text-[12px] text-neutral-500 mt-1">{b.req}</p>
              <p className="text-[11px] text-neutral-400 mt-1.5">{b.date}</p>
            </div>
          ))}
        </div>
        <p className="mt-4 text-[13px] text-neutral-400">Showing 1-{earned.length} of {earned.length}</p>

        <h3 className="mt-8 mb-1 text-xl font-bold text-ink" style={{ fontFamily: _pp }}>For you</h3>
        <p className="text-[13px] text-neutral-500 mb-4">Badges you're close to earning or ones you can complete now.</p>
        <div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-4">
          {GAMI_BADGES_FORYOU.map(b => (
            <div key={b.name} className="bg-white border border-neutral-200 rounded-2xl p-5 text-center">
              <div className="flex justify-center"><GamiBadgeSeal hue={b.hue} locked size={72} /></div>
              <div className="font-bold text-ink text-sm mt-3">{b.name}</div>
              <p className="text-[12px] text-neutral-500 mt-1">{b.req}</p>
              <div className="mt-3 h-1.5 rounded-full bg-neutral-100 overflow-hidden"><div className="h-full rounded-full bg-green-500" style={{ width: (b.done / b.total * 100) + '%' }} /></div>
              <p className="text-[11px] text-neutral-400 mt-1.5">{b.done} of {b.total} completed</p>
            </div>
          ))}
        </div>
      </div>
    </UserShell>
  );
}

/* ---------- 8) Public Learner profile ---------- */
const GAMI_PROFILE = {
  name: 'Aisha A', role: 'Learner', location: 'Kuala Lumpur, Malaysia',
  headline: "Learner's Interest as Headline Here", joined: 'Joined in January 2021',
  speaks: ['English (Fluent)', 'Mandarin (Conversational)', 'Malay (Basic)'],
  about: 'I am interested in .... Aliquet fames eget nisi amet. Nisi erat tincidunt lectus ornare sagittis, sit enim vitae fusce. Aliquet fames eget nisi amet. Nisi erat tincidunt lectus ornare sagittis, sit enim vitae fusce. Aliquet fames eget nisi amet. Aliquet fames eget nisi amet. Nisi erat tincidunt lectus ornare sagittis, sit enim vitae fusce. Aliquet fames eget nisi amet. Aliquet fames eget nisi amet.',
  interests: ['COMPUTING & IT', 'WEB DEVELOPMENT', 'UI/UX', 'HTML & CSS', 'FIGMA', 'BOOTSTRAP'],
};
function GamiLearnerProfilePage() {
  const p = GAMI_PROFILE;
  const earned = GAMI_BADGES.filter(b => b.earned);
  return (
    <PublicShell route="/web">
      <div className="max-w-[900px] mx-auto px-4 lg:px-6 py-6">
        <div className="relative rounded-2xl overflow-hidden">
          <div className="h-56 lg:h-64 bg-gradient-to-br from-[#C9B79C] to-[#8A7A63]" />
          <div className="relative -mt-20 ml-4 lg:ml-6 w-[280px] bg-white rounded-2xl border border-neutral-200 shadow-sm p-5">
            <Avatar name={p.name} size={80} />
            <h1 className="mt-3 text-xl font-bold text-ink" style={{ fontFamily: _pp }}>{p.name}</h1>
            <p className="text-sm text-neutral-500">{p.role}</p>
            <p className="mt-1 text-[13px] text-neutral-500 inline-flex items-center gap-1"><Icon name="pin" size={13} className="text-neutral-400" /> {p.location}</p>
            <div className="mt-3 flex items-center gap-2 text-neutral-500">
              {['globe', 'briefcase', 'user', 'mail'].map(ic => <span key={ic} className="w-8 h-8 rounded-lg bg-neutral-100 flex items-center justify-center hover:bg-neutral-200 cursor-pointer"><Icon name={ic} size={15} /></span>)}
            </div>
            <div className="mt-3 flex items-center gap-1.5">
              {earned.slice(0, 4).map(b => <GamiBadgeSeal key={b.name} hue={b.hue} size={26} letter={b.letter} />)}
              <span className="text-[12px] font-semibold text-neutral-400 ml-1">+2</span>
            </div>
          </div>
        </div>
        <div className="ml-4 lg:ml-6 mt-6">
          <h2 className="text-2xl font-bold text-ink" style={{ fontFamily: _pp }}>{p.headline}</h2>
          <p className="text-[13px] text-neutral-400 mt-1">{p.joined}</p>
          <div className="mt-3 flex items-center gap-2 flex-wrap text-[13px] text-neutral-600">
            <span className="font-semibold text-neutral-400">Speaks:</span>
            {p.speaks.map((s, i) => <React.Fragment key={s}>{i > 0 && <span className="text-neutral-300">|</span>}<span>{s}</span></React.Fragment>)}
          </div>
        </div>
      </div>
      <div className="bg-neutral-50 border-t border-neutral-100">
        <div className="max-w-[900px] mx-auto px-4 lg:px-6 py-10">
          <h3 className="text-xl font-bold text-ink mb-4" style={{ fontFamily: _pp }}>About me</h3>
          <div className="bg-white border border-neutral-200 rounded-2xl p-6 grid lg:grid-cols-[1fr_260px] gap-8">
            <p className="text-[14px] text-neutral-600 leading-relaxed">{p.about}</p>
            <div className="lg:border-l lg:border-neutral-100 lg:pl-8">
              <p className="text-[13px] font-bold text-neutral-500 mb-3">Interests</p>
              <div className="flex flex-wrap gap-2">{p.interests.map(t => <span key={t} className="px-2.5 h-7 inline-flex items-center rounded-md bg-neutral-900 text-white text-[11px] font-bold uppercase tracking-wide">{t}</span>)}</div>
            </div>
          </div>
          <div className="mt-8">{typeof ProgrammeJourney === 'function' && <ProgrammeJourney />}</div>
        </div>
      </div>
    </PublicShell>
  );
}

/* ---------- Dashboard "Quests for you" carousel (embeddable) ---------- */
function GamiDashboardQuests() {
  const on = useGamiOn();
  const [quest, setQuest] = React.useState(null);
  if (!on) return null;
  return (
    <section className="mb-10">
      <div className="flex items-center justify-between gap-4 mb-5 flex-wrap">
        <h2 className="text-2xl font-bold">Quests for you</h2>
        <Link to="/app/dashboard/points" className="h-9 px-4 rounded-full border border-neutral-200 text-[13px] font-semibold inline-flex items-center gap-1 hover:bg-neutral-50">View all <Icon name="chevronRight" size={13} /></Link>
      </div>
      <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
        {GAMI_FEATURED_QUESTS.map((q, i) => (
          <div key={i} style={i === 0 ? { background: 'linear-gradient(150deg,#FFE9A8 0%,#F7C9E0 48%,#C9D8FF 100%)' } : undefined} className={`border rounded-2xl overflow-hidden hover:shadow-sm transition-all flex flex-col ${i === 0 ? 'border-primary/60 ring-1 ring-primary/20' : 'bg-white border-neutral-200'}`}>
            <div className="relative"><GamiQuestArt seed={q.art || 0} size={132} full rounded="rounded-none" /><span className="absolute top-2.5 right-2.5 w-7 h-7 rounded-full bg-white/85 text-neutral-500 flex items-center justify-center"><Icon name="heart" size={14} /></span></div>
            <div className="p-3 flex-1 flex flex-col">
              <div className="font-bold text-ink text-sm leading-snug">{q.title}</div>
              <div className="mt-1.5 flex items-center gap-2 flex-wrap text-[11px]">
                <span className="font-bold text-[#B8860B]">+{q.pts} pts</span>
                {q.expires && <span className="text-red-400 inline-flex items-center gap-0.5"><Icon name="clock" size={10} /> Expires in {q.expires}</span>}
                {q.prog && <span className="text-neutral-400 inline-flex items-center gap-0.5"><Icon name="check" size={10} /> {q.prog}</span>}
                {q.repeatable && <span className="text-neutral-400 inline-flex items-center gap-0.5"><Icon name="refresh" size={10} /> Repeatable</span>}
              </div>
              <div className="mt-3 flex justify-end"><button onClick={() => setQuest(q)} className={`h-8 px-4 rounded-full text-[13px] font-semibold inline-flex items-center gap-1 ${q.cta === 'Continue' ? 'bg-ink text-white hover:bg-black' : 'border border-neutral-200 text-neutral-700 hover:bg-neutral-50'}`}>{q.cta || 'Start'} <Icon name="chevronRight" size={13} /></button></div>
            </div>
          </div>
        ))}
      </div>
      <GamiQuestModal quest={quest} onClose={() => setQuest(null)} />
    </section>
  );
}

/* ---------- Sidebar points + badges block ---------- */
function GamiEarnPopup({ onClose }) {
  const ALL = [
    { id: 'profile', title: 'Complete profile', pts: 10, cta: 'Continue', to: '/app/dashboard/points', fav: true, active: true, forYou: true },
    { id: 'ai4u', title: 'Join AI4U programme', pts: 10, cta: 'Start', to: '/app/dashboard/points', fav: true, active: true, forYou: true },
    { id: 'refer', title: 'Refer and earn', pts: 10, cta: 'Start', to: '/app/dashboard/points/rewards', fav: true, active: true, forYou: false },
    { id: 'course', title: 'Join a course', pts: 10, cta: 'Start', to: '/app/dashboard/points', fav: false, active: true, forYou: true },
    { id: 'experience', title: 'Join an experience', pts: 10, cta: 'Start', to: '/app/dashboard/points', fav: false, active: true, forYou: true },
    { id: 'review', title: 'Leave a review', pts: 10, cta: 'Start', to: '/app/dashboard/points', fav: false, active: false, forYou: true },
  ];
  const [tab, setTab] = React.useState('Favorites');
  const [favs, setFavs] = React.useState(function () { const s = {}; ALL.forEach(function (q) { if (q.fav) s[q.id] = true; }); return s; });
  const toggleFav = function (id) { setFavs(function (f) { const n = Object.assign({}, f); n[id] = !n[id]; return n; }); };
  const list = ALL.filter(function (q) { return tab === 'Favorites' ? !!favs[q.id] : tab === 'Active' ? q.active : q.forYou; });
  const pct = Math.min(100, Math.round(GAMI.tierPoints / GAMI.tierNext * 100));
  return (
    <div className="absolute left-0 top-[calc(100%+10px)] w-[360px] max-w-[calc(100vw-1.5rem)] bg-white rounded-2xl overflow-hidden z-50 border border-neutral-100" style={{ boxShadow: '0 18px 48px rgba(0,0,0,0.18)' }} onClick={function (e) { e.stopPropagation(); }}>
      <div className="px-4 pt-3.5 pb-4" style={{ background: 'linear-gradient(120deg,#F6E7C8 0%,#F3CFE3 38%,#CFE0F6 70%,#CDEAEA 100%)' }}>
        <div className="flex items-center gap-2">
          <GamiTierMedal color="#C77B3B" size={26} />
          <span className="font-bold text-ink text-[17px]" style={{ fontFamily: _pp }}>{GAMI.tier}</span>
          <Icon name="info" size={15} className="text-neutral-500" />
          <span className="ml-auto text-[13px] font-bold text-ink num-tabular">{GAMI.tierPoints} / {GAMI.tierNext}</span>
        </div>
        <div className="mt-3 relative h-2 rounded-full bg-white/70 overflow-hidden">
          <div className="absolute inset-y-0 left-0 rounded-full" style={{ width: pct + '%', background: 'linear-gradient(90deg,#8B5CF6,#EC4899,#F59E0B,#22D3EE)' }} />
        </div>
      </div>
      <div className="px-4 pt-3 pb-1 flex items-center gap-2">
        {['Favorites', 'Active', 'For you'].map(function (t) {
          return <button key={t} onClick={function () { setTab(t); }} className={`h-8 px-4 rounded-full text-[13px] font-semibold transition-colors ${tab === t ? 'bg-ink text-white' : 'border border-neutral-200 text-neutral-700 hover:bg-neutral-50'}`}>{t}</button>;
        })}
      </div>
      <div className="max-h-[46vh] overflow-y-auto">
        {list.length === 0 ? (
          <div className="px-4 py-10 text-center text-[13px] text-neutral-400">No quests here yet.</div>
        ) : list.map(function (q) {
          const isFav = !!favs[q.id];
          return (
            <div key={q.id} className="flex items-center gap-3 px-4 py-3 border-t border-neutral-100">
              <span className="w-5 h-5 rounded-md border-2 border-neutral-300 flex-shrink-0" />
              <span className="flex-1 min-w-0 text-[15px] font-semibold text-ink">{q.title} <span className="ml-1 px-1.5 h-5 inline-flex items-center rounded-full bg-[#FFF4D6] text-[#B8860B] text-[11px] font-bold align-middle">+{q.pts} pts</span></span>
              <button onClick={function () { toggleFav(q.id); }} aria-label="Favorite" aria-pressed={isFav} className={`flex-shrink-0 transition-colors ${isFav ? 'text-ink' : 'text-neutral-300 hover:text-neutral-400'}`}>
                <Icon name="heart" size={20} fill={isFav ? 'currentColor' : 'none'} strokeWidth={isFav ? 0 : 2} />
              </button>
              <Link to={q.to} onClick={onClose} className={`h-8 px-4 rounded-full text-[13px] font-semibold inline-flex items-center gap-1 flex-shrink-0 ${q.cta === 'Continue' ? 'bg-ink text-white hover:bg-black' : 'border border-neutral-200 text-neutral-700 hover:bg-neutral-50'}`}>{q.cta} <Icon name="chevronRight" size={13} /></Link>
            </div>
          );
        })}
      </div>
      <Link to="/app/dashboard/points" onClick={onClose} className="flex items-center justify-end gap-1 px-4 py-3 border-t border-neutral-100 text-sm font-semibold text-neutral-700 hover:bg-neutral-50">See all <Icon name="chevronRight" size={14} /></Link>
    </div>
  );
}
function GamiSidebarBlock() {
  const earned = GAMI_BADGES.filter(b => b.earned);
  const [earnOpen, setEarnOpen] = React.useState(false);
  const earnRef = React.useRef(null);
  React.useEffect(() => { if (!earnOpen) return; const h = (e) => { if (earnRef.current && !earnRef.current.contains(e.target)) setEarnOpen(false); }; document.addEventListener('click', h); return () => document.removeEventListener('click', h); }, [earnOpen]);
  return (
    <div className="mb-4">
      <div className="relative mb-3" ref={earnRef}>
        <button onClick={() => setEarnOpen(o => !o)} className="w-full flex items-center justify-between rounded-full p-0.5 pr-2" style={{ background: 'linear-gradient(90deg,#FFE9A8,#F7C9E0,#C9D8FF)' }}>
          <span className="inline-flex items-center gap-1.5 px-2.5 h-7 text-[13px] font-bold text-[#7a5a10]"><span aria-hidden style={{ color: '#E8901A', display: 'inline-flex' }}><Icon name="star" size={13} fill="#E8901A" strokeWidth={0} /></span> {GAMI.balance} PTS</span>
          <span className="text-[13px] font-semibold text-neutral-700 inline-flex items-center gap-0.5">Earn more <Icon name="chevronRight" size={12} /></span>
        </button>
        {earnOpen && <GamiEarnPopup onClose={() => setEarnOpen(false)} />}
      </div>
      <div className="flex items-center justify-between mb-2">
        <span className="text-[13px] font-bold text-neutral-500">My Badges ({earned.length})</span>
        <Link to="/app/dashboard/points/badges" className="text-[12px] font-semibold text-neutral-400 hover:text-primary-700 inline-flex items-center gap-0.5">See all <Icon name="chevronRight" size={12} /></Link>
      </div>
      <div className="flex items-center gap-2">{earned.slice(0, 4).map(b => <Link key={b.name} to="/app/dashboard/points/badges" title={b.name}><GamiBadgeSeal hue={b.hue} size={34} letter={b.letter} /></Link>)}</div>
    </div>
  );
}
function GamiProfilePoints() {
  return (
    <Link to="/app/dashboard/points" className="inline-flex items-center gap-1.5 mt-1 px-2.5 h-6 rounded-full bg-[#FFF4D6] text-[#B8860B] text-[12px] font-bold hover:bg-[#FCEEC0]">
      <span aria-hidden style={{ color: '#E8B23A', display: 'inline-flex' }}><Icon name="star" size={12} fill="#E8B23A" strokeWidth={0} /></span> {GAMI.balance} pts
    </Link>
  );
}

/* ---------- Quest / weekly-streak popup ---------- */
const GAMI_QUEST_PARTS = [
  { part: 'PART 1', name: 'Welcome', pct: 50, pts: 50, items: [{ title: 'Create an account', pts: 10, done: true }, { title: 'Upload profile photo', pts: 10, done: false }] },
  { part: 'PART 2', name: 'Learn', pct: 0, pts: 50, items: [{ title: 'Complete a course module', pts: 10, done: false }, { title: 'Finish your first course', pts: 40, done: false }] },
  { part: 'PART 3', name: 'Community', pct: 0, pts: 50, items: [{ title: 'Leave a review', pts: 10, done: false }, { title: 'Refer a friend', pts: 40, done: false }] },
];
function GamiQuestPopup({ onClose }) {
  const [open, setOpen] = React.useState({ 0: true });
  const [dismissed, setDismissed] = React.useState(false);
  return (
    <div className="absolute top-[calc(100%+12px)] right-0 w-[400px] max-w-[92vw] bg-white rounded-2xl border border-neutral-100 overflow-hidden z-50" style={{ boxShadow: '0 18px 48px rgba(0,0,0,0.18)' }}>
      <div className="px-4 pt-3 pb-2 text-[13px] font-bold uppercase tracking-wide text-neutral-400">Quest</div>
      {!dismissed && (
        <div className="mx-3 mb-2 rounded-xl bg-[#FFF3D6] p-3">
          <div className="flex items-center justify-between"><div className="font-bold text-ink" style={{ fontFamily: _pp }}>Weekly streak <span aria-hidden>🔥</span> {GAMI.streakWeeks}</div><button onClick={() => setDismissed(true)} className="text-neutral-500 hover:text-neutral-800"><Icon name="close" size={18} /></button></div>
          <div className="mt-3 flex items-center gap-2"><span className="w-4 h-4 rounded border border-neutral-300 bg-white flex-shrink-0" /><span className="flex-1 text-sm text-neutral-600">Complete a course module <span className="ml-1 px-1.5 h-5 inline-flex items-center rounded-full bg-[#FFF4D6] text-[#B8860B] text-[11px] font-bold">+10 pts</span></span><Link to="/app/dashboard/courses" onClick={onClose} className="h-8 px-3 rounded-full bg-white border border-neutral-200 text-[13px] font-semibold inline-flex items-center gap-1 hover:bg-neutral-50">Continue <Icon name="chevronRight" size={13} /></Link></div>
        </div>
      )}
      <div className="max-h-[46vh] overflow-y-auto">
        {GAMI_QUEST_PARTS.map((p, i) => (
          <div key={i} className="border-t border-neutral-100">
            <button onClick={() => setOpen(o => ({ ...o, [i]: !o[i] }))} className="w-full flex items-center justify-between px-4 py-3 text-left hover:bg-neutral-50">
              <div><div className="text-[11px] font-bold uppercase tracking-wide text-neutral-400">{p.part} ({p.pct}%) {i === 0 && <span className="ml-1 px-1.5 h-4 inline-flex items-center rounded-full bg-[#FFF4D6] text-[#B8860B] text-[10px] font-bold align-middle">+{p.pts} pts</span>}</div><div className="font-bold text-ink inline-flex items-center gap-1" style={{ fontFamily: _pp }}>{p.name} <Icon name={open[i] ? 'chevronUp' : 'chevronDown'} size={15} className="text-neutral-400" /></div></div>
              {!open[i] && <span className="px-2 h-6 inline-flex items-center rounded-full bg-[#FFF4D6] text-[#B8860B] text-[12px] font-bold">+{p.pts} pts</span>}
            </button>
            {open[i] && (
              <div className="pb-1">
                {p.items.map((it, j) => (
                  <div key={j} className={`flex items-center gap-2 px-4 py-2.5 ${it.done ? 'bg-neutral-50' : ''}`}>
                    {it.done ? <span className="w-4 h-4 rounded border border-neutral-300 bg-neutral-200 flex items-center justify-center flex-shrink-0"><Icon name="check" size={11} className="text-neutral-500" /></span> : <span className="w-4 h-4 rounded border border-neutral-300 bg-white flex-shrink-0" />}
                    <span className={`flex-1 text-sm ${it.done ? 'line-through text-neutral-400' : 'text-neutral-700'}`}>{it.title} <span className="ml-1 px-1.5 h-5 inline-flex items-center rounded-full bg-[#FFF4D6] text-[#B8860B] text-[11px] font-bold">+{it.pts} pts</span></span>
                    {!it.done && <Link to="/app/dashboard/points" onClick={onClose} className="h-8 px-3 rounded-full bg-white border border-neutral-200 text-[13px] font-semibold inline-flex items-center gap-1 hover:bg-neutral-50">Continue <Icon name="chevronRight" size={13} /></Link>}
                  </div>
                ))}
              </div>
            )}
          </div>
        ))}
      </div>
      <Link to="/app/dashboard/points" onClick={onClose} className="flex items-center justify-end gap-1 px-4 py-3 border-t border-neutral-100 text-sm font-semibold text-neutral-700 hover:bg-neutral-50">See all <Icon name="chevronRight" size={14} /></Link>
    </div>
  );
}
function GamiQuestTrigger() {
  const on = useGamiOn();
  const [open, setOpen] = React.useState(false);
  const ref = React.useRef(null);
  React.useEffect(() => { if (!open) return; const h = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); }; document.addEventListener('click', h); return () => document.removeEventListener('click', h); }, [open]);
  if (!on) return null;
  return (
    <div className="relative" ref={ref}>
      <button onClick={() => setOpen(o => !o)} className="relative w-10 h-10 flex items-center justify-center rounded-full hover:bg-neutral-100 transition-colors" aria-label="Quests and streak">
        <Icon name="award" size={23} className="text-neutral-900" />
        <span className="absolute top-1 right-1 min-w-[16px] h-4 px-1 rounded-full bg-[#E8901A] text-white text-[10px] font-bold flex items-center justify-center">{GAMI.streakWeeks}</span>
      </button>
      {open && <GamiQuestPopup onClose={() => setOpen(false)} />}
    </div>
  );
}

/* ---------- Floating on/off toggle ---------- */
function GamificationToggle() {
  const on = useGamiOn();
  return (
    <button onClick={() => setGamiOn(!on)} title="Toggle the gamification feature set"
      className={`fixed bottom-[76px] right-5 z-50 inline-flex items-center gap-2 px-4 py-2.5 rounded-full shadow-lg transition-all ${on ? 'bg-primary text-white hover:bg-primary-hover' : 'bg-white text-neutral-600 border border-neutral-300 hover:bg-neutral-50'}`}>
      <Icon name="spark" size={16} /> Gamification
      <span className={`ml-1 inline-flex items-center h-5 w-9 rounded-full p-0.5 transition-colors ${on ? 'bg-white/30' : 'bg-neutral-200'}`}><span className={`w-4 h-4 rounded-full bg-white shadow transform transition-transform ${on ? 'translate-x-4' : ''}`} /></span>
    </button>
  );
}

Object.assign(window, {
  useGamiOn, setGamiOn,
  GamiPointsRewardsPage, GamiAllRewardsPage, GamiMyRedemptionsPage, GamiPointHistoryPage,
  GamiLeaderboardPage, GamiMembershipPage, GamiBadgesPage, GamiLearnerProfilePage, GamificationToggle,
  GamiSidebarBlock, GamiProfilePoints, GamiQuestTrigger, GamiQuestPopup, GamiDashboardQuests,
});
