diff --git a/IWantToHeal-Thor-v1.1.8.apk b/IWantToHeal-Thor-v1.1.8.apk new file mode 100644 index 0000000..1e8677d Binary files /dev/null and b/IWantToHeal-Thor-v1.1.8.apk differ diff --git a/android/app/build.gradle b/android/app/build.gradle index ef2fad8..7db8508 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -7,8 +7,8 @@ android { applicationId "com.warren.iwanttoheal" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 86 - versionName "1.1.7" + versionCode 87 + versionName "1.1.8" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" aaptOptions { // Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps. diff --git a/src/App.css b/src/App.css index 05c80d7..2f9bb4c 100644 --- a/src/App.css +++ b/src/App.css @@ -6977,6 +6977,95 @@ h2 { font-size: 15px; } + .auth-shell { + height: 100dvh; + min-height: 0; + overflow: hidden; + padding: 10px; + } + + .auth-panel { + gap: 12px; + max-height: calc(100dvh - 20px); + } + + .auth-brand, + .auth-card { + padding: 14px; + } + + .auth-brand { + min-height: 0; + } + + .auth-brand h1 { + font-size: 28px; + line-height: 1.15; + margin: 6px 0 10px; + } + + .auth-brand > p:last-child { + font-size: 17px; + line-height: 1.15; + } + + .auth-tabs { + gap: 5px; + } + + .auth-tabs button { + min-height: 34px; + } + + .auth-card form { + gap: 8px; + margin-top: 10px; + } + + .auth-card label { + gap: 5px; + } + + .auth-card input { + font-size: 16px; + padding: 7px 9px; + } + + .auth-card .primary-button { + min-height: 34px; + padding: 8px 12px; + } + + .auth-message { + font-size: 13px; + line-height: 1.1; + margin-top: 8px; + min-height: 24px; + } + + .offline-divider { + gap: 8px; + margin: 8px 0; + } + + .offline-entry { + gap: 7px; + padding: 10px; + } + + .offline-entry p:not(.eyebrow) { + display: none; + } + + .offline-entry h2 { + font-size: 9px; + } + + .offline-resume-button, + .offline-new-button { + min-height: 32px; + } + .screen-heading { padding-bottom: 10px; } @@ -7340,6 +7429,127 @@ h2 { .ability-library { max-height: 190px; } + + .enemy-card { + gap: 10px; + margin-top: 8px; + padding: 8px; + } + + .enemy-portrait { + flex-basis: 48px; + font-size: 17px; + height: 48px; + } + + .enemy-info p { + font-size: 14px; + line-height: 1; + margin-top: 4px; + max-height: 28px; + overflow: hidden; + } + + .bar-label { + font-size: 15px; + } + + .combat-layout { + gap: 8px; + grid-template-columns: minmax(0, 1.6fr) minmax(248px, 0.85fr); + margin-top: 8px; + min-height: 0; + } + + .party-panel, + .combat-log { + min-height: 0; + padding: 8px; + } + + .party-panel-top { + margin-bottom: 6px; + } + + .party-grid { + gap: 7px; + margin-top: 8px; + } + + .party-member { + min-height: 72px; + padding: 7px; + } + + .member-header { + gap: 5px; + } + + .member-header small { + font-size: 12px; + } + + .member-health { + margin-top: 5px; + } + + .member-effects { + gap: 3px; + margin-top: 4px; + min-height: 14px; + } + + .member-effects span { + font-size: 11px; + padding: 0 4px; + } + + .combat-side-rail .panel-heading .eyebrow { + display: none; + } + + .combat-side-rail .panel-heading h2 { + font-size: 10px; + } + + .vertical-spell-bar, + .vertical-spell-bar.six-slots { + gap: 6px; + grid-template-columns: repeat(2, minmax(0, 1fr)); + margin-top: 8px; + overflow: hidden; + padding-right: 0; + } + + .vertical-spell-bar .spell { + min-height: 54px; + padding: 5px 5px 5px 36px; + } + + .vertical-spell-bar .spell-icon { + font-size: 10px; + height: 22px; + left: 6px; + width: 22px; + } + + .vertical-spell-bar .spell strong { + font-size: 11px; + line-height: 1.1; + } + + .vertical-spell-bar .spell small { + font-size: 10px; + line-height: 1; + } + + .vertical-spell-bar .spell kbd { + font-size: 8px; + min-height: 16px; + padding: 1px 4px; + right: 3px; + top: 3px; + } } @media (max-width: 800px) { diff --git a/src/dualScreen.tsx b/src/dualScreen.tsx index 01d058a..7332bdc 100644 --- a/src/dualScreen.tsx +++ b/src/dualScreen.tsx @@ -150,6 +150,16 @@ function formatDualTime(seconds: number) { return `${Math.floor(total / 60)}:${String(total % 60).padStart(2, '0')}` } +function shouldRelayBottomAction( + action: InputAction, + state: DualScreenCombatState | null, +) { + if (!state) return false + if (action === 'confirm' || action === 'toggleTouchLock') return false + if (state.status === 'playing') return true + return action === 'pause' || action === 'back' +} + export function DualScreenProvider({ children }: { children: ReactNode }) { const [enabled, setEnabledState] = useState( () => localStorage.getItem(STORAGE_KEY) === 'true', @@ -404,8 +414,7 @@ export function DualScreenBottomDisplay() { } useGameAction((action) => { - if (!state || state.opponentParty) return - if (action === 'confirm' || action === 'back' || action === 'toggleTouchLock') return + if (!shouldRelayBottomAction(action, state)) return sendAction(action) }) @@ -458,7 +467,7 @@ export function DualScreenBottomDisplay() { return (
diff --git a/src/input.tsx b/src/input.tsx index 6ff84e8..bd29fb9 100644 --- a/src/input.tsx +++ b/src/input.tsx @@ -131,7 +131,7 @@ const PREFERENCES_STORAGE_KEY = 'ashen-halls-input-preferences-v1' const GAME_ACTION_EVENT = 'ashen-halls-game-action' const NATIVE_CONTROLLER_EVENT = 'ashen-halls-native-controller' const FOCUSABLE_SELECTOR = 'button:not(:disabled):not([data-controller-nav="skip"]), input:not(:disabled):not([data-controller-nav="skip"]), select:not(:disabled):not([data-controller-nav="skip"]), textarea:not(:disabled):not([data-controller-nav="skip"]), [tabindex]:not([tabindex="-1"]):not([data-controller-nav="skip"])' -const MAIN_CONTENT_SELECTOR = '.menu-screen, .content-screen, .dungeon-run-screen' +const MAIN_CONTENT_SELECTOR = '.auth-shell, .menu-screen, .content-screen, .dungeon-run-screen, .dual-bottom-display' const HEADER_CONTENT_SELECTOR = '.app-header' const GAMEPAD_COMBAT_POLL_MS = 1000 / 60 const GAMEPAD_MENU_POLL_MS = 1000 / 30 @@ -309,6 +309,7 @@ function rememberFocusableControl(element: HTMLElement) { function focusControl(element: HTMLElement) { rememberFocusableControl(element) element.focus({ preventScroll: true }) + element.scrollIntoView({ block: 'nearest', inline: 'nearest' }) } function currentFocusableControl(candidates = focusableElements()) {