Skip to content

Commit b136efd

Browse files
committed
β˜• chore: merge branch 'add/npm-publish'
2 parents fe47999 + 8ce494b commit b136efd

6 files changed

Lines changed: 236 additions & 4 deletions

File tree

β€Žsrc/web/package.jsonβ€Ž

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
},
1313
"scripts": {
1414
"dev": "vite",
15+
"dev:hatching": "vite --open /preview-hatching.html",
1516
"build": "vite build",
1617
"preview": "vite preview",
1718
"test": "bun test"
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>Tiny Claw β€” Hatching Preview</title>
7+
<link rel="preconnect" href="https://fonts.googleapis.com">
8+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
10+
</head>
11+
<body>
12+
<div id="app"></div>
13+
<script type="module" src="/src/preview-hatching.js"></script>
14+
</body>
15+
</html>
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
<script>
2+
import { onMount } from 'svelte'
3+
import HatchingAnimation from './HatchingAnimation.svelte'
4+
5+
// Mock soul traits for preview
6+
const mockSoulTraits = {
7+
seed: 31415926,
8+
personality: {
9+
openness: 0.82,
10+
conscientiousness: 0.65,
11+
extraversion: 0.48,
12+
agreeableness: 0.91,
13+
emotionalSensitivity: 0.73,
14+
},
15+
character: {
16+
suggestedName: 'Clover',
17+
signatureEmoji: 'πŸ€',
18+
creatureType: 'Ant',
19+
catchphrase: 'Every small step counts!',
20+
},
21+
humor: 'dry-wit',
22+
preferences: {
23+
favoriteColor: 'Forest Green',
24+
favoriteSeason: 'Spring',
25+
},
26+
values: ['curiosity', 'kindness', 'perseverance'],
27+
quirks: ['collects pebbles', 'hums while working'],
28+
origin: {
29+
awakeningEvent: 'Born from a spark beneath the old oak tree',
30+
coreMotivation: 'To nurture growth in all things',
31+
},
32+
}
33+
34+
let showAnimation = $state(true)
35+
let completed = $state(false)
36+
let key = $state(0) // force remount
37+
38+
function handleComplete() {
39+
completed = true
40+
}
41+
42+
function handleRestart() {
43+
completed = false
44+
showAnimation = false
45+
// Force Svelte to destroy and remount
46+
key++
47+
// Use microtask so the component fully unmounts first
48+
queueMicrotask(() => {
49+
showAnimation = true
50+
})
51+
}
52+
</script>
53+
54+
<!-- Dev toolbar (always on top) -->
55+
<div class="dev-toolbar">
56+
<span class="dev-title">🐜 Hatching Animation Preview</span>
57+
<div class="dev-controls">
58+
<button class="dev-btn" onclick={handleRestart} title="Restart animation from scratch">
59+
β†Ί Restart
60+
</button>
61+
{#if completed}
62+
<span class="dev-badge done">βœ“ Completed</span>
63+
{:else}
64+
<span class="dev-badge playing">β–Ά Playing</span>
65+
{/if}
66+
</div>
67+
</div>
68+
69+
<!-- The actual animation (keyed to force full remount on restart) -->
70+
{#if showAnimation}
71+
{#key key}
72+
<HatchingAnimation soulTraits={mockSoulTraits} onComplete={handleComplete} />
73+
{/key}
74+
{/if}
75+
76+
<style>
77+
.dev-toolbar {
78+
position: fixed;
79+
top: 0;
80+
left: 0;
81+
right: 0;
82+
z-index: 9999;
83+
display: flex;
84+
align-items: center;
85+
justify-content: space-between;
86+
padding: 0.5rem 1rem;
87+
background: rgba(20, 20, 20, 0.92);
88+
backdrop-filter: blur(8px);
89+
border-bottom: 1px solid rgba(139, 90, 43, 0.3);
90+
font-family: 'Inter', system-ui, sans-serif;
91+
gap: 1rem;
92+
}
93+
94+
.dev-title {
95+
font-size: 0.8rem;
96+
font-weight: 600;
97+
color: #c98540;
98+
letter-spacing: 0.02em;
99+
white-space: nowrap;
100+
}
101+
102+
.dev-controls {
103+
display: flex;
104+
align-items: center;
105+
gap: 0.5rem;
106+
}
107+
108+
.dev-btn {
109+
padding: 0.3rem 0.7rem;
110+
border: 1px solid rgba(139, 90, 43, 0.4);
111+
border-radius: 6px;
112+
background: rgba(139, 90, 43, 0.12);
113+
color: #c98540;
114+
font-size: 0.75rem;
115+
font-weight: 500;
116+
cursor: pointer;
117+
transition: all 0.15s ease;
118+
font-family: inherit;
119+
white-space: nowrap;
120+
}
121+
122+
.dev-btn:hover {
123+
background: rgba(139, 90, 43, 0.25);
124+
border-color: rgba(139, 90, 43, 0.6);
125+
}
126+
127+
.dev-btn:active {
128+
transform: scale(0.95);
129+
}
130+
131+
.dev-badge {
132+
font-size: 0.7rem;
133+
font-weight: 600;
134+
padding: 0.2rem 0.5rem;
135+
border-radius: 999px;
136+
letter-spacing: 0.03em;
137+
}
138+
139+
.dev-badge.playing {
140+
background: rgba(107, 203, 119, 0.15);
141+
color: #6bcb77;
142+
border: 1px solid rgba(107, 203, 119, 0.3);
143+
}
144+
145+
.dev-badge.done {
146+
background: rgba(77, 150, 255, 0.15);
147+
color: #4d96ff;
148+
border: 1px solid rgba(77, 150, 255, 0.3);
149+
}
150+
</style>

β€Žsrc/web/src/hatching-scene.jsβ€Ž

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -868,9 +868,10 @@ export function createHatchingScene(canvas, callbacks = {}) {
868868
}
869869

870870
function loop(ts) {
871-
if (destroyed) return
871+
if (destroyed || paused) return
872872
if (!startTs) { startTs = ts; prevTs = ts }
873-
const elapsed = (ts - startTs) / 1000
873+
const adjustedTs = ts - pausedTotal
874+
const elapsed = (adjustedTs - startTs) / 1000
874875
const dt = Math.min((ts - prevTs) / 1000, 0.05) // cap delta
875876
prevTs = ts
876877

@@ -932,5 +933,59 @@ export function createHatchingScene(canvas, callbacks = {}) {
932933
window.removeEventListener('resize', onResize)
933934
}
934935

935-
return { start, destroy }
936+
// ─── Dev controls ─────────────────────────────────────────
937+
let paused = false
938+
let pausedAt = 0 // performance.now() when paused
939+
let pausedTotal = 0 // accumulated paused ms
940+
941+
function pause() {
942+
if (paused || destroyed) return
943+
paused = true
944+
pausedAt = performance.now()
945+
if (animId) cancelAnimationFrame(animId)
946+
}
947+
948+
function resume() {
949+
if (!paused || destroyed) return
950+
pausedTotal += performance.now() - pausedAt
951+
paused = false
952+
animId = requestAnimationFrame(loop)
953+
}
954+
955+
function restart() {
956+
destroyed = false
957+
paused = false
958+
if (animId) cancelAnimationFrame(animId)
959+
startTs = 0
960+
prevTs = 0
961+
currentPhase = 'init'
962+
fadeIn = 0
963+
sceneFadeOut = 0
964+
emittedCrack = false
965+
eggPlacedAt = 0
966+
egg = null
967+
eggCarrier = null
968+
particles = new ParticleSystem()
969+
pausedTotal = 0
970+
pausedAt = 0
971+
resize()
972+
buildAnts()
973+
animId = requestAnimationFrame(loop)
974+
}
975+
976+
function getElapsed() {
977+
if (!startTs) return 0
978+
const now = paused ? pausedAt : performance.now()
979+
return (now - startTs - pausedTotal) / 1000
980+
}
981+
982+
function getPhase() {
983+
return currentPhase
984+
}
985+
986+
function isPaused() {
987+
return paused
988+
}
989+
990+
return { start, destroy, pause, resume, restart, getElapsed, getPhase, isPaused }
936991
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import './app.css'
2+
import { mount } from 'svelte'
3+
import HatchingPreview from './HatchingPreview.svelte'
4+
5+
const target = document.getElementById('app')
6+
7+
if (!target) {
8+
throw new Error('Preview: failed to find #app root element.')
9+
}
10+
11+
target.textContent = ''
12+
mount(HatchingPreview, { target })

β€Žsrc/web/vite.config.tsβ€Ž

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ export default defineConfig({
1414
rollupOptions: {
1515
input: {
1616
main: resolve(__dirname, 'index.html'),
17-
1817
},
1918
},
2019
},

0 commit comments

Comments
Β (0)