From 89fd9b3c40b74ea8d63e18f4b4c8d56e6e43c42c Mon Sep 17 00:00:00 2001 From: rope Date: Tue, 20 Oct 2026 16:04:50 +0100 Subject: [PATCH] Skill pages: potion and tool side by side with swaps; happenings as red and green banners with a fix button Co-Authored-By: Claude Opus 5.5 --- game.py | 2 +- static/app.js | 65 +++++++++++++++++++++++++++-------------------- static/styles.css | 16 ++++++++++++ 3 files changed, 54 insertions(+), 29 deletions(-) diff --git a/game.py b/game.py index fae30c5..deb8f73 100644 --- a/game.py +++ b/game.py @@ -3562,7 +3562,7 @@ for _index, (_area, _level, _prefix, _races) in enumerate(AREAS): ACTION_GLYPHS[f"fight_{_slug(_race)}"] = (RACE_GLYPHS[_race], TIER_TINTS[_index]) UI_GLYPHS = ["two-coins", "sparkles", "hearts", "prayer", "checked-shield", "magic-shield", "stopwatch", "swordman", "biceps", "cancel", "striking-diamonds", "archery-target", "dodging", "shield-reflect", - "cape", "gem-pendant", "gauntlet", "ring", "quiver", "leather-boot", "crystal-ball", "hot-meal", "visored-helm", "breastplate", + "cape", "gem-pendant", "gauntlet", "ring", "quiver", "leather-boot", "crystal-ball", "hot-meal", "round-bottom-flask", "visored-helm", "breastplate", "leg-armor", "pointy-sword", "round-shield", *(kind["glyph"] for kind in DAMAGE_TYPES.values())] diff --git a/static/app.js b/static/app.js index a270d63..8f9bb76 100644 --- a/static/app.js +++ b/static/app.js @@ -426,8 +426,7 @@ function skillPage(id) { if (id === "construction") html += S.city.construction ? siteBanner() : `
Nothing under construction.
`; if (id === "farming" && me.cls === "homesteader") html += plotsPanel(); - html += potionPanel(id); - if (`tool_${id}` in me.equip) html += toolPanel(id); + html += skillGear(id); if (id === "reconnaissance") html += `
Raises intel on the coming threat. Higher levels open as it nears.
`; html += skillHappenings(id); const live = new Set(S.happenings.problems.map((p) => p.kind)); @@ -473,19 +472,32 @@ function plotsPanel() { } // The skill's active potion, or the ones in the bank that could be drunk for it. -function potionPanel(skill) { - const me = S.me; +// A skill's potion and tool side by side, each a slot with what you could switch to beside it. +function skillGear(skill) { + const me = S.me, cards = []; const kinds = Object.entries(C.items).filter(([, it]) => it.potion?.skill === skill); - if (!kinds.length) return ""; - const active = me.potions[skill]; - const owned = kinds.filter(([id]) => me.bank[id]); - const drink = owned.map(([id, it]) => ``).join(""); - return `
${active - ? `${item(active.item).icon} ${esc(item(active.item).name)} ${esc(item(active.item).potion.text)} · ${fmt(active.charges)} uses left${me.bank[active.item] ? `, ${fmt(me.bank[active.item])} more in the bank` : ""}` - : `No potion.`} - ${!active && drink ? `Drink: ${drink}` : ""} - ${!active && !drink ? `Brewed in Herblore.` : ""} -
`; + if (kinds.length) { + const active = me.potions[skill], it = active && item(active.item); + const others = kinds.filter(([id]) => me.bank[id] && id !== active?.item).map(([id, p]) => + ``).join(""); + cards.push(`
+ ${it ? it.icon : glyph("round-bottom-flask", "#98a1b4")} +
${it ? `${esc(it.name)}${esc(it.potion.text)} · ${fmt(active.charges)} uses` + : `No potion`}
+ ${others ? `
${others}
` : ""}
`); + } + if (`tool_${skill}` in me.equip) { + const id = me.equip[`tool_${skill}`], it = id && item(id); + const others = Object.keys(me.bank).filter((x) => item(x).tool === skill).sort((a, b) => item(b).speed - item(a).speed).map((x) => { + const [s, level] = item(x).req || [], why = s && me.levels[s] < level ? `Needs ${skillOf(s).name.toLowerCase()} ${level}` : ""; + return ``; + }).join(""); + cards.push(`
+ ${it ? it.icon : skillOf(skill).icon} +
${it ? `${esc(it.name)}${Math.round(it.speed * 100)}% faster` : `No tool`}
+ ${others || it ? `
${others}${it ? `` : ""}
` : ""}
`); + } + return cards.length ? `
${cards.join("")}
` : ""; } function prayerPanel() { @@ -515,14 +527,6 @@ function prayerPanel() { `; } -function toolPanel(skill) { - const tool = S.me.equip[`tool_${skill}`]; - return `
${tool - ? `${item(tool).icon} ${esc(item(tool).name)} ${Math.round(item(tool).speed * 100)}% faster - ` - : `No tool.`}
`; -} - // A damage type's icon: what an enemy attacks with, or (tinted) what it's vulnerable to or resists. function dmgIcon(type, how = "") { const t = C.damage_types[type], name = t.name.toLowerCase(); @@ -975,17 +979,22 @@ function happeningsPanel() { // What's speeding up, slowing down or blocking this skill right now. function skillHappenings(skill) { - const h = S.happenings, lines = []; - const note = (icon, name, text) => lines.push(`${icon} ${esc(name)} ${esc(text)}`); + const h = S.happenings, banners = []; const touches = (src) => ["speed", "yield", "xp"].some((k) => src[k] && skill in src[k]) || (src.block && skill in src.block); - if (touches(h.weather)) note(h.weather.icon, h.weather.name, h.weather.text); - for (const e of h.effects) if (touches(e)) note(e.icon, e.name, e.text); + const good = (src) => (src.speed?.[skill] ?? 1) < 1 || (src.yield?.[skill] ?? 1) > 1 || (src.xp?.[skill] ?? 1) > 1; + const banner = (tone, icon, name, text, button = "") => banners.push(``); + if (touches(h.weather)) banner(good(h.weather) ? "good" : "bad", h.weather.icon, h.weather.name, h.weather.text); + for (const e of h.effects) if (touches(e)) banner(good(e) ? "good" : "bad", e.icon, e.name, e.text); for (const p of h.problems) { if (!C.problems[p.kind] || !touches(C.problems[p.kind])) continue; const fix = p.action && C.actions.find((x) => x.id === p.action); - note(p.icon, p.name, !fix ? p.text : fix.skill === skill ? "Clear it below." : `${skillOf(fix.skill).name} clears it (Town square).`); + const button = fix ? (fix.skill === skill ? `` + : ``) + : ``; + banner("bad", p.icon, p.name, p.text, button); } - return lines.length ? `
${lines.join("")}
` : ""; + return banners.join(""); } function buildingCard(key, b) { diff --git a/static/styles.css b/static/styles.css index 234b77b..42f9fd2 100644 --- a/static/styles.css +++ b/static/styles.css @@ -555,3 +555,19 @@ button.sheet-skill:hover { background: var(--panel-2); } .doll-slot.starving { border-color: rgba(226, 100, 92, .7); background: rgba(226, 100, 92, .12); } .doll-slot.starving .gi { opacity: .9; } @media (max-width: 520px) { .doll-col { justify-items: center; } } + +/* A skill's potion and tool, side by side */ +.gear-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr)); gap: 14px; margin-bottom: 14px; } +.gear-row .panel { margin: 0; } +.gear-card { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; padding: 12px 14px; } +.gear-card > .doll-slot { cursor: default; flex: none; } +.gear-text { display: grid; gap: 2px; min-width: 0; flex: 1; } +.gear-card .swap-grid { margin-left: auto; } +/* Happenings that touch this skill */ +.happening-banner { margin-bottom: 14px; align-items: center; } +.happening-banner .banner-ico { font-size: 30px; } +.happening-banner b { font-size: 15px; } +.banner.good { border-color: rgba(95, 174, 90, .55); background: rgba(95, 174, 90, .08); } +.happening-banner { flex-wrap: wrap; } +.happening-banner .grow { min-width: min(260px, 70%); } +.happening-banner > .btn { margin-left: auto; }