diff --git a/images-source/make_webp.sh b/images-source/make_webp.sh index f4ca93e..47e1b36 100755 --- a/images-source/make_webp.sh +++ b/images-source/make_webp.sh @@ -54,8 +54,15 @@ for category in items locations npcs interactables characters placeholder static rm "$tmp" else # Standard conversion for other categories + # If locations or interactables, crop to 16:9 + tmp="/tmp/${base}_clean.png" + if [[ "$category" == "locations" || "$category" == "interactables" ]]; then + convert "$img" -resize "16:9" "$tmp" + else + convert "$img" "$tmp" + fi echo " ➜ Converting: $filename" - cwebp "$img" -q 85 -o "$out_file" >/dev/null + cwebp "$tmp" -q 85 -o "$out_file" >/dev/null fi done done diff --git a/images/interactables/dumpster.webp b/images/interactables/dumpster.webp index f6193c9..8bb9fbd 100644 Binary files a/images/interactables/dumpster.webp and b/images/interactables/dumpster.webp differ diff --git a/images/interactables/house.webp b/images/interactables/house.webp index a6baf24..c0ce140 100644 Binary files a/images/interactables/house.webp and b/images/interactables/house.webp differ diff --git a/images/interactables/medkit.webp b/images/interactables/medkit.webp index f98c202..4239f19 100644 Binary files a/images/interactables/medkit.webp and b/images/interactables/medkit.webp differ diff --git a/images/interactables/rubble.webp b/images/interactables/rubble.webp index 6e73d63..923234c 100644 Binary files a/images/interactables/rubble.webp and b/images/interactables/rubble.webp differ diff --git a/images/interactables/sedan.webp b/images/interactables/sedan.webp index fa216c5..6d5e8f0 100644 Binary files a/images/interactables/sedan.webp and b/images/interactables/sedan.webp differ diff --git a/images/interactables/storage_box.webp b/images/interactables/storage_box.webp index 3242702..f15e4eb 100644 Binary files a/images/interactables/storage_box.webp and b/images/interactables/storage_box.webp differ diff --git a/images/interactables/toolshed.webp b/images/interactables/toolshed.webp index ad95602..d1a8b6e 100644 Binary files a/images/interactables/toolshed.webp and b/images/interactables/toolshed.webp differ diff --git a/images/interactables/vending.webp b/images/interactables/vending.webp index d8c50a2..1cc0c1a 100644 Binary files a/images/interactables/vending.webp and b/images/interactables/vending.webp differ diff --git a/images/locations/clinic.webp b/images/locations/clinic.webp index 968c0d1..8d17d40 100644 Binary files a/images/locations/clinic.webp and b/images/locations/clinic.webp differ diff --git a/images/locations/downtown.webp b/images/locations/downtown.webp index 0da8d3a..7bd0ce5 100644 Binary files a/images/locations/downtown.webp and b/images/locations/downtown.webp differ diff --git a/images/locations/gas_station.webp b/images/locations/gas_station.webp index a818fc0..d8e33d5 100644 Binary files a/images/locations/gas_station.webp and b/images/locations/gas_station.webp differ diff --git a/images/locations/office_building.webp b/images/locations/office_building.webp index 73bc932..5b2b318 100644 Binary files a/images/locations/office_building.webp and b/images/locations/office_building.webp differ diff --git a/images/locations/office_interior.webp b/images/locations/office_interior.webp index f03bfd8..1184a2f 100644 Binary files a/images/locations/office_interior.webp and b/images/locations/office_interior.webp differ diff --git a/images/locations/overpass.webp b/images/locations/overpass.webp index ef531ec..c701504 100644 Binary files a/images/locations/overpass.webp and b/images/locations/overpass.webp differ diff --git a/images/locations/park.webp b/images/locations/park.webp index fd1ef6c..b20779f 100644 Binary files a/images/locations/park.webp and b/images/locations/park.webp differ diff --git a/images/locations/plaza.webp b/images/locations/plaza.webp index 9ccedc9..3e5c70a 100644 Binary files a/images/locations/plaza.webp and b/images/locations/plaza.webp differ diff --git a/images/locations/residential.webp b/images/locations/residential.webp index 4d6e4df..e26c641 100644 Binary files a/images/locations/residential.webp and b/images/locations/residential.webp differ diff --git a/images/locations/subway.webp b/images/locations/subway.webp index 5a329ed..7c9ad85 100644 Binary files a/images/locations/subway.webp and b/images/locations/subway.webp differ diff --git a/images/locations/subway_section_a.webp b/images/locations/subway_section_a.webp index 62d3955..cd353ce 100644 Binary files a/images/locations/subway_section_a.webp and b/images/locations/subway_section_a.webp differ diff --git a/images/locations/subway_tunnels.webp b/images/locations/subway_tunnels.webp index d466522..e22cc59 100644 Binary files a/images/locations/subway_tunnels.webp and b/images/locations/subway_tunnels.webp differ diff --git a/images/locations/warehouse.webp b/images/locations/warehouse.webp index 1af7cb7..748aaa1 100644 Binary files a/images/locations/warehouse.webp and b/images/locations/warehouse.webp differ diff --git a/images/locations/warehouse_interior.webp b/images/locations/warehouse_interior.webp index 1ebd281..139fb76 100644 Binary files a/images/locations/warehouse_interior.webp and b/images/locations/warehouse_interior.webp differ diff --git a/pwa/src/components/Game.css b/pwa/src/components/Game.css index e8b5567..317280e 100644 --- a/pwa/src/components/Game.css +++ b/pwa/src/components/Game.css @@ -1,16 +1,17 @@ html { - overflow-y: scroll; - /* Always show scrollbar to prevent layout shift */ + overflow: hidden; + /* Lock the page — no document-level scrollbar */ } .game-container { - min-height: 100vh; + height: 100vh; display: flex; flex-direction: column; background: var(--game-bg-app); color: var(--game-text-primary); position: relative; font-family: var(--game-font-main); + overflow: hidden; } /* Death Overlay */ @@ -317,7 +318,8 @@ html { .game-main { flex: 1; padding: 1.5rem; - overflow-y: auto; + overflow: hidden; + min-height: 0; } /* Unified Search Bar */ @@ -365,7 +367,6 @@ html { .explore-tab-desktop { display: grid; grid-template-columns: 380px 1fr 380px; - gap: 1.5rem; height: 100%; padding: 0; } @@ -374,9 +375,9 @@ html { .left-sidebar { display: flex; flex-direction: column; - gap: 1.5rem; + gap: var(--game-gap-md); overflow-y: auto; - padding-right: 0.5rem; + padding-right: var(--game-padding-md); } /* Center Content */ @@ -387,13 +388,21 @@ html { overflow-y: auto; } +/* Center Area (combat/location container) */ +.center-area { + display: flex; + flex-direction: column; + overflow: hidden; + min-height: 0; +} + /* Right Sidebar */ .right-sidebar { display: flex; flex-direction: column; - gap: 1.5rem; + gap: var(--game-gap-md); overflow-y: auto; - padding-left: 0.5rem; + padding-left: var(--game-padding-md); } /* Mobile fallback */ @@ -411,11 +420,14 @@ html { .location-info { background: var(--game-bg-panel); - padding: 1.5rem; - margin-bottom: 1.5rem; + padding: var(--game-padding-md); + gap: var(--game-gap-md); + margin-bottom: 0; border: 1px solid var(--game-border-color); box-shadow: var(--game-shadow-card); clip-path: var(--game-clip-path); + display: flex; + flex-direction: column; } .location-info h2 { @@ -425,7 +437,7 @@ html { display: flex; align-items: center; justify-content: center; - gap: 0.75rem; + gap: var(--game-gap-md); flex-wrap: wrap; text-transform: uppercase; letter-spacing: 1px; @@ -683,18 +695,19 @@ html { .location-image-container { width: 100%; - max-width: 800px; - margin: 1rem auto; - aspect-ratio: 10 / 7; + max-width: var(--location-content-width); + margin: 0 auto; + aspect-ratio: 16 / 9; overflow: hidden; border: 2px solid rgba(255, 107, 107, 0.3); clip-path: var(--game-clip-path); + flex-shrink: 0; } .location-image { width: 100%; height: 100%; - object-fit: contain; + object-fit: cover; } .message-box { @@ -709,8 +722,8 @@ html { .location-messages-log { background: var(--game-bg-panel); border: 1px solid var(--game-border-color); - padding: 0.8rem; - margin-top: 1rem; + padding: 0.5rem 0.8rem; + margin-top: 0; max-width: 100%; clip-path: var(--game-clip-path); } @@ -761,7 +774,7 @@ html { .movement-controls { background: var(--game-bg-panel); - padding: 1.5rem; + padding: var(--game-padding-md); border: 1px solid var(--game-border-color); box-shadow: var(--game-shadow-card); clip-path: var(--game-clip-path); @@ -781,7 +794,7 @@ html { grid-template-columns: repeat(3, 80px); gap: 0.6rem; max-width: 260px; - margin: 0 auto 1rem auto; + margin: 0 auto 0 auto; justify-content: center; } @@ -1035,14 +1048,18 @@ html { /* Interactables Section */ .interactables-section { background: var(--game-bg-panel); - padding: 1.5rem; + padding: var(--game-padding-md); border: 1px solid var(--game-border-color); clip-path: var(--game-clip-path); + display: flex; + flex-direction: column; + gap: var(--game-gap-md); } .interactables-section h3 { - margin: 0 0 1rem 0; + margin: 0 0 0.3rem 0; color: #ff6b6b; + font-size: 0.9rem; } body.no-scroll { @@ -1052,7 +1069,6 @@ body.no-scroll { /* Interactable Card with Image */ .interactable-card { background: rgba(255, 255, 255, 0.05); - margin-bottom: 1rem; border: 1px solid rgba(255, 193, 7, 0.4); overflow: hidden; display: flex; @@ -1069,7 +1085,7 @@ body.no-scroll { .interactable-image-container { width: 100%; - aspect-ratio: 10 / 7; + aspect-ratio: 16 / 9; overflow: hidden; background: rgba(0, 0, 0, 0.5); display: flex; @@ -1080,7 +1096,7 @@ body.no-scroll { .interactable-image { width: 100%; height: 100%; - object-fit: contain; + object-fit: cover; transition: transform 0.3s; } @@ -1140,39 +1156,32 @@ body.no-scroll { /* Ground Entities - NPCs and Items */ .ground-entities { - margin-top: 1.5rem; display: flex; flex-direction: column; - gap: 1.5rem; + gap: var(--game-gap-md); + flex: 1; + overflow: hidden; + min-height: 0; } .entity-section { background: var(--game-bg-panel); - padding: 1.5rem; + padding: 0.5rem 0.75rem; border: 1px solid var(--game-border-color); clip-path: var(--game-clip-path); } .entity-section h3 { - margin: 0 0 1rem 0; + margin: 0 0 0.3rem 0; color: #ff6b6b; - font-size: 1.2rem; + font-size: 0.9rem; } -.entity-list { +.entity-list:not(.grid-view) { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 1rem; - max-height: 400px; - overflow-y: auto; - padding-right: 5px; - /* Custom scrollbar */ - scrollbar-width: thin; - scrollbar-color: var(--game-border-active) rgba(0, 0, 0, 0.3); - /* Ensure overflow doesn't clip dropdowns if possible, - but for scrolling lists we need overflow-y: auto. - Dropdowns must use fixed position or Portal if they need to escape. - However, we can try to make sure cards have z-index context */ + overflow: hidden; } /* Ensure cards handle their own stacking context */ @@ -1735,20 +1744,20 @@ body.no-scroll { /* Profile Sidebar */ .profile-sidebar { background: var(--game-bg-panel); - padding: 1rem; + padding: 0.5rem 0.75rem; border: 1px solid var(--game-border-color); clip-path: var(--game-clip-path); } .profile-sidebar h3 { - margin: 0 0 1rem 0; + margin: 0 0 0.3rem 0; color: #ff6b6b; - font-size: 1.1rem; + font-size: 1rem; text-align: center; } .sidebar-stat-bars { - margin-bottom: 1rem; + margin-bottom: 0.3rem; } .sidebar-stat-bar { @@ -1960,17 +1969,16 @@ body.no-scroll { /* Equipment Sidebar */ .equipment-sidebar { background: var(--game-bg-panel); - padding: 1rem; + padding: 0.5rem 0.75rem; border: 1px solid var(--game-border-color); overflow: visible; clip-path: var(--game-clip-path); - /* Allow tooltips to overflow */ } .equipment-sidebar h3 { - margin: 0 0 1rem 0; + margin: 0 0 0.3rem 0; color: #ff6b6b; - font-size: 1.1rem; + font-size: 1rem; text-align: center; } @@ -3516,14 +3524,13 @@ body.no-scroll { /* Combat Log Styles */ .combat-log-container { - margin-top: 2rem; background: rgba(0, 0, 0, 0.5); - border: 2px solid rgba(244, 67, 54, 0.3); - padding: 1rem; - max-width: 800px; + border: 1px solid rgba(244, 67, 54, 0.3); + padding: var(--game-padding-md); + width: var(--location-content-width); margin-left: auto; margin-right: auto; - clip-path: var(--game-clip-path-sm); + clip-path: var(--game-clip-path); } .combat-log-container h4 { @@ -3657,10 +3664,9 @@ body.no-scroll { .location-description-box { background: rgba(25, 26, 31, 0.6); border: 1px solid rgba(107, 185, 240, 0.3); - padding: 1rem; - margin-top: 1rem; + padding: var(--game-padding-md); width: 100%; - max-width: 800px; + max-width: var(--location-content-width); margin-left: auto; margin-right: auto; box-sizing: border-box; @@ -4320,7 +4326,7 @@ body.no-scroll { .combat-actions { display: flex; flex-direction: column; - gap: 1rem; + gap: var(--game-gap-md); /* min-height removed to fit content */ justify-content: center; /* Center content vertically */ diff --git a/pwa/src/components/GameHeader.tsx b/pwa/src/components/GameHeader.tsx index f9ff37a..792e5f5 100644 --- a/pwa/src/components/GameHeader.tsx +++ b/pwa/src/components/GameHeader.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from 'react' +import { useState, useEffect, useCallback } from 'react' import { useNavigate, useLocation } from 'react-router-dom' import { useAuth } from '../hooks/useAuth' import { useGameWebSocket } from '../hooks/useGameWebSocket' @@ -34,6 +34,28 @@ export default function GameHeader({ const { currentCharacter, logout } = useAuth() const { t } = useTranslation() const [playerCount, setPlayerCount] = useState(0) + const [isFullscreen, setIsFullscreen] = useState(!!document.fullscreenElement) + + // Fullscreen toggle + const toggleFullscreen = useCallback(() => { + if (!document.fullscreenElement) { + document.documentElement.requestFullscreen().catch((err) => { + console.error('Failed to enter fullscreen:', err) + }) + } else { + document.exitFullscreen().catch((err) => { + console.error('Failed to exit fullscreen:', err) + }) + } + }, []) + + useEffect(() => { + const handleFullscreenChange = () => { + setIsFullscreen(!!document.fullscreenElement) + } + document.addEventListener('fullscreenchange', handleFullscreenChange) + return () => document.removeEventListener('fullscreenchange', handleFullscreenChange) + }, []) // Get game state from context (undefined when outside GameProvider) const gameContext = useOptionalGame() @@ -149,6 +171,16 @@ export default function GameHeader({ + + + + @@ -225,8 +219,9 @@ function MovementControls({ )}> @@ -239,7 +234,6 @@ function MovementControls({ {/* Surroundings - outside movement controls */} {location.interactables && location.interactables.length > 0 && (
-

{t('game.surroundings')}

{location.interactables.map((interactable: any) => (
{interactable.image_path && ( diff --git a/pwa/src/components/game/PlayerSidebar.tsx b/pwa/src/components/game/PlayerSidebar.tsx index 7dcdf31..41c865d 100644 --- a/pwa/src/components/game/PlayerSidebar.tsx +++ b/pwa/src/components/game/PlayerSidebar.tsx @@ -229,7 +229,7 @@ function PlayerSidebar({
)} -
+ {/* Compact 2x2 Stats Grid */}
@@ -263,52 +263,53 @@ function PlayerSidebar({
-
- {/* Inventory Capacity - matching HP/Stamina/XP style */} -
- -
-
- + {/* Inventory Capacity - Weight & Volume side-by-side */} +
+
+ +
+
+ +
)} -
+
setShowInventory(true)} - style={{ width: '100%', justifyContent: 'center' }} + style={{ flex: 1, justifyContent: 'center' }} > {t('game.inventory')} 📜 {t('common.quests')} diff --git a/pwa/src/index.css b/pwa/src/index.css index 339b72b..213a05f 100644 --- a/pwa/src/index.css +++ b/pwa/src/index.css @@ -26,6 +26,19 @@ --game-clip-path-sm: polygon(4px 0, 100% 0, 100% calc(100% - 4px), calc(100% - 4px) 100%, 0 100%, 0 4px); --game-clip-path-no-br: polygon(10px 0, 100% 0, 100% 100%, 0 100%, 0 10px); + /* --- Gaps and paddings --- */ + --game-padding-xs: 0.25rem; + --game-padding-sm: 0.5rem; + --game-padding-md: 0.75rem; + --game-padding-lg: 1rem; + --game-padding-xl: 1.5rem; + + --game-gap-xs: 0.25rem; + --game-gap-sm: 0.5rem; + --game-gap-md: 0.75rem; + --game-gap-lg: 1rem; + --game-gap-xl: 1.5rem; + /* --- Typography --- */ --game-font-main: 'Saira Condensed', system-ui, sans-serif; --game-text-primary: #e0e0e0; @@ -68,6 +81,24 @@ text-rendering: optimizeLegibility; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; + + /* Responsive Location Widths */ + --location-content-width: 600px; + /* Default (1080p and below) */ +} + +@media (min-width: 2200px) { + :root { + --location-content-width: 1000px; + /* 1440p */ + } +} + +@media (min-width: 3400px) { + :root { + --location-content-width: 1400px; + /* 4K */ + } } /* --- Reusable Game Classes --- */