From 79fc2eacdc5ead65286ce2d3f1f6ba83a7634527 Mon Sep 17 00:00:00 2001 From: shafin-r Date: Wed, 4 Mar 2026 01:23:21 +0600 Subject: [PATCH] fix(inventory): fix inventory modal instantiation --- src/components/InventoryModal.tsx | 104 ++++++------------------------ 1 file changed, 19 insertions(+), 85 deletions(-) diff --git a/src/components/InventoryModal.tsx b/src/components/InventoryModal.tsx index 3b371ae..a87bf11 100644 --- a/src/components/InventoryModal.tsx +++ b/src/components/InventoryModal.tsx @@ -1,4 +1,5 @@ import { useEffect, useRef, useState, useCallback } from "react"; +import { createPortal } from "react-dom"; import { X } from "lucide-react"; import type { InventoryItem, ActiveEffect } from "../types/quest"; import { @@ -43,7 +44,6 @@ const STYLES = ` to { transform: translateY(0); opacity:1; } } - /* Sea shimmer bg */ .inv-sheet::before { content: ''; position: absolute; inset: 0; pointer-events: none; z-index: 0; @@ -58,7 +58,6 @@ const STYLES = ` 100% { background-position: 100% 100%, 0% 100%; } } - /* Gold orb top-right */ .inv-sheet::after { content: ''; position: absolute; top: -60px; right: -40px; z-index: 0; @@ -67,7 +66,6 @@ const STYLES = ` pointer-events: none; } - /* ── Handle ── */ .inv-handle-row { display: flex; justify-content: center; padding: 0.75rem 0 0; flex-shrink: 0; position: relative; z-index: 2; @@ -77,7 +75,6 @@ const STYLES = ` background: rgba(255,255,255,0.1); } - /* ── Header ── */ .inv-header { position: relative; z-index: 2; display: flex; align-items: center; justify-content: space-between; @@ -108,7 +105,6 @@ const STYLES = ` background: rgba(251,191,36,0.1); } - /* ── Active effects banner ── */ .inv-active-bar { position: relative; z-index: 2; display: flex; gap: 0.5rem; overflow-x: auto; scrollbar-width: none; @@ -140,7 +136,6 @@ const STYLES = ` margin-left: 0.1rem; } - /* ── Divider ── */ .inv-divider { position: relative; z-index: 2; height: 1px; margin: 0.85rem 1.3rem 0; @@ -154,7 +149,6 @@ const STYLES = ` text-transform: uppercase; color: rgba(255,255,255,0.25); } - /* ── Scrollable item grid ── */ .inv-scroll { position: relative; z-index: 2; flex: 1; overflow-y: auto; scrollbar-width: none; @@ -162,7 +156,6 @@ const STYLES = ` } .inv-scroll::-webkit-scrollbar { display: none; } - /* ── Empty state ── */ .inv-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 0.6rem; @@ -173,7 +166,6 @@ const STYLES = ` } .inv-empty-icon { font-size: 2.5rem; opacity: 0.4; } - /* ── Loading skeleton ── */ .inv-skeleton-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0.75rem; } @@ -187,12 +179,10 @@ const STYLES = ` 50% { opacity: 1; } } - /* ── Item grid ── */ .inv-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0.75rem; } - /* ── Item card ── */ .inv-card { border-radius: 20px; padding: 1rem; border: 1.5px solid rgba(255,255,255,0.07); @@ -213,8 +203,6 @@ const STYLES = ` transform: translateY(-2px); } .inv-card:active { transform: translateY(0) scale(0.98); } - - /* Active card styling */ .inv-card.is-active { border-color: rgba(251,191,36,0.4); background: rgba(251,191,36,0.06); @@ -223,26 +211,17 @@ const STYLES = ` border-color: rgba(251,191,36,0.6); background: rgba(251,191,36,0.09); } - - /* Just-activated flash */ @keyframes invActivateFlash { 0% { background: rgba(251,191,36,0.25); border-color: rgba(251,191,36,0.8); } 100%{ background: rgba(251,191,36,0.06); border-color: rgba(251,191,36,0.4); } } - .inv-card.just-activated { - animation: invActivateFlash 0.9s ease forwards; - } - - /* Card shimmer overlay */ + .inv-card.just-activated { animation: invActivateFlash 0.9s ease forwards; } .inv-card-sheen { position: absolute; inset: 0; pointer-events: none; background: linear-gradient(135deg, transparent 30%, rgba(255,255,255,0.04) 50%, transparent 70%); - transform: translateX(-100%); - transition: transform 0.5s ease; + transform: translateX(-100%); transition: transform 0.5s ease; } .inv-card:hover .inv-card-sheen { transform: translateX(100%); } - - /* Icon area */ .inv-card-icon-wrap { width: 44px; height: 44px; border-radius: 14px; display: flex; align-items: center; justify-content: center; @@ -258,30 +237,23 @@ const STYLES = ` .inv-card-active-dot { position: absolute; top: -3px; right: -3px; width: 10px; height: 10px; border-radius: 50%; - background: #fbbf24; - border: 2px solid #08111f; + background: #fbbf24; border: 2px solid #08111f; animation: invDotPulse 2s ease-in-out infinite; } @keyframes invDotPulse { 0%,100% { box-shadow: 0 0 0 0 rgba(251,191,36,0.6); } 50% { box-shadow: 0 0 0 5px rgba(251,191,36,0); } } - - /* Card text */ .inv-card-name { font-family: 'Nunito', sans-serif; - font-size: 0.82rem; font-weight: 900; color: #fff; - line-height: 1.2; + font-size: 0.82rem; font-weight: 900; color: #fff; line-height: 1.2; } .inv-card.is-active .inv-card-name { color: #fbbf24; } .inv-card-desc { font-family: 'Nunito Sans', sans-serif; font-size: 0.63rem; font-weight: 600; - color: rgba(255,255,255,0.38); line-height: 1.4; - flex: 1; + color: rgba(255,255,255,0.38); line-height: 1.4; flex: 1; } - - /* Qty + type row */ .inv-card-meta { display: flex; align-items: center; justify-content: space-between; gap: 0.4rem; margin-top: auto; @@ -299,11 +271,8 @@ const STYLES = ` letter-spacing: 0.1em; text-transform: uppercase; color: rgba(255,255,255,0.22); } - - /* Activate button */ .inv-activate-btn { - width: 100%; - padding: 0.48rem; + width: 100%; padding: 0.48rem; border-radius: 10px; border: none; cursor: pointer; font-family: 'Nunito', sans-serif; font-size: 0.7rem; font-weight: 900; @@ -315,10 +284,7 @@ const STYLES = ` border: 1px solid rgba(255,255,255,0.1); color: rgba(255,255,255,0.6); } - .inv-activate-btn.idle:hover { - background: rgba(255,255,255,0.12); - color: white; - } + .inv-activate-btn.idle:hover { background: rgba(255,255,255,0.12); color: white; } .inv-activate-btn.activating { background: rgba(251,191,36,0.1); border: 1px solid rgba(251,191,36,0.25); @@ -330,8 +296,7 @@ const STYLES = ` .inv-activate-btn.active-state { background: rgba(251,191,36,0.12); border: 1px solid rgba(251,191,36,0.3); - color: #fbbf24; - cursor: default; + color: #fbbf24; cursor: default; } .inv-activate-btn.success-flash { background: rgba(74,222,128,0.18); @@ -339,24 +304,17 @@ const STYLES = ` color: #4ade80; animation: invSuccessScale 0.35s cubic-bezier(0.34,1.56,0.64,1) both; } - @keyframes invSuccessScale { - from { transform: scale(0.94); } - to { transform: scale(1); } - } + @keyframes invSuccessScale { from{transform:scale(0.94)} to{transform:scale(1)} } .inv-activate-btn:disabled { pointer-events: none; } - - /* Time remaining on active button */ .inv-active-time { font-family: 'Nunito Sans', sans-serif; - font-size: 0.55rem; font-weight: 700; - color: rgba(251,191,36,0.5); + font-size: 0.55rem; font-weight: 700; color: rgba(251,191,36,0.5); } - /* ── Toast ── */ .inv-toast { position: fixed; bottom: calc(1.5rem + env(safe-area-inset-bottom)); left: 50%; transform: translateX(-50%); - z-index: 90; + z-index: 9999; display: flex; align-items: center; gap: 0.55rem; padding: 0.7rem 1.2rem; background: linear-gradient(135deg, #1a3a1a, #0d2010); @@ -380,13 +338,11 @@ const ITEM_ICON: Record = { title: "🏴‍☠️", coin_boost: "🪙", }; -const ITEM_ICON_DEFAULT = "📦"; function itemIcon(effectType: string): string { - return ITEM_ICON[effectType] ?? ITEM_ICON_DEFAULT; + return ITEM_ICON[effectType] ?? "📦"; } -// ─── Check if an item is currently active ───────────────────────────────────── function isItemActive( item: InventoryItem, activeEffects: ActiveEffect[], @@ -438,33 +394,23 @@ const ItemCard = ({ style={{ "--ci-delay": `${index * 0.045}s` } as React.CSSProperties} >
- - {/* Icon */}
{itemIcon(inv.item.effect_type)} {isActive &&
}
- - {/* Name + description */}

{inv.item.name}

{inv.item.description}

- - {/* Qty + type */}
×{inv.quantity} {inv.item.type.replace(/_/g, " ")}
- - {/* Time remaining if active */} {isActive && activeEffect && (
{formatTimeLeft(activeEffect.expires_at)} remaining
)} - - {/* Activate button */}
- {/* Active effects bar */} {liveEffects.length > 0 && (
{liveEffects.map((e) => ( @@ -616,7 +552,6 @@ export const InventoryModal = ({ onClose }: Props) => { : "Your hold"}

- {/* Scroll area */}
{loading && items.length === 0 ? (
@@ -649,7 +584,6 @@ export const InventoryModal = ({ onClose }: Props) => {
)} - {/* Error inline */} {error && (

{

- {/* Success toast */} {showToast &&
{toastMsg}
} - + , + document.body, ); };