diff --git a/frontend/public/assets/icons/apps/market/hover.png b/frontend/public/assets/icons/apps/market/hover.png new file mode 100644 index 0000000..d7813fa Binary files /dev/null and b/frontend/public/assets/icons/apps/market/hover.png differ diff --git a/frontend/public/assets/icons/apps/market/regular.png b/frontend/public/assets/icons/apps/market/regular.png new file mode 100644 index 0000000..6f47646 Binary files /dev/null and b/frontend/public/assets/icons/apps/market/regular.png differ diff --git a/frontend/public/assets/icons/apps/messages/hover.png b/frontend/public/assets/icons/apps/messages/hover.png new file mode 100644 index 0000000..a66e448 Binary files /dev/null and b/frontend/public/assets/icons/apps/messages/hover.png differ diff --git a/frontend/public/assets/icons/apps/messages/regular.png b/frontend/public/assets/icons/apps/messages/regular.png new file mode 100644 index 0000000..81da9fe Binary files /dev/null and b/frontend/public/assets/icons/apps/messages/regular.png differ diff --git a/frontend/public/assets/icons/apps/news/hover.png b/frontend/public/assets/icons/apps/news/hover.png new file mode 100644 index 0000000..831acca Binary files /dev/null and b/frontend/public/assets/icons/apps/news/hover.png differ diff --git a/frontend/public/assets/icons/apps/news/regular.png b/frontend/public/assets/icons/apps/news/regular.png new file mode 100644 index 0000000..5891efb Binary files /dev/null and b/frontend/public/assets/icons/apps/news/regular.png differ diff --git a/frontend/public/assets/icons/apps/shoebox/hover.png b/frontend/public/assets/icons/apps/shoebox/hover.png new file mode 100644 index 0000000..5d9db4e Binary files /dev/null and b/frontend/public/assets/icons/apps/shoebox/hover.png differ diff --git a/frontend/public/assets/icons/apps/shoebox/regular.png b/frontend/public/assets/icons/apps/shoebox/regular.png new file mode 100644 index 0000000..3af525d Binary files /dev/null and b/frontend/public/assets/icons/apps/shoebox/regular.png differ diff --git a/frontend/public/assets/icons/apps/shot-caller/hover.png b/frontend/public/assets/icons/apps/shot-caller/hover.png new file mode 100644 index 0000000..eb5ee61 Binary files /dev/null and b/frontend/public/assets/icons/apps/shot-caller/hover.png differ diff --git a/frontend/public/assets/icons/apps/shot-caller/regular.png b/frontend/public/assets/icons/apps/shot-caller/regular.png new file mode 100644 index 0000000..6e7d72d Binary files /dev/null and b/frontend/public/assets/icons/apps/shot-caller/regular.png differ diff --git a/frontend/public/assets/icons/apps/trap-house/hover.png b/frontend/public/assets/icons/apps/trap-house/hover.png new file mode 100644 index 0000000..55b04f0 Binary files /dev/null and b/frontend/public/assets/icons/apps/trap-house/hover.png differ diff --git a/frontend/public/assets/icons/apps/trap-house/regular.png b/frontend/public/assets/icons/apps/trap-house/regular.png new file mode 100644 index 0000000..5a3623e Binary files /dev/null and b/frontend/public/assets/icons/apps/trap-house/regular.png differ diff --git a/frontend/public/assets/icons/apps/trap/hover.png b/frontend/public/assets/icons/apps/trap/hover.png new file mode 100644 index 0000000..7339599 Binary files /dev/null and b/frontend/public/assets/icons/apps/trap/hover.png differ diff --git a/frontend/public/assets/icons/apps/trap/regular.png b/frontend/public/assets/icons/apps/trap/regular.png new file mode 100644 index 0000000..3176551 Binary files /dev/null and b/frontend/public/assets/icons/apps/trap/regular.png differ diff --git a/frontend/src/components/common/GameSprite.css b/frontend/src/components/common/GameSprite.css index 6007111..2332621 100644 --- a/frontend/src/components/common/GameSprite.css +++ b/frontend/src/components/common/GameSprite.css @@ -38,3 +38,34 @@ .game-sprite.hit { animation: spriteFlash 0.2s ease-out 3; } + + +/* Generated app icon state swap: regular at rest, hover art on pointer hover/focus. */ +.game-icon-frames { + position: relative; + overflow: hidden; + vertical-align: middle; +} +.game-icon-frame { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + border-radius: inherit; + object-fit: cover; + transition: opacity 0.16s ease, transform 0.16s ease, filter 0.16s ease; +} +.game-icon-hover { + opacity: 0; + transform: scale(0.96); +} +.game-icon-frames:hover .game-icon-regular, +.game-icon-frames:focus-visible .game-icon-regular { + opacity: 0; +} +.game-icon-frames:hover .game-icon-hover, +.game-icon-frames:focus-visible .game-icon-hover { + opacity: 1; + transform: scale(1); + filter: brightness(1.08) saturate(1.08); +} diff --git a/frontend/src/components/common/GameSprite.tsx b/frontend/src/components/common/GameSprite.tsx index b2aa076..5cbe542 100644 --- a/frontend/src/components/common/GameSprite.tsx +++ b/frontend/src/components/common/GameSprite.tsx @@ -91,22 +91,34 @@ const APP_ICONS: Record = { crew: '/assets/icons/icon_crew_new.png', dealt: '/assets/icons/icon_dealt_new.png', ops: '/assets/icons/icon_ops_new.png', - shoebox: '/assets/icons/icon_shoebox_new.png', - market: '/assets/icons/icon_market_new.png', + // shoebox/market use the new per-app artwork (replaces old icon_*_new.png) + shoebox: '/assets/icons/apps/shoebox/regular.png', + market: '/assets/icons/apps/market/regular.png', casino: '/assets/icons/icon_casino_new.png', settings: '/assets/icons/icon_settings_new.png', driveby: '/assets/icons/icon_driveby_new.png', graffiti: '/assets/icons/icon_graffiti_new.png', - news: '/assets/generated/ui/icons/icon_news_v001.png', - // Sprint: trap-app-icon-deemoji — dedicated icons replacing fallbacks - leaderboard: '/assets/icons/icon_leaderboard_new.png', + news: '/assets/icons/apps/news/regular.png', + leaderboard: '/assets/icons/apps/shot-caller/regular.png', cocaine_crush: '/assets/icons/icon_casino_new.png', - trap: '/assets/icons/icon_trap_new.png', + trap: '/assets/icons/apps/trap/regular.png', contacts: '/assets/icons/icon_contacts_new.png', bipndip: '/assets/icons/icon_bipndip_new.png', attack: '/assets/icons/icon_attack_new.png', planner: '/assets/icons/icon_planner_new.png', raid: '/assets/icons/icon_raid_new.png', + messages: '/assets/icons/apps/messages/regular.png', + trap_house: '/assets/icons/apps/trap-house/regular.png', +}; + +const APP_ICONS_HOVER: Record = { + shoebox: '/assets/icons/apps/shoebox/hover.png', + trap: '/assets/icons/apps/trap/hover.png', + market: '/assets/icons/apps/market/hover.png', + messages: '/assets/icons/apps/messages/hover.png', + trap_house: '/assets/icons/apps/trap-house/hover.png', + leaderboard: '/assets/icons/apps/shot-caller/hover.png', + news: '/assets/icons/apps/news/hover.png', }; // Role-to-frame mapping for gang members @@ -158,6 +170,8 @@ interface GameSpriteProps { frame?: number; /** App icon name (alternative to sheet+frame) */ icon?: keyof typeof APP_ICONS; + /** Optional hover-state app icon key */ + hoverIcon?: keyof typeof APP_ICONS_HOVER; /** Display size in pixels */ size?: number; /** Optional CSS class */ @@ -174,6 +188,7 @@ export const GameSprite: React.FC = ({ sheet, frame = 0, icon, + hoverIcon, size = 48, className = '', style = {}, @@ -188,6 +203,34 @@ export const GameSprite: React.FC = ({ if (!iconUrl || error) { return fallback ? {fallback} : null; } + const hoverUrl = hoverIcon ? APP_ICONS_HOVER[hoverIcon] : undefined; + if (hoverUrl) { + return ( + + {alt setError(true)} + loading="lazy" + /> + + + ); + } return ( = ({ gangMorale = 75, incomePerMinute = 0 }, { id: 'leaderboard', - label: 'RANKINGS', - icon: 'RANK', + label: 'SHOT CALLER', + icon: 'SHOT CALLER', spriteIcon: 'leaderboard', colorClass: 'app-gold', available: true, - description: 'Global Leaderboard', + description: 'Block Attack Alerts', }, { id: 'news', @@ -238,6 +238,15 @@ const OSShell: React.FC = ({ gangMorale = 75, incomePerMinute = 0 available: true, description: 'Call Contacts', }, + { + id: 'messages', + label: 'MESSAGES', + icon: 'MESSAGES', + spriteIcon: 'messages', + colorClass: 'app-cyan', + available: true, + description: 'Text Messages', + }, { id: 'planner', label: 'OPS PLAN', @@ -260,7 +269,7 @@ const OSShell: React.FC = ({ gangMorale = 75, incomePerMinute = 0 const handleAppClick = (app: AppIcon) => { if (app.available) { - navigateTo(app.id); + navigateTo(app.id === 'messages' ? 'phone' : app.id); } }; @@ -456,7 +465,12 @@ const OSShell: React.FC = ({ gangMorale = 75, incomePerMinute = 0 handleAppClick(app)} + onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); handleAppClick(app); } }} initial={{ opacity: 0, scale: 0.5 }} animate={{ opacity: 1, scale: 1 }} transition={{ @@ -470,7 +484,7 @@ const OSShell: React.FC = ({ gangMorale = 75, incomePerMinute = 0 >
{app.spriteIcon ? ( - + ) : ( {app.icon} )}