fuzzyworld

explodorama / files / src / ui / offer.ts

offer.ts

Blame: the commit that last changed each line · file · history

e6bb7c42 2026-10-25 Pavel Safronov1import { isCharge, type GameState, type OfferKind } from '../sim/types'
e3f29330 2026-10-24 Pavel Safronov2import { CHARGE_ICONS, POWER_UP_ICONS, spriteUrl } from '../render/sprites'
32dd53cd 2026-10-25 Pavel Safronov3import type { SceneKind } from '../sim/demo'
4import { startClip, stopClip } from './clip'
ac534e57 2026-10-25 Pavel Safronov5import { openDialog } from './dialog'
e6bb7c42 2026-10-25 Pavel Safronov6import { CHARGE_TEXT, changeLine, POWER_UP_CLIP, POWER_UP_TEXT } from './info'
52d68b59 2026-10-24 Pavel Safronov7
8/**
9 * The gem's choice (user request, 2026-10-23): its offer of power-ups, each with what it does,
10 * laid out like the almanac. One must be picked; the dialog can't be closed any other way.
e3f29330 2026-10-24 Pavel Safronov11 * Developer mode's rainbow gem offers the charges too (2026-10-24).
32dd53cd 2026-10-25 Pavel Safronov12 *
ac534e57 2026-10-25 Pavel Safronov13 * Choosing takes two taps (user request, 2026-10-24: a tap on a card chose it at once): a tap on a
14 * card selects it, and the clip above the cards (PLAN.md §13c) plays it; **Choose** takes the one
15 * selected. The first card is selected at first.
52d68b59 2026-10-24 Pavel Safronov16 */
e3f29330 2026-10-24 Pavel Safronov17let chosen: ((kind: OfferKind) => void) | null = null
ac534e57 2026-10-25 Pavel Safronov18let selected: OfferKind | null = null
52d68b59 2026-10-24 Pavel Safronov19
e6bb7c42 2026-10-25 Pavel Safronov20export function openOffer(s: GameState, offer: OfferKind[], choose: (kind: OfferKind) => void): void {
52d68b59 2026-10-24 Pavel Safronov21 chosen = choose
22 openDialog({
23 title: 'Choose a power-up',
32dd53cd 2026-10-25 Pavel Safronov24 body:
ac534e57 2026-10-25 Pavel Safronov25 '<div class="offer-clip"><canvas class="clip" aria-hidden="true"></canvas><img class="offer-still" alt="" draggable="false" hidden></div>' +
32dd53cd 2026-10-25 Pavel Safronov26 offer
27 .map((k) => {
28 const text = isCharge(k) ? CHARGE_TEXT[k] : POWER_UP_TEXT[k]
29 return (
ac534e57 2026-10-25 Pavel Safronov30 `<button type="button" class="almanac-entry offer-choice" data-offer="${k}" aria-pressed="false">` +
31 `<img src="${spriteUrl(isCharge(k) ? CHARGE_ICONS[k] : POWER_UP_ICONS[k])}" alt="" draggable="false">` +
e6bb7c42 2026-10-25 Pavel Safronov32 `<div><strong>${text.name}</strong>${changeLine(s, k)}<p>${text.text}</p></div></button>`
32dd53cd 2026-10-25 Pavel Safronov33 )
34 })
35 .join(''),
ac534e57 2026-10-25 Pavel Safronov36 buttons: [
37 {
38 label: 'Choose',
39 kind: 'primary',
40 onClick: () => {
41 chosen = null
42 if (selected) choose(selected)
43 },
44 },
45 ],
52d68b59 2026-10-24 Pavel Safronov46 required: true,
47 })
ac534e57 2026-10-25 Pavel Safronov48 select(offer[0]!)
49}
50
51function nameOf(k: OfferKind): string {
52 return (isCharge(k) ? CHARGE_TEXT[k] : POWER_UP_TEXT[k]).name
32dd53cd 2026-10-25 Pavel Safronov53}
54
55function clipOf(k: OfferKind): SceneKind | undefined {
56 return isCharge(k) ? k : POWER_UP_CLIP[k]
57}
58
ac534e57 2026-10-25 Pavel Safronov59/**
60 * Selects `k`: its card marked, the Choose button naming it, and the clip above the cards playing
61 * it; one without a clip (Heart, Coin bag: nothing to see on the board) shows its picture there,
62 * big, instead (user report, 2026-10-24: the space was left empty).
63 */
64function select(k: OfferKind): void {
65 selected = k
66 for (const card of document.querySelectorAll<HTMLElement>('#dialog-body .offer-choice')) {
67 card.setAttribute('aria-pressed', String(card.dataset.offer === k))
68 }
69 const label = document.querySelector('#dialog-buttons button span')
70 if (label) label.textContent = `Choose ${nameOf(k)}`
32dd53cd 2026-10-25 Pavel Safronov71 const canvas = document.querySelector<HTMLCanvasElement>('#dialog-body .offer-clip canvas.clip')
ac534e57 2026-10-25 Pavel Safronov72 const still = document.querySelector<HTMLImageElement>('#dialog-body .offer-still')
73 if (!canvas || !still) return
32dd53cd 2026-10-25 Pavel Safronov74 const clip = clipOf(k)
ac534e57 2026-10-25 Pavel Safronov75 canvas.hidden = !clip
76 still.hidden = !!clip
32dd53cd 2026-10-25 Pavel Safronov77 if (clip) return startClip(canvas, clip)
78 stopClip(canvas)
ac534e57 2026-10-25 Pavel Safronov79 still.src = spriteUrl(isCharge(k) ? CHARGE_ICONS[k] : POWER_UP_ICONS[k])
52d68b59 2026-10-24 Pavel Safronov80}
81
82const dialogBody = document.getElementById('dialog-body')!
83dialogBody.addEventListener('click', (e) => {
ac534e57 2026-10-25 Pavel Safronov84 const card = (e.target as Element).closest<HTMLElement>('[data-offer]')
85 if (card && chosen) select(card.dataset.offer as OfferKind)
32dd53cd 2026-10-25 Pavel Safronov86})