import { useState } from "react"; import { useInventoryStore, getLiveEffects, formatTimeLeft, hasActiveEffect, } from "../stores/useInventoryStore"; import { InventoryModal } from "./InventoryModal"; // ─── Styles ─────────────────────────────────────────────────────────────────── const BTN_STYLES = ` @import url('https://fonts.googleapis.com/css2?family=Nunito:wght@800;900&family=Cinzel:wght@700&display=swap'); /* ── Inventory trigger button ── */ .inv-btn { position: relative; display: inline-flex; align-items: center; gap: 0.38rem; padding: 0.48rem 0.85rem; background: rgba(255,255,255,0.05); border: 1.5px solid rgba(255,255,255,0.1); border-radius: 100px; cursor: pointer; font-family: 'Nunito', sans-serif; font-size: 0.72rem; font-weight: 900; color: rgba(255,255,255,0.7); transition: all 0.18s ease; outline: none; white-space: nowrap; } .inv-btn:hover { background: rgba(255,255,255,0.09); border-color: rgba(255,255,255,0.2); color: white; transform: translateY(-1px); box-shadow: 0 4px 16px rgba(0,0,0,0.25); } .inv-btn:active { transform: translateY(0) scale(0.97); } /* When active effects are running — gold glow */ .inv-btn.has-active { border-color: rgba(251,191,36,0.45); color: #fbbf24; background: rgba(251,191,36,0.08); animation: invBtnGlow 2.6s ease-in-out infinite; } @keyframes invBtnGlow { 0%,100% { box-shadow: 0 0 0 0 rgba(251,191,36,0); } 50% { box-shadow: 0 0 14px 3px rgba(251,191,36,0.2); } } .inv-btn.has-active:hover { border-color: rgba(251,191,36,0.7); background: rgba(251,191,36,0.14); } /* Badge dot */ .inv-btn-badge { position: absolute; top: -4px; right: -4px; width: 14px; height: 14px; border-radius: 50%; background: #fbbf24; border: 2px solid transparent; /* will be set to match parent bg via CSS var */ display: flex; align-items: center; justify-content: center; font-family: 'Nunito', sans-serif; font-size: 0.45rem; font-weight: 900; color: #1a0800; animation: invBadgePop 1.8s ease-in-out infinite; } @keyframes invBadgePop { 0%,100%{ transform: scale(1); } 50% { transform: scale(1.15); } } /* ── Active Effect Banner (shown on other screens, e.g. pretest) ── */ .aeb-wrap { display: flex; gap: 0.5rem; flex-wrap: wrap; } .aeb-pill { display: inline-flex; align-items: center; gap: 0.4rem; padding: 0.38rem 0.85rem; border-radius: 100px; font-family: 'Nunito', sans-serif; font-size: 0.72rem; font-weight: 900; animation: aebPillIn 0.35s cubic-bezier(0.34,1.56,0.64,1) both; animation-delay: var(--aeb-delay, 0s); } @keyframes aebPillIn { from { opacity:0; transform: scale(0.8) translateY(6px); } to { opacity:1; transform: scale(1) translateY(0); } } /* Color variants per effect type */ .aeb-pill.xp_boost { background: rgba(251,191,36,0.12); border: 1.5px solid rgba(251,191,36,0.4); color: #fbbf24; } .aeb-pill.streak_shield { background: rgba(96,165,250,0.1); border: 1.5px solid rgba(96,165,250,0.35); color: #60a5fa; } .aeb-pill.coin_boost { background: rgba(167,243,208,0.08); border: 1.5px solid rgba(52,211,153,0.35); color: #34d399; } .aeb-pill.default { background: rgba(255,255,255,0.06); border: 1.5px solid rgba(255,255,255,0.15); color: rgba(255,255,255,0.7); } .aeb-pill-icon { font-size: 0.9rem; line-height:1; } .aeb-pill-label { line-height:1; } .aeb-pill-time { font-family: 'Nunito Sans', sans-serif; font-size: 0.58rem; font-weight: 700; opacity: 0.55; margin-left: 0.1rem; } `; const ITEM_ICON: Record = { xp_boost: "⚡", streak_shield: "🛡️", title: "🏴‍☠️", coin_boost: "🪙", }; function itemIcon(effectType: string): string { return ITEM_ICON[effectType] ?? "📦"; } // ─── InventoryButton ────────────────────────────────────────────────────────── /** * Drop-in trigger button. Can be placed in any nav bar, header, or screen. * Shows a gold glow + badge count when active effects are running. * * Usage: * * */ export const InventoryButton = ({}: {}) => { const [open, setOpen] = useState(false); const activeEffects = useInventoryStore((s) => s.activeEffects); const liveEffects = getLiveEffects(activeEffects); const hasActive = liveEffects.length > 0; return ( <> {open && setOpen(false)} />} ); }; // ─── ActiveEffectBanner ─────────────────────────────────────────────────────── /** * Shows pills for each currently-active effect. * Place wherever you want a contextual reminder (pretest screen, dashboard, etc.) * * Usage: * * ← only show a specific effect * * Example output on Pretest screen: * ⚡ XP Boost ×2 · 1h 42m 🛡️ Streak Shield · 23m */ export const ActiveEffectBanner = ({ filter, className, }: { filter?: string; className?: string; }) => { const activeEffects = useInventoryStore((s) => s.activeEffects); const live = getLiveEffects(activeEffects).filter( (e) => !filter || e.item.effect_type === filter, ); if (live.length === 0) return null; return ( <>
{live.map((e, i) => (
{itemIcon(e.item.effect_type)} {e.item.name} {e.item.effect_type === "xp_boost" && e.item.effect_value ? ` ×${e.item.effect_value}` : ""} {formatTimeLeft(e.expires_at)}
))}
); };