fix: DocsPage escaped quotes breaking template + missing rotate() on ₿ text objects
DocsPage: escaped \"answer\" inside v-for HTML attribute broke template
parsing causing infinite Vite error loop. Replaced with " entity.
FightScene: three k.text('₿') objects used .angle without k.rotate()
component, causing TS2339 build errors.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
da14ca81ee
commit
8a345f5e56
+263
-29
@@ -114,7 +114,7 @@ const TIER_ULTIMATES: Record<number, string[]> = {
|
||||
}
|
||||
|
||||
// The Creator — exclusive moves (50% ultimate chance, 100% on crits, custom pool)
|
||||
const CREATOR_MOVES = ['bitcoinRain', 'lightningInvoice', 'satoshiStrike', 'hashRateOverload', 'blockchainSlam', 'creatorMode', 'morphStrike', 'divineIntervention']
|
||||
const CREATOR_MOVES = ['bitcoinRain', 'lightningInvoice', 'satoshiStrike', 'hashRateOverload', 'blockchainSlam', 'creatorMode', 'morphStrike', 'divineIntervention', 'bitcoinSwarm', 'satoshiTornado', 'hodlWave', 'bitcoinBarrage']
|
||||
const CREATOR_ULTIMATES = ['ultimateGenesisBlock', 'ultimateTheHalving', 'ultimateFullNodeCrush', 'ultimateSatoshiVision']
|
||||
|
||||
function pickChoreography(challengeType: string, isCritical: boolean, _round: number, attackerTier: number = 0, attackerArchetype?: string): string {
|
||||
@@ -5719,26 +5719,31 @@ export async function createFightScene(config: FightSceneConfig) {
|
||||
atk.play('special')
|
||||
sfxSpecial()
|
||||
await k.wait(0.2)
|
||||
// Rain golden ₿ coins from above
|
||||
const coinCount = isCritical ? 30 : 15
|
||||
// Rain golden ₿ letters from above
|
||||
const coinCount = isCritical ? 40 : 20
|
||||
for (let i = 0; i < coinCount; i++) {
|
||||
const cx = def.pos.x + (Math.random() - 0.5) * 120
|
||||
const startY = def.pos.y - 200 - Math.random() * 100
|
||||
const cx = def.pos.x + (Math.random() - 0.5) * 140
|
||||
const startY = def.pos.y - 200 - Math.random() * 120
|
||||
const sz = isCritical ? 8 + Math.random() * 10 : 6 + Math.random() * 6
|
||||
const coin = k.add([
|
||||
k.circle(isCritical ? 5 : 3),
|
||||
k.text('₿', { size: sz }),
|
||||
k.pos(cx, startY),
|
||||
k.color(safeColor(k, Math.random() > 0.3 ? '#ffd700' : '#ffee88')),
|
||||
k.color(safeColor(k, Math.random() > 0.3 ? '#ffd700' : Math.random() > 0.5 ? '#ffee88' : '#ff8c00')),
|
||||
k.opacity(0.9),
|
||||
k.z(18),
|
||||
k.anchor('center'),
|
||||
k.rotate(Math.random() * 360),
|
||||
])
|
||||
const spin = (Math.random() - 0.5) * 400
|
||||
coin.onUpdate(() => {
|
||||
coin.pos.y += 600 * k.dt()
|
||||
coin.pos.y += 650 * k.dt()
|
||||
coin.angle += spin * k.dt()
|
||||
if (coin.pos.y > def.pos.y + 10) {
|
||||
coin.destroy()
|
||||
}
|
||||
})
|
||||
if (i % 4 === 0) sfxCoin()
|
||||
await k.wait(0.03)
|
||||
if (i % 3 === 0) sfxCoin()
|
||||
await k.wait(0.02)
|
||||
}
|
||||
await k.wait(0.15)
|
||||
def.play(isCritical ? 'knockback' : 'hit')
|
||||
@@ -6013,6 +6018,218 @@ export async function createFightScene(config: FightSceneConfig) {
|
||||
atk.play('idle')
|
||||
}
|
||||
|
||||
// ── Creator ₿ Swarm Variations ──
|
||||
|
||||
async function bitcoinSwarm(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) {
|
||||
// Swarm of ₿ letters fly from creator toward defender with zigzag paths
|
||||
atk.play('special')
|
||||
sfxSpecial()
|
||||
await k.wait(0.15)
|
||||
const total = isCritical ? 30 : 18
|
||||
const swarmThings: any[] = []
|
||||
for (let i = 0; i < total; i++) {
|
||||
const sx = atk.pos.x + dir * 10 + (Math.random() - 0.5) * 30
|
||||
const sy = atk.pos.y - 50 + (Math.random() - 0.5) * 60
|
||||
const sz = 6 + Math.random() * 8
|
||||
const colors = ['#ffd700', '#ffee88', '#ff8c00', '#c8a000']
|
||||
const p = k.add([
|
||||
k.text('₿', { size: sz }),
|
||||
k.pos(sx, sy),
|
||||
k.color(safeColor(k, colors[i % colors.length])),
|
||||
k.opacity(0.9),
|
||||
k.z(16),
|
||||
k.anchor('center'),
|
||||
k.rotate(Math.random() * 360),
|
||||
])
|
||||
const tx = def.pos.x + (Math.random() - 0.5) * 30
|
||||
const ty = def.pos.y - 30 + (Math.random() - 0.5) * 40
|
||||
const zigFreq = 4 + Math.random() * 4
|
||||
const zigAmp = 12 + Math.random() * 15
|
||||
const spinRate = (Math.random() - 0.5) * 600
|
||||
swarmThings.push(p)
|
||||
k.tween(0, 1, 0.3 + Math.random() * 0.15, (t) => {
|
||||
p.pos.x = sx + (tx - sx) * t
|
||||
p.pos.y = sy + (ty - sy) * t + Math.sin(t * Math.PI * zigFreq) * zigAmp
|
||||
p.angle += spinRate * k.dt()
|
||||
p.opacity = 0.9 - t * 0.3
|
||||
}).then(() => { if (p.exists()) { spawnSparks(p.pos.x, p.pos.y, 2, '#ffd700'); p.destroy() } })
|
||||
if (i % 3 === 0) sfxCoin()
|
||||
await k.wait(0.02)
|
||||
}
|
||||
await k.wait(0.25)
|
||||
sfxBonk()
|
||||
def.play(isCritical ? 'knockback' : 'hit')
|
||||
k.shake(isCritical ? 18 : 8)
|
||||
if (isCritical) { screenFlash('#ffd700', 0.1); sfxCritical() }
|
||||
spawnSparks(def.pos.x, def.pos.y - 25, isCritical ? 18 : 10, '#ffd700')
|
||||
const push = dir * (isCritical ? 90 : 40)
|
||||
k.tween(def.pos.x, origDX + push, 0.2, (v: number) => { def.pos.x = v }, k.easings.easeOutQuad)
|
||||
await k.wait(0.3)
|
||||
atk.play('idle')
|
||||
swarmThings.forEach(s => { if (s.exists()) s.destroy() })
|
||||
await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad)
|
||||
}
|
||||
|
||||
async function satoshiTornado(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) {
|
||||
// ₿ letters spiral around the defender in a tightening vortex then explode
|
||||
atk.play('special')
|
||||
sfxSpecial()
|
||||
sfxZoomWhoosh()
|
||||
const total = isCritical ? 24 : 14
|
||||
const vortex: any[] = []
|
||||
for (let i = 0; i < total; i++) {
|
||||
const sz = 7 + Math.random() * 7
|
||||
const colors = ['#ffd700', '#ffee88', '#ff8c00', '#ffffff']
|
||||
const p = k.add([
|
||||
k.text('₿', { size: sz }),
|
||||
k.pos(def.pos.x, def.pos.y - 30),
|
||||
k.color(safeColor(k, colors[i % colors.length])),
|
||||
k.opacity(0.85),
|
||||
k.z(18),
|
||||
k.anchor('center'),
|
||||
k.rotate(0),
|
||||
])
|
||||
vortex.push(p)
|
||||
const baseAngle = (i / total) * Math.PI * 2
|
||||
const startRadius = 80 + Math.random() * 30
|
||||
p.onUpdate(() => {
|
||||
const elapsed = k.time()
|
||||
const shrink = Math.max(5, startRadius - elapsed * 40)
|
||||
const a = baseAngle + elapsed * (6 + i * 0.3)
|
||||
p.pos.x = def.pos.x + Math.cos(a) * shrink
|
||||
p.pos.y = def.pos.y - 30 + Math.sin(a) * shrink * 0.6
|
||||
p.angle = elapsed * 200 + i * 45
|
||||
p.opacity = 0.6 + Math.sin(elapsed * 8 + i) * 0.3
|
||||
})
|
||||
await k.wait(0.04)
|
||||
}
|
||||
// Let the tornado spin for a moment
|
||||
await k.wait(0.7)
|
||||
// Explode outward
|
||||
screenFlash('#ffd700', 0.12)
|
||||
sfxExplosion()
|
||||
k.shake(isCritical ? 22 : 10)
|
||||
vortex.forEach((p, i) => {
|
||||
if (!p.exists()) return
|
||||
const angle = (i / total) * Math.PI * 2
|
||||
const dist = 100 + Math.random() * 60
|
||||
k.tween(0, 1, 0.3, (t) => {
|
||||
p.pos.x = def.pos.x + Math.cos(angle) * dist * t
|
||||
p.pos.y = def.pos.y - 30 + Math.sin(angle) * dist * 0.6 * t
|
||||
p.opacity = 1 - t
|
||||
}).then(() => { if (p.exists()) p.destroy() })
|
||||
})
|
||||
def.play(isCritical ? 'knockback' : 'hit')
|
||||
if (isCritical) { sfxCritical(); glitchRGB() }
|
||||
spawnSparks(def.pos.x, def.pos.y - 30, isCritical ? 20 : 10, '#ffd700')
|
||||
const push = dir * (isCritical ? 100 : 45)
|
||||
k.tween(def.pos.x, origDX + push, 0.25, (v: number) => { def.pos.x = v }, k.easings.easeOutQuad)
|
||||
await k.wait(0.4)
|
||||
atk.play('idle')
|
||||
vortex.forEach(p => { if (p.exists()) p.destroy() })
|
||||
await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad)
|
||||
}
|
||||
|
||||
async function hodlWave(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) {
|
||||
// Wall of ₿ letters advancing across the screen like a tidal wave
|
||||
atk.play('special')
|
||||
sfxSpecial()
|
||||
await k.wait(0.1)
|
||||
const rows = isCritical ? 6 : 4
|
||||
const cols = isCritical ? 10 : 7
|
||||
const wave: any[] = []
|
||||
const startX = atk.pos.x + dir * 25
|
||||
const screenTop = -20
|
||||
const screenBot = GROUND_Y + 10
|
||||
for (let c = 0; c < cols; c++) {
|
||||
for (let r = 0; r < rows; r++) {
|
||||
const sz = 8 + Math.random() * 6
|
||||
const colors = ['#ffd700', '#ffee88', '#ff8c00', '#c8a000', '#ffffff']
|
||||
const py = screenTop + (screenBot - screenTop) * (r / rows) + (Math.random() - 0.5) * 20
|
||||
const p = k.add([
|
||||
k.text('₿', { size: sz }),
|
||||
k.pos(startX + c * dir * 8, py),
|
||||
k.color(safeColor(k, colors[(r + c) % colors.length])),
|
||||
k.opacity(0.85),
|
||||
k.z(17),
|
||||
k.anchor('center'),
|
||||
k.rotate(Math.random() * 40 - 20),
|
||||
])
|
||||
wave.push(p)
|
||||
}
|
||||
// Stagger each column
|
||||
const targetX = def.pos.x + dir * (10 + c * 3)
|
||||
wave.slice(-rows).forEach((p) => {
|
||||
const sx = p.pos.x
|
||||
k.tween(0, 1, 0.35 + c * 0.04, (t) => {
|
||||
p.pos.x = sx + (targetX - sx) * t
|
||||
p.angle += 120 * k.dt()
|
||||
p.pos.y += Math.sin(t * Math.PI * 3) * 2
|
||||
}).then(() => { if (p.exists()) { spawnSparks(p.pos.x, p.pos.y, 1, '#ffd700'); p.destroy() } })
|
||||
})
|
||||
if (c % 2 === 0) sfxCoin()
|
||||
await k.wait(0.04)
|
||||
}
|
||||
await k.wait(0.3)
|
||||
sfxExplosion()
|
||||
def.play(isCritical ? 'knockback' : 'hit')
|
||||
k.shake(isCritical ? 20 : 9)
|
||||
if (isCritical) { screenFlash('#ffd700', 0.12); glitchRGB(0.2) }
|
||||
spawnSparks(def.pos.x, def.pos.y - 25, isCritical ? 22 : 12, '#ffd700')
|
||||
const push = dir * (isCritical ? 95 : 42)
|
||||
k.tween(def.pos.x, origDX + push, 0.2, (v: number) => { def.pos.x = v }, k.easings.easeOutQuad)
|
||||
await k.wait(0.35)
|
||||
atk.play('idle')
|
||||
wave.forEach(p => { if (p.exists()) p.destroy() })
|
||||
await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad)
|
||||
}
|
||||
|
||||
async function bitcoinBarrage(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) {
|
||||
// Rapid-fire ₿ letters shot from the creator's laptop like bullets
|
||||
atk.play('special')
|
||||
sfxSpecial()
|
||||
await k.wait(0.1)
|
||||
const shotCount = isCritical ? 35 : 20
|
||||
for (let i = 0; i < shotCount; i++) {
|
||||
const sz = 5 + Math.random() * 8
|
||||
const colors = ['#ffd700', '#ffee88', '#ff8c00', '#c8a000']
|
||||
const spreadY = (Math.random() - 0.5) * (15 + i * 0.5)
|
||||
const p = k.add([
|
||||
k.text('₿', { size: sz }),
|
||||
k.pos(atk.pos.x + dir * 25, atk.pos.y - 42 + spreadY),
|
||||
k.color(safeColor(k, colors[i % colors.length])),
|
||||
k.opacity(0.9),
|
||||
k.z(16),
|
||||
k.anchor('center'),
|
||||
k.rotate(Math.random() * 360),
|
||||
])
|
||||
const speed = 900 + Math.random() * 500
|
||||
const spinRate = (Math.random() - 0.5) * 800
|
||||
p.onUpdate(() => {
|
||||
p.pos.x += dir * speed * k.dt()
|
||||
p.angle += spinRate * k.dt()
|
||||
p.opacity -= 1.2 * k.dt()
|
||||
if (p.opacity <= 0 || Math.abs(p.pos.x - atk.pos.x) > 500) p.destroy()
|
||||
})
|
||||
if (i % 2 === 0) sfxCoin()
|
||||
if (i % 4 === 0) {
|
||||
def.play('hit')
|
||||
k.shake(2)
|
||||
spawnSparks(def.pos.x, def.pos.y - 25 - Math.random() * 20, 3, '#ffd700')
|
||||
}
|
||||
await k.wait(0.018)
|
||||
}
|
||||
def.play(isCritical ? 'knockback' : 'hit')
|
||||
k.shake(isCritical ? 15 : 6)
|
||||
if (isCritical) { screenFlash('#ffd700', 0.1); sfxCritical() }
|
||||
spawnSparks(def.pos.x, def.pos.y - 30, isCritical ? 18 : 8, '#ffd700')
|
||||
const push = dir * (isCritical ? 80 : 35)
|
||||
k.tween(def.pos.x, origDX + push, 0.2, (v: number) => { def.pos.x = v }, k.easings.easeOutQuad)
|
||||
await k.wait(0.3)
|
||||
atk.play('idle')
|
||||
await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad)
|
||||
}
|
||||
|
||||
// ── Creator Ultimates ──
|
||||
|
||||
async function ultimateGenesisBlock(atk: any, def: any, dir: number, origAX: number, origDX: number, _isCrit: boolean) {
|
||||
@@ -6264,6 +6481,7 @@ export async function createFightScene(config: FightSceneConfig) {
|
||||
// --- THE CREATOR: Bitcoin-themed moves ---
|
||||
bitcoinRain, lightningInvoice, satoshiStrike, hashRateOverload, blockchainSlam, creatorMode,
|
||||
morphStrike, divineIntervention,
|
||||
bitcoinSwarm, satoshiTornado, hodlWave, bitcoinBarrage,
|
||||
// --- THE CREATOR: Exclusive ultimates ---
|
||||
ultimateGenesisBlock, ultimateTheHalving, ultimateFullNodeCrush, ultimateSatoshiVision,
|
||||
}
|
||||
@@ -6526,23 +6744,30 @@ export async function createFightScene(config: FightSceneConfig) {
|
||||
})
|
||||
activeMorphs.push({ obj: aura, type: 'omni' })
|
||||
|
||||
// Orbiting golden ₿ symbols (4 orbiting)
|
||||
for (let i = 0; i < 4; i++) {
|
||||
// Orbiting golden ₿ symbols (8 in dual rings, varied sizes)
|
||||
for (let i = 0; i < 8; i++) {
|
||||
const ring = i < 5 ? 0 : 1 // inner ring (5) + outer ring (3)
|
||||
const ringCount = ring === 0 ? 5 : 3
|
||||
const ringIdx = ring === 0 ? i : i - 5
|
||||
const sz = ring === 0 ? 10 + Math.random() * 4 : 14 + Math.random() * 4
|
||||
const rad = ring === 0 ? 40 : 60
|
||||
const spd = ring === 0 ? 3 : -2 // counter-rotate outer ring
|
||||
const btc = k.add([
|
||||
k.text('₿', { size: 10 }),
|
||||
k.text('₿', { size: sz }),
|
||||
k.pos(fighter.pos.x, fighter.pos.y - 20),
|
||||
k.color(safeColor(k, '#ffd700')),
|
||||
k.opacity(0.7),
|
||||
k.color(safeColor(k, i % 3 === 0 ? '#ffd700' : i % 3 === 1 ? '#ffee88' : '#ff8c00')),
|
||||
k.opacity(0.8),
|
||||
k.z(fighter.z + 2),
|
||||
k.anchor('center'),
|
||||
k.rotate(0),
|
||||
])
|
||||
const baseAngle = (i / 4) * Math.PI * 2
|
||||
const radius = 40
|
||||
const baseAngle = (ringIdx / ringCount) * Math.PI * 2
|
||||
btc.onUpdate(() => {
|
||||
const a = baseAngle + k.time() * 3
|
||||
btc.pos.x = fighter.pos.x + Math.cos(a) * radius
|
||||
btc.pos.y = fighter.pos.y - 20 + Math.sin(a) * radius * 0.5
|
||||
btc.opacity = 0.7 + Math.sin(k.time() * 8 + i) * 0.3
|
||||
const a = baseAngle + k.time() * spd
|
||||
btc.pos.x = fighter.pos.x + Math.cos(a) * rad
|
||||
btc.pos.y = fighter.pos.y - 20 + Math.sin(a) * rad * 0.5
|
||||
btc.opacity = 0.7 + Math.sin(k.time() * 6 + i * 1.3) * 0.3
|
||||
btc.angle = Math.sin(k.time() * 4 + i) * 20
|
||||
})
|
||||
activeMorphs.push({ obj: btc, type: 'omni' })
|
||||
}
|
||||
@@ -7726,17 +7951,26 @@ export async function createFightScene(config: FightSceneConfig) {
|
||||
}, k.easings.easeOutBack)
|
||||
k.shake(12)
|
||||
sfxExplosion()
|
||||
// Persistent golden particles around creator
|
||||
for (let i = 0; i < 6; i++) {
|
||||
// Persistent orbiting ₿ letters around creator (always visible)
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const ring = i < 6 ? 0 : 1
|
||||
const ringIdx = ring === 0 ? i : i - 6
|
||||
const ringCount = ring === 0 ? 6 : 4
|
||||
const sz = ring === 0 ? 7 + Math.random() * 3 : 10 + Math.random() * 4
|
||||
const rad = ring === 0 ? 25 + i * 4 : 45 + (i - 6) * 6
|
||||
const spd = ring === 0 ? 2 + i * 0.3 : -(1.5 + (i - 6) * 0.4)
|
||||
const colors = ['#ffd700', '#ffee88', '#ff8c00', '#c8a000', '#ffffff']
|
||||
const p = k.add([
|
||||
k.circle(2), k.pos(homeX, GROUND_Y - 30),
|
||||
k.color(safeColor(k, '#ffd700')), k.opacity(0.5), k.z(11), k.anchor('center'),
|
||||
k.text('₿', { size: sz }), k.pos(homeX, GROUND_Y - 30),
|
||||
k.color(safeColor(k, colors[i % colors.length])),
|
||||
k.opacity(0.5), k.z(11), k.anchor('center'), k.rotate(0),
|
||||
])
|
||||
p.onUpdate(() => {
|
||||
const a = k.time() * (2 + i * 0.4) + i
|
||||
p.pos.x = fighter.pos.x + Math.cos(a) * (20 + i * 5)
|
||||
p.pos.y = fighter.pos.y - 25 + Math.sin(a) * (15 + i * 3)
|
||||
p.opacity = 0.3 + Math.sin(k.time() * 6 + i) * 0.3
|
||||
const a = k.time() * spd + ringIdx * (Math.PI * 2 / ringCount)
|
||||
p.pos.x = fighter.pos.x + Math.cos(a) * rad
|
||||
p.pos.y = fighter.pos.y - 25 + Math.sin(a) * rad * 0.45
|
||||
p.opacity = 0.35 + Math.sin(k.time() * 5 + i * 1.2) * 0.25
|
||||
p.angle = Math.sin(k.time() * 3 + i) * 15
|
||||
})
|
||||
}
|
||||
announceCreatorEntrance()
|
||||
|
||||
Reference in New Issue
Block a user