diff --git a/IWantToHeal-Thor-v1.0.38.apk b/IWantToHeal-Thor-v1.0.38.apk new file mode 100644 index 0000000..ad86083 Binary files /dev/null and b/IWantToHeal-Thor-v1.0.38.apk differ diff --git a/android/app/build.gradle b/android/app/build.gradle index 35e27fe..8117d03 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 57 - versionName "1.0.37" + versionCode 58 + versionName "1.0.38" 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 0b4084f..4182f2b 100644 --- a/src/App.css +++ b/src/App.css @@ -1683,7 +1683,8 @@ h2 { .equipment-screen .equipment-layout, .equipment-screen .crafting-panel, -.talent-screen .talent-tree { +.talent-screen .talent-tree, +.talent-screen .spell-effect-layout { flex: 1; min-height: 0; } @@ -3474,6 +3475,8 @@ h2 { .effect-pool-panel { background: #191b25; border: 2px solid #090a0d; + display: flex; + flex-direction: column; min-height: 0; outline: 2px solid #3a3944; padding: 12px; @@ -3581,24 +3584,30 @@ h2 { } .effect-pool { + align-content: start; display: grid; + flex: 1; gap: 10px; - grid-template-columns: repeat(3, minmax(0, 1fr)); + grid-template-columns: repeat(4, minmax(0, 1fr)); + grid-template-rows: repeat(2, 62px); margin-top: 12px; + min-height: 0; + overflow: hidden; } .effect-pool > button { - align-items: center; + align-content: center; background: #20222d; border: 2px solid #090a0d; color: var(--ink); cursor: pointer; display: grid; - gap: 10px; - grid-template-columns: 34px minmax(0, 1fr) auto; - min-height: 72px; + gap: 6px; + grid-template-columns: 26px minmax(0, 1fr); + min-height: 0; outline: 2px solid #3a3944; - padding: 9px; + overflow: hidden; + padding: 7px; text-align: left; } @@ -3623,7 +3632,7 @@ h2 { color: var(--gold); display: flex; font-family: 'Press Start 2P', monospace; - height: 34px; + height: 26px; justify-content: center; } @@ -3634,17 +3643,54 @@ h2 { .effect-pool strong { font-family: 'Press Start 2P', monospace; - font-size: 8px; + font-size: 7px; line-height: 1.35; } .effect-pool small { color: var(--muted); - font-size: 14px; + font-size: 11px; line-height: 1; margin-top: 5px; } +.effect-pool > button i { + grid-column: 1 / -1; + line-height: 1; +} + +.effect-pager { + align-items: center; + display: flex; + gap: 8px; + justify-content: flex-end; + margin-top: 8px; +} + +.effect-pager button { + background: #15161c; + border: 2px solid #090a0d; + color: var(--ink); + cursor: pointer; + font-family: 'Press Start 2P', monospace; + font-size: 7px; + min-height: 28px; + outline: 2px solid #41404a; + padding: 4px 8px; + text-transform: uppercase; +} + +.effect-pager button:disabled { + color: #676773; + cursor: not-allowed; +} + +.effect-pager span { + color: var(--gold); + font-family: 'Press Start 2P', monospace; + font-size: 8px; +} + @media (max-width: 800px) { .spell-effect-layout { grid-template-columns: 1fr; @@ -3655,7 +3701,7 @@ h2 { } .effect-pool { - grid-template-columns: 1fr; + grid-template-columns: repeat(2, minmax(0, 1fr)); } .selected-effect-strip { @@ -7576,6 +7622,125 @@ h2 { margin-top: 4px; } + .workshop-shell .spell-effect-layout { + gap: 6px; + grid-template-columns: 172px minmax(0, 1fr); + margin-top: 5px; + overflow: hidden; + } + + .workshop-shell .effect-slots-panel, + .workshop-shell .effect-pool-panel { + padding: 5px; + } + + .workshop-shell .effect-slots-panel { + gap: 5px; + grid-auto-rows: minmax(43px, 1fr); + } + + .workshop-shell .effect-slot { + min-height: 0; + overflow: hidden; + padding: 5px; + } + + .workshop-shell .effect-slot span, + .workshop-shell .effect-pool > button i, + .workshop-shell .effect-panel-heading > span, + .workshop-shell .effect-pager span { + font-size: 6px; + } + + .workshop-shell .effect-slot strong { + font-size: 6px; + line-height: 1.15; + margin-top: 3px; + } + + .workshop-shell .effect-slot small { + font-size: 9px; + line-height: 1; + margin-top: 2px; + max-height: 18px; + overflow: hidden; + } + + .workshop-shell .effect-panel-heading h2 { + font-size: 9px; + line-height: 1.1; + } + + .workshop-shell .selected-effect-strip { + gap: 6px; + grid-template-columns: minmax(0, 1fr) auto; + margin-top: 5px; + padding: 5px; + } + + .workshop-shell .selected-effect-strip .eyebrow { + display: none; + } + + .workshop-shell .selected-effect-strip strong { + font-size: 7px; + line-height: 1.1; + margin-top: 0; + } + + .workshop-shell .selected-effect-strip small { + font-size: 10px; + line-height: 1; + margin-top: 3px; + max-height: 20px; + overflow: hidden; + } + + .workshop-shell .selected-effect-strip .primary-button { + font-size: 7px; + min-height: 25px; + min-width: 72px; + padding: 3px 6px; + } + + .workshop-shell .effect-pool { + gap: 5px; + grid-template-columns: repeat(4, minmax(0, 1fr)); + grid-template-rows: repeat(2, 52px); + margin-top: 5px; + } + + .workshop-shell .effect-pool > button { + gap: 4px; + grid-template-columns: 22px minmax(0, 1fr); + padding: 4px; + } + + .workshop-shell .effect-pool > button > span { + height: 22px; + } + + .workshop-shell .effect-pool strong { + font-size: 6px; + line-height: 1.15; + } + + .workshop-shell .effect-pool small { + font-size: 9px; + margin-top: 2px; + } + + .workshop-shell .effect-pager { + gap: 5px; + margin-top: 4px; + } + + .workshop-shell .effect-pager button { + font-size: 6px; + min-height: 22px; + padding: 2px 5px; + } + .workshop-shell .talent-tier { gap: 6px; grid-template-columns: 66px minmax(0, 1fr); diff --git a/src/components/TalentScreen.tsx b/src/components/TalentScreen.tsx index 9a7a5db..526153e 100644 --- a/src/components/TalentScreen.tsx +++ b/src/components/TalentScreen.tsx @@ -16,6 +16,7 @@ type Props = { const EFFECT_SLOT_LEVELS = [5, 10, 15, 20] as const const EFFECT_CLASS_ID = 1 +const EFFECTS_PER_PAGE = 8 const EFFECT_SOURCE_LABELS: Record = { mend: 'Mend', radiance: 'Radiance', @@ -42,6 +43,7 @@ export function TalentScreen({ profile, onBack, onUpdated, embedded = false }: P const [busyTalentId, setBusyTalentId] = useState(null) const [resetting, setResetting] = useState(false) const [selectedTalentId, setSelectedTalentId] = useState(null) + const [effectPage, setEffectPage] = useState(0) const [message, setMessage] = useState('') const scrollRef = useRef(0) const gameClass = profile.classes.find( @@ -54,6 +56,11 @@ export function TalentScreen({ profile, onBack, onUpdated, embedded = false }: P ?? selectedEffects[0] ?? gameClass.talents[0] ?? null + const effectPageCount = Math.max(1, Math.ceil(gameClass.talents.length / EFFECTS_PER_PAGE)) + const visibleTalents = gameClass.talents.slice( + effectPage * EFFECTS_PER_PAGE, + effectPage * EFFECTS_PER_PAGE + EFFECTS_PER_PAGE, + ) useEffect(() => { window.scrollTo(0, scrollRef.current) @@ -64,6 +71,10 @@ export function TalentScreen({ profile, onBack, onUpdated, embedded = false }: P setSelectedTalentId(selectedTalent?.id ?? null) }, [gameClass.talents, selectedTalent?.id, selectedTalentId]) + useEffect(() => { + setEffectPage((page) => Math.min(page, effectPageCount - 1)) + }, [effectPageCount]) + function saveScroll() { scrollRef.current = window.scrollY } @@ -225,7 +236,7 @@ export function TalentScreen({ profile, onBack, onUpdated, embedded = false }: P )}
- {gameClass.talents.map((talent) => { + {visibleTalents.map((talent) => { const reason = lockReason(talent) const active = talent.rank > 0 const selected = selectedTalent?.id === talent.id @@ -244,13 +255,32 @@ export function TalentScreen({ profile, onBack, onUpdated, embedded = false }: P {talent.glyph}
{talent.name} - {talent.description} + {EFFECT_SOURCE_LABELS[effectSource(talent.effectType)] ?? 'Spell'}
{isBusy ? 'Saving' : active ? 'Active' : reason || 'Available'} ) })}
+ {effectPageCount > 1 && ( +
+ + {effectPage + 1}/{effectPageCount} + +
+ )} )}