diff --git a/app/src/main/java/com/darkaxt/dualdex/settings/SettingsRepository.kt b/app/src/main/java/com/darkaxt/dualdex/settings/SettingsRepository.kt index 63097a8f..ec24cdcb 100644 --- a/app/src/main/java/com/darkaxt/dualdex/settings/SettingsRepository.kt +++ b/app/src/main/java/com/darkaxt/dualdex/settings/SettingsRepository.kt @@ -75,6 +75,7 @@ class SettingsRepository( overlayScale = sanitized.overlayScale, battlePollingIntervalMs = sanitized.battlePollingIntervalMs, mapFollowSmoothingPercent = sanitized.mapFollowSmoothingPercent, + highVisibilityMapPlayer = sanitized.highVisibilityMapPlayer, ) val override = StoredSettings.difference(sanitized, globals) val overrides = LinkedHashMap(state.romOverrides) @@ -194,6 +195,7 @@ class SettingsRepository( }, battlePollingIntervalMs = if (includeDeviceFields) objectValue.intValue("battlePollingIntervalMs")?.coerceIn(1, 20) else null, mapFollowSmoothingPercent = if (includeDeviceFields) objectValue.intValue("mapFollowSmoothingPercent")?.coerceIn(0, 100) else null, + highVisibilityMapPlayer = if (includeDeviceFields) objectValue.booleanValue("highVisibilityMapPlayer") else null, ) private fun sanitize(settings: CompanionSettings): CompanionSettings { @@ -268,6 +270,7 @@ class SettingsRepository( val overlayScale: Double? = null, val battlePollingIntervalMs: Int? = null, val mapFollowSmoothingPercent: Int? = null, + val highVisibilityMapPlayer: Boolean? = null, ) { fun applyTo(fallback: CompanionSettings): CompanionSettings = CompanionSettings( knowledgeMode = knowledgeMode?.let(KnowledgeMode::valueOf) ?: fallback.knowledgeMode, @@ -285,6 +288,7 @@ class SettingsRepository( overlayScale = overlayScale ?: fallback.overlayScale, battlePollingIntervalMs = battlePollingIntervalMs ?: fallback.battlePollingIntervalMs, mapFollowSmoothingPercent = mapFollowSmoothingPercent ?: fallback.mapFollowSmoothingPercent, + highVisibilityMapPlayer = highVisibilityMapPlayer ?: fallback.highVisibilityMapPlayer, ) fun isEmpty(): Boolean = this == StoredSettings() @@ -306,6 +310,7 @@ class SettingsRepository( overlayScale = settings.overlayScale, battlePollingIntervalMs = settings.battlePollingIntervalMs, mapFollowSmoothingPercent = settings.mapFollowSmoothingPercent, + highVisibilityMapPlayer = settings.highVisibilityMapPlayer, ) fun difference(settings: CompanionSettings, globals: CompanionSettings) = StoredSettings( diff --git a/app/src/main/java/com/darkaxt/dualdex/web/ProductionCompanionRuntime.kt b/app/src/main/java/com/darkaxt/dualdex/web/ProductionCompanionRuntime.kt index f9b39b56..096fbe7e 100644 --- a/app/src/main/java/com/darkaxt/dualdex/web/ProductionCompanionRuntime.kt +++ b/app/src/main/java/com/darkaxt/dualdex/web/ProductionCompanionRuntime.kt @@ -1531,6 +1531,8 @@ class ProductionCompanionRuntime( ?: current.battlePollingIntervalMs, mapFollowSmoothingPercent = values["mapFollowSmoothingPercent"]?.toIntOrNull()?.coerceIn(0, 100) ?: current.mapFollowSmoothingPercent, + highVisibilityMapPlayer = values["highVisibilityMapPlayer"]?.toBooleanStrictOrNull() + ?: current.highVisibilityMapPlayer, ) gateway.dispatch( CompanionAction.UpdateSettings(updated), diff --git a/app/src/test/java/com/darkaxt/dualdex/settings/SettingsRepositoryTest.kt b/app/src/test/java/com/darkaxt/dualdex/settings/SettingsRepositoryTest.kt index 6612b230..944a2f00 100644 --- a/app/src/test/java/com/darkaxt/dualdex/settings/SettingsRepositoryTest.kt +++ b/app/src/test/java/com/darkaxt/dualdex/settings/SettingsRepositoryTest.kt @@ -36,6 +36,7 @@ class SettingsRepositoryTest { overlayScale = 0.65, battlePollingIntervalMs = 1, mapFollowSmoothingPercent = 70, + highVisibilityMapPlayer = true, ) repository.write(settings) @@ -109,6 +110,28 @@ class SettingsRepositoryTest { assertFalse(requireNotNull(document).substringAfter("\"romOverrides\"").contains("mapFollowSmoothingPercent")) } + @Test + fun keepsTheHighVisibilityMapPlayerPreferenceDeviceGlobalAcrossRomProfiles() { + var document: String? = null + val repository = SettingsRepository({ document }, { document = it }) + repository.writeForRom( + romA, + CompanionSettings(theme = Theme.DARK, highVisibilityMapPlayer = true), + ) + repository.writeForRom( + romB, + repository.readForRom(romB).copy(theme = Theme.LIGHT), + ) + + assertTrue(repository.readForRom(romA).highVisibilityMapPlayer) + assertTrue(repository.readForRom(romB).highVisibilityMapPlayer) + assertFalse( + requireNotNull(document) + .substringAfter("\"romOverrides\"") + .contains("highVisibilityMapPlayer"), + ) + } + @Test fun storesIndependentRomOverridesWhileDeviceSettingsRemainGlobal() { var document: String? = null diff --git a/app/src/test/java/com/darkaxt/dualdex/web/ProductionCompanionRuntimeTest.kt b/app/src/test/java/com/darkaxt/dualdex/web/ProductionCompanionRuntimeTest.kt index 5631a145..55a64dde 100644 --- a/app/src/test/java/com/darkaxt/dualdex/web/ProductionCompanionRuntimeTest.kt +++ b/app/src/test/java/com/darkaxt/dualdex/web/ProductionCompanionRuntimeTest.kt @@ -1754,7 +1754,12 @@ class ProductionCompanionRuntimeTest { val state = runtime.action( "SETTINGS", - mapOf("displayTarget" to "EXTERNAL", "theme" to "LIGHT", "fontScale" to "1.2"), + mapOf( + "displayTarget" to "EXTERNAL", + "theme" to "LIGHT", + "fontScale" to "1.2", + "highVisibilityMapPlayer" to "true", + ), ) val settings = state.settings as CompanionSettings @@ -1762,6 +1767,7 @@ class ProductionCompanionRuntimeTest { assertEquals(DisplayTarget.EXTERNAL, settings.displayTarget) assertEquals(Theme.LIGHT, settings.theme) assertEquals(1.2, settings.fontScale, 0.0) + assertTrue(settings.highVisibilityMapPlayer) assertEquals(settings, persisted) runtime.close() } diff --git a/companion-core/src/main/kotlin/com/enrpau/dualscreendex/companion/model/AppModels.kt b/companion-core/src/main/kotlin/com/enrpau/dualscreendex/companion/model/AppModels.kt index 465b3050..e3bed17a 100644 --- a/companion-core/src/main/kotlin/com/enrpau/dualscreendex/companion/model/AppModels.kt +++ b/companion-core/src/main/kotlin/com/enrpau/dualscreendex/companion/model/AppModels.kt @@ -32,6 +32,7 @@ data class CompanionSettings( val overlayScale: Double = 1.0, val battlePollingIntervalMs: Int = 5, val mapFollowSmoothingPercent: Int = 25, + val highVisibilityMapPlayer: Boolean = false, ) data class OwnedPokemon( diff --git a/companion-web/e2e/ui-space-regressions.spec.ts b/companion-web/e2e/ui-space-regressions.spec.ts index eecd5c33..d30d219f 100644 --- a/companion-web/e2e/ui-space-regressions.spec.ts +++ b/companion-web/e2e/ui-space-regressions.spec.ts @@ -197,11 +197,11 @@ test('Pokédex density uses exact shared row geometry at the Thor viewport', asy await page.getByRole('button', { name: 'Back' }).click(); await expect(page.locator('.pokedex-screen')).toBeVisible(); - expect((await firstRow.boundingBox())?.height).toBe(68); + expect((await firstRow.boundingBox())?.height).toBe(76); expect(await page.locator('.species-list').evaluate(element => ({ clientHeight: element.clientHeight, scrollHeight: element.scrollHeight, - }))).toMatchObject({ scrollHeight: 80 * 68 }); + }))).toMatchObject({ scrollHeight: 80 * 76 }); await page.getByRole('button', { name: 'Settings' }).click(); await page.getByRole('button', { name: 'Accessibility' }).click(); @@ -213,14 +213,14 @@ test('Pokédex density uses exact shared row geometry at the Thor viewport', asy const enlargedRows = page.locator('.species-row'); const firstEnlargedRow = await enlargedRows.nth(0).boundingBox(); const secondEnlargedRow = await enlargedRows.nth(1).boundingBox(); - expect(firstEnlargedRow?.height).toBe(92); + expect(firstEnlargedRow?.height).toBe(103); expect(firstEnlargedRow!.y + firstEnlargedRow!.height).toBeLessThanOrEqual(secondEnlargedRow!.y); const enlargedContentExtent = await enlargedRows.nth(0).evaluate(element => ({ clientHeight: element.clientHeight, scrollHeight: element.scrollHeight, })); expect(enlargedContentExtent.scrollHeight).toBe(enlargedContentExtent.clientHeight); - expect(await page.locator('.species-list').evaluate(element => element.scrollHeight)).toBe(80 * 92); + expect(await page.locator('.species-list').evaluate(element => element.scrollHeight)).toBe(80 * 103); }); test('shared tabs and route headings keep one keyboard focus path at the Thor viewport', async ({ page }) => { diff --git a/companion-web/src/App.tsx b/companion-web/src/App.tsx index cb97a725..bc9014c0 100644 --- a/companion-web/src/App.tsx +++ b/companion-web/src/App.tsx @@ -37,7 +37,7 @@ const emptyState: State = { filter: 'ALL', selectedAreaId: null, battleTab: 'ENTRY', - settings: { knowledgeMode: 'ORGANIC', attackEnabled: true, rarityEnabled: true, movesEnabled: true, fontScale: 1, density: 'AUTO', highContrast: false, autoOpenTarget: true, ruleset: 'AUTO', theme: 'GAME', displayTarget: 'AUTO', mapFollowSmoothingPercent: 25 }, + settings: { knowledgeMode: 'ORGANIC', attackEnabled: true, rarityEnabled: true, movesEnabled: true, fontScale: 1, density: 'AUTO', highContrast: false, autoOpenTarget: true, ruleset: 'AUTO', theme: 'GAME', displayTarget: 'AUTO', mapFollowSmoothingPercent: 25, highVisibilityMapPlayer: false }, speciesState: {}, observedMoves: {}, battle: null, catalogReady: false, catalogName: null, error: null, trainer: null, party: [], activeRulesetId: null, rulesetAssumed: true, loading: { active: false, phase: 'IDLE', completedUnits: 0, totalUnits: 0 }, diff --git a/companion-web/src/layoutStyles.test.ts b/companion-web/src/layoutStyles.test.ts index 55f8b935..49d49d09 100644 --- a/companion-web/src/layoutStyles.test.ts +++ b/companion-web/src/layoutStyles.test.ts @@ -31,6 +31,30 @@ describe('screen layout containment', () => { expect(styles).toMatch(/\.header-destination-action\.party-action \.party-ball-button-ring\s*\{[^}]*fill\s*:\s*var\(--semantic-selected-bg\)/) }) + it('gives compact Pokédex cards enough height for identity and type labels', () => { + const compactRowRule = styles.match(/\[data-density="compact"\] \.species-row\s*\{([^}]*)\}/)?.[1] + + expect(compactRowRule).toMatch(/min-height\s*:\s*76px/) + }) + + it('keeps the compact Party experience bar thick and visibly blue against gray', () => { + const trackRule = styles.match(/\.party-exp-track\s*\{([^}]*)\}/)?.[1] + const fillRule = styles.match(/\.party-exp-fill\s*\{([^}]*)\}/)?.[1] + const compactRule = styles.match(/@media \(max-width: 650px\)[\s\S]*?\.party-exp-track\s*\{([^}]*)\}/)?.[1] + + expect(trackRule).toMatch(/border\s*:\s*1px solid #36495d/) + expect(trackRule).toMatch(/background\s*:\s*#d8dde2/) + expect(fillRule).toMatch(/linear-gradient\(90deg, #0b6fbe, #2ca7f0\)/) + expect(compactRule).toMatch(/height\s*:\s*7px/) + }) + + it('keeps the Local player pulse behind the Accessibility preference', () => { + expect(styles).toMatch(/\.map-marker\.is-current:not\(\.atlas-location-marker\):not\(\.map-player-marker\)/) + expect(styles).toMatch(/\.map-player-marker\.is-high-visibility\s*\{[^}]*animation\s*:\s*current-map-point/) + expect(styles).toMatch(/\.map-player-marker\.has-sprite img\s*\{[^}]*filter\s*:\s*none/) + expect(styles).toMatch(/\.map-player-marker\.has-sprite\.is-high-visibility img\s*\{[^}]*drop-shadow/) + }) + it('keeps Party card copy inside the full padded card height', () => { const copyRule = styles.match(/\.party-slot-copy\s*\{([^}]*)\}/)?.[1] diff --git a/companion-web/src/models.ts b/companion-web/src/models.ts index 22e60a43..45b457fa 100644 --- a/companion-web/src/models.ts +++ b/companion-web/src/models.ts @@ -273,6 +273,7 @@ export interface Settings { overlayScale?: number; battlePollingIntervalMs?: number; mapFollowSmoothingPercent?: number; + highVisibilityMapPlayer?: boolean; } export interface SpeciesState { diff --git a/companion-web/src/pages/MapPage.test.tsx b/companion-web/src/pages/MapPage.test.tsx index fb58c8c5..e66fe464 100644 --- a/companion-web/src/pages/MapPage.test.tsx +++ b/companion-web/src/pages/MapPage.test.tsx @@ -512,11 +512,27 @@ describe('optional local map presentation', () => { expect(playerMarker?.getAttribute('aria-label')).toBe('Player position 12, 7'); expect(playerMarker?.classList.contains('atlas-location-marker')).toBe(false); expect(playerMarker?.classList.contains('has-sprite')).toBe(false); + expect(playerMarker?.classList.contains('is-high-visibility')).toBe(false); expect(playerMarker?.querySelector('.map-player-dot')).not.toBeNull(); expect(screen.queryByRole('button', { name: 'Show Atlas' })).toBeNull(); expect(screen.queryByRole('button', { name: 'Show Local map' })).toBeNull(); }); + it('adds the player pulse only when the Accessibility preference is enabled', () => { + const { container } = render(); + + expect(container.querySelector('.map-player-marker')?.classList.contains('is-high-visibility')).toBe(true); + }); + it('uses the gender-selected ROM overworld sprite at its ROM-derived native map scale', () => { const bounds = { x: 0, y: 0, top: 0, right: 1240, bottom: 825, left: 0, diff --git a/companion-web/src/pages/MapPage.tsx b/companion-web/src/pages/MapPage.tsx index fe89b7b5..3fff4e96 100644 --- a/companion-web/src/pages/MapPage.tsx +++ b/companion-web/src/pages/MapPage.tsx @@ -646,7 +646,7 @@ export function MapPage({ catalog, state, onOpenPokedex, onOpenSettings, onUpdat >; })} {playerPosition && { it('shares density row geometry across virtualization and scroll rebasing', () => { expect(pokedexRowHeight('AUTO')).toBe(94); expect(pokedexRowHeight('COMFORTABLE')).toBe(94); - expect(pokedexRowHeight('COMPACT')).toBe(68); - expect(pokedexRowHeight('COMPACT', 1.35)).toBe(92); + expect(pokedexRowHeight('COMPACT')).toBe(76); + expect(pokedexRowHeight('COMPACT', 1.35)).toBe(103); expect(pokedexRowHeight('COMFORTABLE', 1.35)).toBe(127); - expect(pokedexRowHeight('COMPACT', 0.85)).toBe(68); + expect(pokedexRowHeight('COMPACT', 0.85)).toBe(76); expect(() => pokedexRowHeight('COMPACT', 0)).toThrow(/font scale/i); - for (const rowHeight of [94, 68]) { + for (const rowHeight of [94, 76]) { for (const scrollTop of [0, rowHeight * 400, Number.MAX_SAFE_INTEGER]) { const window = pokedexVirtualWindow(900, 1, scrollTop, 254, rowHeight); const mountedRows = window.endIndex - window.startIndex; @@ -446,8 +446,8 @@ describe('Pokédex knowledge modes', () => { expect(end.paddingBottom).toBe(0); } - expect(rebasePokedexScrollTop(94 * 123 + 47, 94, 68, 900 * 68 - 254)).toBe(68 * 123 + 34); - expect(rebasePokedexScrollTop(94 * 899, 94, 68, 900 * 68 - 254)).toBe(900 * 68 - 254); + expect(rebasePokedexScrollTop(94 * 123 + 47, 94, 76, 900 * 76 - 254)).toBe(76 * 123 + 38); + expect(rebasePokedexScrollTop(94 * 899, 94, 76, 900 * 76 - 254)).toBe(900 * 76 - 254); }); it('keeps a nine-hundred-entry catalog within sixty mounted rows while preserving counts and search', () => { diff --git a/companion-web/src/pages/PokedexBrowse.tsx b/companion-web/src/pages/PokedexBrowse.tsx index 1391121b..3542ab73 100644 --- a/companion-web/src/pages/PokedexBrowse.tsx +++ b/companion-web/src/pages/PokedexBrowse.tsx @@ -4,7 +4,7 @@ import type { Catalog, EncounterWindow, State } from '../models'; import { Header, maskIdentityName, PokedexAvatar, speciesIdentityKnowledge, StatusMarks, TypeChip, uniqueTypeIds } from '../components'; const DEFAULT_SPECIES_ROW_HEIGHT = 94; -const COMPACT_SPECIES_ROW_HEIGHT = 68; +const COMPACT_SPECIES_ROW_HEIGHT = 76; const MAX_MOUNTED_SPECIES = 60; const OVERSCAN_ROWS = 3; diff --git a/companion-web/src/pages/PokedexDetailNavigation.test.tsx b/companion-web/src/pages/PokedexDetailNavigation.test.tsx index f04bbc0c..cc4d9e11 100644 --- a/companion-web/src/pages/PokedexDetailNavigation.test.tsx +++ b/companion-web/src/pages/PokedexDetailNavigation.test.tsx @@ -241,6 +241,45 @@ describe('Pokédex evolution navigation', () => { expect(openAtlas).toHaveBeenCalledOnce(); }); + it('does not offer Atlas when the species has no catalogued habitat', () => { + render( ({ ...area, speciesIds: [] })), + }} + state={state} + send={vi.fn()} + tab="AREA" + setTab={vi.fn()} + openMove={vi.fn()} + openAbility={vi.fn()} + openAtlas={vi.fn()} + />); + + expect(screen.getByText('NO HABITAT MAP')).toBeTruthy(); + expect(screen.queryByRole('button', { name: 'OPEN ATLAS' })).toBeNull(); + }); + + it('does not offer Atlas before Organic mode has observed a known habitat', () => { + render(); + + expect(screen.getByText('NO KNOWN LOCATIONS')).toBeTruthy(); + expect(screen.queryByRole('button', { name: 'OPEN ATLAS' })).toBeNull(); + }); + it('keeps AREA safe when the catalog has no normalized map', () => { render( area.baseAreaId ?? Math.floor(area.id / 10))), [catalog.areas, speciesId]); const organic = state.settings.knowledgeMode === 'ORGANIC'; const visibleBaseIds = useMemo( - () => organic ? new Set(state.observedAreaBaseIdsBySpecies?.[speciesId] ?? []) : habitatBaseIds, + () => organic + ? new Set((state.observedAreaBaseIdsBySpecies?.[speciesId] ?? []) + .filter(baseAreaId => habitatBaseIds.has(baseAreaId))) + : habitatBaseIds, [habitatBaseIds, organic, speciesId, state.observedAreaBaseIdsBySpecies], ); const presentedBaseIds = organic ? visibleBaseIds : habitatBaseIds; @@ -25,13 +28,14 @@ export function PokemonAreaMap({ catalog, state, speciesId, send, onOpenAtlas }: ) ?? [], [region?.key, visibleBaseIds]); const [selectedKey, setSelectedKey] = useState(() => visibleLocations[0]?.key ?? ''); const selected = visibleLocations.find(location => location.key === selectedKey) ?? visibleLocations[0]; + const atlasAvailable = onOpenAtlas != null && visibleBaseIds.size > 0; const fogRef = useRef(null); useEffect(() => { if (region && fogRef.current) paintFog(fogRef.current, region, visibleLocations); }, [region?.key, visibleLocations]); - if (!region) return
{organic && habitatBaseIds.size > 0 ? 'NO KNOWN LOCATIONS' : 'NO HABITAT MAP'}

{organic && habitatBaseIds.size > 0 ? 'Discover this Pokémon in the wild to reveal its habitat.' : 'No habitat map is available for this game.'}

{onOpenAtlas && }
; + if (!region) return
{organic && habitatBaseIds.size > 0 ? 'NO KNOWN LOCATIONS' : 'NO HABITAT MAP'}

{organic && habitatBaseIds.size > 0 ? 'Discover this Pokémon in the wild to reveal its habitat.' : 'No habitat map is available for this game.'}

{atlasAvailable && }
; return
@@ -61,7 +65,7 @@ export function PokemonAreaMap({ catalog, state, speciesId, send, onOpenAtlas }: })} {selected && } - {visibleLocations.length === 0 &&

No organically observed habitat yet. Undiscovered locations stay masked.

{onOpenAtlas && }
} + {visibleLocations.length === 0 &&

No organically observed habitat yet. Undiscovered locations stay masked.

{atlasAvailable && }
}
; } diff --git a/companion-web/src/pages/SettingsPage.production.test.tsx b/companion-web/src/pages/SettingsPage.production.test.tsx index 460a1cfa..24d45620 100644 --- a/companion-web/src/pages/SettingsPage.production.test.tsx +++ b/companion-web/src/pages/SettingsPage.production.test.tsx @@ -123,6 +123,18 @@ describe('production settings copy', () => { expect(send).toHaveBeenCalledWith('SETTINGS', { mapFollowSmoothingPercent: 70 }); }); + it('keeps the enhanced Local player marker off by default and exposes it under Accessibility', () => { + const send = vi.fn(); + render(); + + const toggle = screen.getByRole('checkbox', { name: 'High-visibility map player' }); + expect(toggle).toHaveProperty('checked', false); + expect(screen.getByText(/adds a pulsing outline around the Local map player/i)).toBeTruthy(); + + fireEvent.click(toggle); + expect(send).toHaveBeenCalledWith('SETTINGS', { highVisibilityMapPlayer: true }); + }); + it('does not expose the retired Thor focus setting or status', () => { render(); diff --git a/companion-web/src/pages/SettingsPage.tsx b/companion-web/src/pages/SettingsPage.tsx index 9e3a02b8..1664b5b8 100644 --- a/companion-web/src/pages/SettingsPage.tsx +++ b/companion-web/src/pages/SettingsPage.tsx @@ -7,7 +7,7 @@ export const SETTINGS_CATEGORIES = [ { id: 'CONNECTION', label: 'Connection', description: 'RetroArch and save data' }, { id: 'DISPLAY', label: 'Display', description: 'Theme, mode, and companion screen' }, { id: 'INFORMATION', label: 'Information', description: 'Guide, map, move, and battle data' }, - { id: 'ACCESSIBILITY', label: 'Accessibility', description: 'Text, density, and contrast' }, + { id: 'ACCESSIBILITY', label: 'Accessibility', description: 'Text, contrast, and map visibility' }, { id: 'BEHAVIOR', label: 'Behavior', description: 'Automatic companion actions' }, { id: 'ADVANCED', label: 'Advanced', description: 'Diagnostics and maintenance' }, ] as const; @@ -151,7 +151,10 @@ export function SettingsPage({

BATTLE TABS

update({ attackEnabled })} /> update({ rarityEnabled })} /> update({ movesEnabled })} />
} - {category === 'ACCESSIBILITY' &&

READABILITY

update({ density })} label="Density" /> update({ highContrast })} />
} + {category === 'ACCESSIBILITY' && <> +

READABILITY

update({ density })} label="Density" /> update({ highContrast })} />
+

LOCAL MAP

update({ highVisibilityMapPlayer })} />

Adds a pulsing outline around the Local map player. This choice applies to every game.

+ } {category === 'BEHAVIOR' &&

BEHAVIOR

update({ autoOpenTarget })} />
} diff --git a/companion-web/src/styles.css b/companion-web/src/styles.css index acd6a5f8..0f106e1e 100644 --- a/companion-web/src/styles.css +++ b/companion-web/src/styles.css @@ -217,8 +217,8 @@ button { cursor: pointer; } .party-hp-line > b { color: var(--muted); font-size: max(var(--ui-min-text), clamp(9px, 1.84vw, 18.8px)); letter-spacing: .07em; } .party-hp-value { display: flex; justify-content: flex-end; align-items: center; gap: 6px; margin-top: 3px; color: var(--ink); font-size: max(var(--ui-min-text), clamp(9px, 1.76vw, 18px)); font-weight: 900; } .party-hp-value > i { font-style: normal; } -.party-exp-track { height: 8px; display: block; margin-top: 5px; overflow: hidden; border: 1px solid #536379; background: #bcc5cf; box-shadow: inset 0 1px 1px #1f304755; } -.party-exp-fill { height: 100%; display: block; background: linear-gradient(90deg, #4d86bc, #75b8e8); } +.party-exp-track { height: 8px; display: block; margin-top: 5px; overflow: hidden; border: 1px solid #36495d; background: #d8dde2; box-shadow: inset 0 1px 1px #1f304755; } +.party-exp-fill { height: 100%; display: block; background: linear-gradient(90deg, #0b6fbe, #2ca7f0); } .party-hp-track { height: 16px; display: block; margin-top: 4px; overflow: hidden; border: 1px solid #556154; background: #c3c8b2; box-shadow: inset 0 1px 2px #24312655; } .party-hp-fill { height: 100%; display: block; background: linear-gradient(90deg, #4b9a5f, #79c26b); } .party-status-dot { padding: 1px 4px; border-radius: 2px; color: #fff; background: #875637; font-style: normal; } @@ -348,7 +348,7 @@ button { cursor: pointer; } .party-sprite { width: 52px; height: 52px; } .party-slot-heading { gap: 4px; } .party-slot-bars { margin-top: 5px; } - .party-exp-track { height: 3px; margin-top: 4px; } + .party-exp-track { height: 7px; margin-top: 4px; } .party-hp-track { height: 6px; margin-top: 4px; } .trainer-card-content { padding: 8px; } .trainer-card-body { grid-template-columns: minmax(0, 1fr) 112px; gap: 9px; padding: 10px; } @@ -947,7 +947,7 @@ button { cursor: pointer; } [data-theme="dark"] .section-heading > strong { color: #f1f5e3; } [data-theme="dark"] .mapper-export .danger-button { color: #fff4e8; } [data-theme="light"] { --paper: #fbf8e8; --paper-deep: #e9ecd7; } -[data-density="compact"] .species-row { min-height: 68px; grid-template-columns: 54px minmax(0, 1fr) auto; padding: 6px 10px; gap: 4px 8px; } [data-density="compact"] .pokedex-avatar { width: 54px; height: 54px; } [data-density="compact"] .sprite-frame:not(.sprite-large) { width: 40px; height: 40px; } +[data-density="compact"] .species-row { min-height: 76px; grid-template-columns: 54px minmax(0, 1fr) auto; padding: 6px 10px; gap: 4px 8px; } [data-density="compact"] .pokedex-avatar { width: 54px; height: 54px; } [data-density="compact"] .sprite-frame:not(.sprite-large) { width: 40px; height: 40px; } .map-screen { grid-template-rows: auto 1fr; color: #e8ecd5; background: #000; } .map-page-header { position: relative; min-height: 62px; z-index: 5; display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 12px; padding-left: 16px; color: #edf1d7; background: linear-gradient(90deg, #0a2a20, #143e2d 68%, #0c291f); border-bottom: 3px solid #90ae53; box-shadow: 0 5px 20px #0008; } @@ -969,7 +969,7 @@ button { cursor: pointer; } .map-mode-control { position: absolute; z-index: 6; left: 12px; bottom: 12px; width: 34px; height: 34px; font: 950 .72em/1 var(--font-sans); letter-spacing: .05em; } .map-zoom-rail .map-control { font: 900 1.45em/1 Georgia, serif; }.recenter-control span { width: 18px; height: 18px; border: 2px solid currentColor; border-radius: 50%; }.recenter-control span::before, .recenter-control span::after { content: ""; position: absolute; background: currentColor; }.recenter-control span::before { left: 21px; top: 10px; width: 2px; height: 26px; }.recenter-control span::after { left: 10px; top: 21px; width: 26px; height: 2px; } .map-legend-panel { position: absolute; left: 58px; top: 0; width: min(240px, calc(100vw - 140px)); display: grid; gap: 7px; padding: 12px; color: #e9eed5; background: #0c211aed; border: 1px solid #728d5b; box-shadow: 7px 8px 0 #0007; backdrop-filter: blur(10px); }.map-legend-panel small { color: #9daf90; font-size: .75em; font-weight: 900; letter-spacing: .15em; }.map-legend-panel > strong { color: var(--acid); font-family: Georgia, serif; }.map-legend-panel > button, .map-region-options button { min-height: 36px; padding: 0 9px; border: 1px solid #61785d; color: #e6ecd0; background: #17382a; font-size: .78em; font-weight: 900; text-align: left; }.map-region-options { display: grid; gap: 4px; }.map-region-options button[aria-pressed="true"] { color: #102419; background: var(--acid); } -.map-marker { position: absolute; z-index: 3; padding: 0; translate: -50% -50%; box-shadow: 0 0 0 3px #0b1f18cc, 0 2px 5px #000; }.atlas-location-marker { width: 44px; height: 44px; display: grid; place-items: center; border: 0; background: transparent; box-shadow: none; }.atlas-location-marker span { width: 11px; height: 11px; display: block; border: 2px solid #d9ffff; border-radius: 2px; background: #62dfe1; box-shadow: 0 0 0 3px #0b1f18cc, 0 2px 5px #000; }.map-marker.is-current:not(.atlas-location-marker), .atlas-location-marker.is-current span { animation: current-map-point 1.5s ease-out infinite; }.map-marker.is-selected { outline: 2px solid var(--acid); outline-offset: 3px; } +.map-marker { position: absolute; z-index: 3; padding: 0; translate: -50% -50%; box-shadow: 0 0 0 3px #0b1f18cc, 0 2px 5px #000; }.atlas-location-marker { width: 44px; height: 44px; display: grid; place-items: center; border: 0; background: transparent; box-shadow: none; }.atlas-location-marker span { width: 11px; height: 11px; display: block; border: 2px solid #d9ffff; border-radius: 2px; background: #62dfe1; box-shadow: 0 0 0 3px #0b1f18cc, 0 2px 5px #000; }.map-marker.is-current:not(.atlas-location-marker):not(.map-player-marker), .atlas-location-marker.is-current span { animation: current-map-point 1.5s ease-out infinite; }.map-marker.is-selected { outline: 2px solid var(--acid); outline-offset: 3px; } .map-poi-marker { position: absolute; z-index: 4; width: 44px; height: 44px; display: grid; place-items: center; padding: 0; translate: -50% -50%; border: 0; color: var(--theme-text); background: transparent; } .map-poi-marker.is-selected { outline: 2px solid var(--acid); outline-offset: 2px; } .map-poi-marker.is-silhouette .map-poi-symbol { color: #050706; background: #d4dbc5; } @@ -1045,7 +1045,7 @@ button.area-guide-point-row:hover, button.area-guide-point-row:focus-visible, .a [data-theme="game"][data-contrast="normal"] .area-guide-encounter-row b, [data-theme="game"][data-contrast="normal"] .area-guide-point-row, [data-theme="game"][data-contrast="normal"] .area-guide-exits button { color: var(--theme-text); } -.map-player-marker { pointer-events: none; }.map-player-marker.is-fallback { width: 17px; height: 17px; border: 2px solid #102419; border-radius: 50%; background: var(--acid); }.map-player-marker.is-fallback .map-player-dot { position: absolute; inset: 4px; border-radius: 50%; background: #102419; }.map-player-marker.has-sprite { min-width: 16px; min-height: 32px; translate: -50% -75%; border: 0; background: transparent; box-shadow: none; animation: none; }.map-player-marker.has-sprite img { width: 100%; height: 100%; display: block; object-fit: contain; image-rendering: pixelated; filter: drop-shadow(0 1px 0 #071e18) drop-shadow(0 0 2px #62dfe1); } +.map-player-marker { pointer-events: none; }.map-player-marker.is-fallback { width: 17px; height: 17px; border: 2px solid #102419; border-radius: 50%; background: var(--acid); }.map-player-marker.is-fallback .map-player-dot { position: absolute; inset: 4px; border-radius: 50%; background: #102419; }.map-player-marker.has-sprite { min-width: 16px; min-height: 32px; translate: -50% -75%; border: 0; background: transparent; box-shadow: none; animation: none; }.map-player-marker.has-sprite img { width: 100%; height: 100%; display: block; object-fit: contain; image-rendering: pixelated; filter: none; }.map-player-marker.is-high-visibility { animation: current-map-point 1.5s ease-out infinite; }.map-player-marker.has-sprite.is-high-visibility img { filter: drop-shadow(0 1px 0 #071e18) drop-shadow(0 0 2px #62dfe1); } @keyframes current-map-point { 0%, 100% { box-shadow: 0 0 0 3px #0b1f18cc, 0 2px 5px #000; } 50% { box-shadow: 0 0 0 7px #d9f47a55, 0 2px 5px #000; } } @media (prefers-reduced-motion: reduce) { diff --git a/docs/images/live/dualdex-thor-qa-pokedex-browser.webp b/docs/images/live/dualdex-thor-qa-pokedex-browser.webp index b9dfdda9..4c3191f5 100644 Binary files a/docs/images/live/dualdex-thor-qa-pokedex-browser.webp and b/docs/images/live/dualdex-thor-qa-pokedex-browser.webp differ