commit 4d77e6f239b086436d5f77522cc9cd3b80248e6e parent c379593a26268354f6e5d6e1d35240ee78faccc5 Author: Kyle Barlow <kb@kylebarlow.com> Date: Mon, 14 Sep 2026 20:28:36 -0700 sound soup update - better phenomes Diffstat:
789 files changed, 5448 insertions(+), 3792 deletions(-)
diff --git a/.gitignore b/.gitignore @@ -20,5 +20,5 @@ node_modules/ !sound_soup_plan.md !games/*/CREDITS.md -# Asset build cache (tools/sound-soup-assets.py) +# Asset build cache (tools/story-stamp-assets.py) .cache/ diff --git a/CLAUDE.md b/CLAUDE.md @@ -30,8 +30,9 @@ rendering that syntax checks miss (see the projection-bug incident). | `games/<slug>/index.html` | HTML shell: CDN scripts, HUD buttons, game container | | `games/<slug>/lib.js` | Pure logic (projection math, route gen) â dual-export for unit tests | | `games/<slug>/game.js` | Phaser scene â all graphics via `generateTexture`, audio via Web Audio / Tone.js | -| `games/sound-soup/` | DOM-based phonics game (no Phaser) â see "Sound Soup" | -| `tools/sound-soup-assets.py` | Fetches + trims Sound Soup's recorded audio and pictures, writes its manifest and credits | +| `games/story-stamp/` | DOM-based phonics game (no Phaser) â see "Story Stamp" | +| `tools/story-stamp-assets.py` | Fetches + cuts Story Stamp's recorded audio and pictures, writes its manifest and credits | +| `tools/story-stamp-audit.py` | Checks the phoneme clips with independent methods (acoustics, cross-speaker match, Vosk recognition, spectrograms) | | `js/shared/game-shared.js` | Shared `KGames.*` helpers (AudioSystem, win screen, confetti, progress bar) | | `js/shared/touch-controls.js` | On-screen keyboard / D-pad for phones â see "Mobile / touch controls" | | `assets/thumbnails/<slug>.svg` | 160Ă130 viewBox, rx=18, stroke from palette, `#FFF5F5` fill | @@ -103,35 +104,63 @@ Guard with `this.ambientReady`; clean up in `shutdown()`. All decor variety is deterministic via `FT.hashCell(x, y, salt)` so the island bakes identically every run. -## Sound Soup (DOM game, recorded audio) +## Story Stamp (DOM game, recorded audio) -Sound Soup teaches blending (synthetic phonics) and is the first game built -outside Phaser: plain DOM + CSS animations in `games/sound-soup/`, keyboard +Story Stamp teaches blending (synthetic phonics) and is the first game built +outside Phaser: plain DOM + CSS animations in `games/story-stamp/`, keyboard only. It is the reference for "Constraints for new games" in practice. -- `lib.js` â pure content + logic: `PHONEMES`/`GRAPHEMES`/`SEQUENCE` (the - teaching order, s-a-t-p-i-n first), `WORDS` (decodable words with a Twemoji - picture; `g` is the grapheme split, `p` overrides ambiguous sounds like - book's `oo`), `CUSTOMERS`, the mastery/spaced-repetition scheduler - (`recordOrder`, `pickNext`, `shouldIntroduce`), `blendTimeline`, and the - multi-child profile store. Unit-tested in `tests/unit/sound-soup-*.test.js`. +The child runs the checkout desk of the island library. Patrons queue with a +topic sign (bugs, space, dinosaurs âŚ); the child picks one, gets a request +slip with the title in sound boxes, stamps each letter (hearing its sound), +presses SPACE to sound it out, and picks the matching book from the cart. +**The game never says the word the child is decoding** â three blend passes +with shrinking gaps, then the picture choice is the proof. "Returns" (patron +says the word, child types its sounds) unlock after five mastered words. + +- `lib.js` â pure content + logic: `PHONEMES` (with `stretch` for held + sounds and `home` for override sounds), `GRAPHEMES`/`BASE`/`SEQUENCE` (the + teaching order, s-a-t-p-i-n first, digraphs and vowel teams later), `TOPICS`, + `WORDS` (decodable words; `g` is the grapheme split, `art` the Fluent Emoji + asset, `t` topics, `p` overrides ambiguous sounds like book's `oo` or + robot's first `o` â override words are only offered once the child knows + the grapheme that teaches that sound), `PATRONS`, the mastery / + spaced-repetition scheduler (`recordOrder`, `pickNext`, `shouldIntroduce`), + `buildQueue` (distinct topics per patron), `blendTimeline` (no word event), + and the multi-child profile store. Unit-tested in + `tests/unit/story-stamp-*.test.js`. - `audio.js` â Web Audio player for the recorded clips, speech synthesis for - everything else, oscillator SFX. **Phonemes are never synthesised**; a - missing clip is silent. `audio.log` records every clip for the tests. -- `game.js` â screens + order flow. Exposes `window.__SS_GAME__` (`state`, - `order`, `profile`, `audio`) and `quickStart(name)` for tests; honours - `window.__SS_NOW__` (clock) and `window.__SS_FORCE_MODE__` (`'order'`). -- **Assets are generated, not hand-edited.** `python3 tools/sound-soup-assets.py` - downloads Wikimedia Commons IPA recordings and Wiktionary word - pronunciations, cuts the isolated sound out of each IPA clip (they are - recorded as "sa ⌠sa" / "ta ⌠ata"), encodes MP3s, fetches Twemoji SVGs, - and writes `assets/manifest.js` + `CREDITS.md`. Add a word to `WORDS`, rerun - the script (`--offline` reuses `.cache/`), commit the outputs. The assets - unit test checks manifest â lib â credits consistency. + everything else, oscillator SFX. **Phonemes are never synthesised** and + **never looped**: held sounds are ~1 s recordings and `playPhoneme(id, + {len})` cuts them with a fade. A missing clip is silent. `audio.log` + records every clip for the tests. +- `game.js` â screens + desk flow. Exposes `window.__SS_GAME__` (`state`, + `order`, `queue`, `profile`, `audio`) and `quickStart(name)` (lands on the + queue) for tests; honours `window.__SS_NOW__` (clock) and + `window.__SS_FORCE_MODE__` (`'return'`). +- **Assets are generated, not hand-edited.** `python3 tools/story-stamp-assets.py` + downloads Wikimedia Commons recordings and Fluent Emoji 3D PNGs and writes + `assets/manifest.js` + `CREDITS.md`. Continuants and short vowels are cut + from IPA recordings and held to ~1 s by WSOLA time-stretching of the steady + middle; stops/affricates are cut from the start of word recordings (burst + + aspiration, up to voicing); r-coloured vowels and diphthongs are whole word + recordings ("are", "or", "ow", "boy" minus the b âŚ). Add a word to `WORDS` + with a Fluent asset name, rerun the script (`--offline` reuses `.cache/`), + commit the outputs. The assets unit test checks manifest â lib â credits. +- **Audit the clips after touching the pipeline:** `python3 + tools/story-stamp-audit.py` (Vosk in `.cache/pylib`, model in + `.cache/vosk`; `--no-asr` without them). It checks acoustics per clip + (duration class, voicing, centroid, stutter = looping, level flatness), + matches each clip against sounds cut from a *different speaker's* word + recordings, runs Vosk over words assembled from the clips (about half + recognised at speech rate is normal for concatenated sounds; look for one + sound far below the rest), and writes a spectrogram sheet to + `.cache/audit/phonemes.png` to look at. - Age rules the code enforces: no timers/lives/fail state (a wrong key - re-demonstrates the sound), every instruction is spoken, six orders make a - "day" (natural stopping point), meaning is checked with a picture choice in - cook mode, progress is per-child in `localStorage` (`kg-sound-soup-v1`). + re-demonstrates the sound; after three wrong books the word is given and the + book pointed out), every instruction is spoken, six books make a "day", + meaning is checked with the book choice, progress is per-child in + `localStorage` (`kg-story-stamp-v1`). ## Mobile / touch controls @@ -144,7 +173,7 @@ unchanged â game logic never references this module. - Opt in from the HTML shell, after `game.js`: `<script>KGames.initTouchControls({ layout: 'alphabet' });</script>` - Layouts: `alphabet` (AâZ), `text` (AâZ + Space/Backspace/Enter, e.g. work), - `text-nav` (text + arrow row, e.g. sound-soup), `drive` (arrows + + `text-nav` (text + arrow row, e.g. story-stamp), `drive` (arrows + Siren/Lights/Spray, e.g. fire-truck). - Shown only on touch / coarse-pointer devices. Force with `?kbd=1` / `?kbd=0` or `window.__FORCE_TOUCH_CONTROLS__` (tests use the latter). @@ -211,7 +240,7 @@ Priority order for any new art or audio: The same order applies to audio: real recorded voice and openly licensed music and SFX beat synthesised equivalents, so use human recordings when they can be found (Wikimedia Commons has thousands of CC-licensed word pronunciations and -IPA sound recordings â see `tools/sound-soup-assets.py`). **Synthesised speech +IPA sound recordings â see `tools/story-stamp-assets.py`). **Synthesised speech is acceptable** where no recording exists or the text is dynamic (instructions, character chatter, a word with no clip): use the browser's Web Speech API, which needs no server. Where a game depends on audio accuracy diff --git a/README.md b/README.md @@ -6,7 +6,7 @@ Free games for kids. No ads. No signup. No backend. - Everything runs in the browser â HTML5, CSS, and JavaScript only - No build tools, no package managers, no server required -- Most games use [Phaser 3](https://phaser.io/) loaded from CDN; Sound Soup is plain DOM +- Most games use [Phaser 3](https://phaser.io/) loaded from CDN; Story Stamp is plain DOM - Open source under GPL-3.0 ## Run locally @@ -36,11 +36,12 @@ An internet connection is required on first load so the browser can fetch Phaser | Animal Spell | spelling with letter tiles | | Work | oral language and vocabulary | | Fire Truck | arrow keys, directions | -| Sound Soup | blending sounds into words (synthetic phonics), with spaced review | +| Story Stamp | sounding out words at the library desk (synthetic phonics), with spaced review | -Sound Soup's recorded phonemes and words come from Wikimedia Commons and its -pictures from Twemoji; see `games/sound-soup/CREDITS.md`. Regenerate its -assets with `python3 tools/sound-soup-assets.py` (needs `numpy` and `ffmpeg`). +Story Stamp's recorded phonemes and words come from Wikimedia Commons and its +pictures from Fluent Emoji; see `games/story-stamp/CREDITS.md`. Regenerate its +assets with `python3 tools/story-stamp-assets.py` (needs `numpy` and `ffmpeg`) +and check the phoneme clips with `python3 tools/story-stamp-audit.py`. ## Testing diff --git a/assets/thumbnails/sound-soup.svg b/assets/thumbnails/sound-soup.svg @@ -1,32 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 160 130" width="160" height="130"> - <!-- background rounded rect --> - <rect x="8" y="8" width="144" height="114" rx="18" fill="#FFF5F5" stroke="#2EC4B6" stroke-width="4"/> - - <!-- steam --> - <path d="M62,44 q-6,-8 0,-16 q6,-8 0,-16" stroke="#B8E0DC" stroke-width="4" fill="none" stroke-linecap="round"/> - <path d="M80,40 q-6,-8 0,-16 q6,-8 0,-16" stroke="#B8E0DC" stroke-width="4" fill="none" stroke-linecap="round"/> - <path d="M98,44 q-6,-8 0,-16 q6,-8 0,-16" stroke="#B8E0DC" stroke-width="4" fill="none" stroke-linecap="round"/> - - <!-- pot --> - <ellipse cx="80" cy="56" rx="42" ry="9" fill="#1D7CF2"/> - <path d="M40,56 v34 q0,14 14,14 h52 q14,0 14,-14 v-34 z" fill="#1D7CF2"/> - <ellipse cx="80" cy="56" rx="36" ry="6" fill="#FFD23F"/> - <!-- handles --> - <rect x="24" y="60" width="14" height="8" rx="4" fill="#E63946"/> - <rect x="122" y="60" width="14" height="8" rx="4" fill="#E63946"/> - - <!-- letter bubbles floating in the soup --> - <circle cx="60" cy="56" r="8" fill="#E63946"/> - <text x="60" y="60.5" font-family="Fredoka, sans-serif" font-size="12" font-weight="600" fill="#fff" text-anchor="middle">c</text> - <circle cx="82" cy="54" r="8" fill="#2EC4B6"/> - <text x="82" y="58.5" font-family="Fredoka, sans-serif" font-size="12" font-weight="600" fill="#fff" text-anchor="middle">a</text> - <circle cx="103" cy="57" r="8" fill="#E63946"/> - <text x="103" y="61.5" font-family="Fredoka, sans-serif" font-size="12" font-weight="600" fill="#fff" text-anchor="middle">t</text> - - <!-- spoon --> - <path d="M116,26 l-14,30" stroke="#8B5A2B" stroke-width="5" stroke-linecap="round"/> - <ellipse cx="118" cy="22" rx="7" ry="5" fill="#8B5A2B" transform="rotate(-30 118 22)"/> - - <!-- pot label "soup" --> - <text x="80" y="90" font-family="Fredoka, sans-serif" font-size="16" font-weight="600" fill="#fff" text-anchor="middle">SOUP</text> -</svg> diff --git a/assets/thumbnails/story-stamp.svg b/assets/thumbnails/story-stamp.svg @@ -0,0 +1,20 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 160 130"> + <rect x="3" y="3" width="154" height="124" rx="18" fill="#FFF5F5" stroke="#2EC4B6" stroke-width="4"/> + <!-- book --> + <rect x="34" y="34" width="70" height="78" rx="6" fill="#1D7CF2"/> + <rect x="42" y="34" width="62" height="78" rx="6" fill="#FFFFFF" stroke="#1D7CF2" stroke-width="3"/> + <!-- sound boxes on the cover --> + <g font-family="Fredoka, Arial, sans-serif" font-weight="700" font-size="17" text-anchor="middle"> + <rect x="49" y="52" width="16" height="20" rx="4" fill="#E63946"/><text x="57" y="67" fill="#fff">b</text> + <rect x="65" y="52" width="16" height="20" rx="4" fill="#FFD23F"/><text x="73" y="67" fill="#1A1A2E">u</text> + <rect x="81" y="52" width="16" height="20" rx="4" fill="#2EC4B6"/><text x="89" y="67" fill="#fff">g</text> + </g> + <!-- stamp --> + <rect x="98" y="16" width="34" height="14" rx="4" fill="#8a5227"/> + <rect x="93" y="28" width="44" height="14" rx="3" fill="#333"/> + <!-- stamp mark --> + <g transform="rotate(-14 73 94)"> + <rect x="52" y="86" width="42" height="16" rx="4" fill="none" stroke="#E63946" stroke-width="3"/> + <text x="73" y="98" font-family="Fredoka, Arial, sans-serif" font-weight="700" font-size="11" text-anchor="middle" fill="#E63946">OUT</text> + </g> +</svg> diff --git a/games/sound-soup/CREDITS.md b/games/sound-soup/CREDITS.md @@ -1,191 +0,0 @@ -# Sound Soup â asset credits - -Generated by `tools/sound-soup-assets.py`. Every third-party file used by the game is listed here with its source, author and license. - -## Pictures - -All pictures are [Twemoji](https://github.com/jdecked/twemoji) graphics, Š Twitter, Inc and other contributors, licensed [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). Files: `games/sound-soup/assets/img/*.svg` (unmodified). - -## Audio - -Recordings from [Wikimedia Commons](https://commons.wikimedia.org/). Phoneme clips are *cut from* the listed originals (the isolated sound is extracted from an "sa ⌠sa" / "ta ⌠ata" style recording); word clips are trimmed and level-matched. Modified files are `games/sound-soup/assets/audio/**/*.mp3`. - -### Dvortygirl â [CC BY-SA 3.0](http://creativecommons.org/licenses/by-sa/3.0/) - -- [En-us-ant.ogg](https://commons.wikimedia.org/wiki/File:En-us-ant.ogg) â word "ant" -- [En-us-bag.ogg](https://commons.wikimedia.org/wiki/File:En-us-bag.ogg) â word "bag" -- [En-us-bat.ogg](https://commons.wikimedia.org/wiki/File:En-us-bat.ogg) â word "bat" -- [En-us-bath.ogg](https://commons.wikimedia.org/wiki/File:En-us-bath.ogg) â word "bath" -- [En-us-bed.ogg](https://commons.wikimedia.org/wiki/File:En-us-bed.ogg) â word "bed" -- [En-us-bee.ogg](https://commons.wikimedia.org/wiki/File:En-us-bee.ogg) â word "bee" -- [En-us-bell.ogg](https://commons.wikimedia.org/wiki/File:En-us-bell.ogg) â word "bell" -- [En-us-boat.ogg](https://commons.wikimedia.org/wiki/File:En-us-boat.ogg) â word "boat" -- [En-us-book.ogg](https://commons.wikimedia.org/wiki/File:En-us-book.ogg) â word "book" -- [En-us-boot.ogg](https://commons.wikimedia.org/wiki/File:En-us-boot.ogg) â word "boot" -- [En-us-box.ogg](https://commons.wikimedia.org/wiki/File:En-us-box.ogg) â word "box" -- [En-us-brush.ogg](https://commons.wikimedia.org/wiki/File:En-us-brush.ogg) â word "brush" -- [En-us-bug.ogg](https://commons.wikimedia.org/wiki/File:En-us-bug.ogg) â word "bug" -- [En-us-bus.ogg](https://commons.wikimedia.org/wiki/File:En-us-bus.ogg) â word "bus" -- [En-us-cab.ogg](https://commons.wikimedia.org/wiki/File:En-us-cab.ogg) â word "cab" -- [En-us-can.ogg](https://commons.wikimedia.org/wiki/File:En-us-can.ogg) â word "can" -- [En-us-cap.ogg](https://commons.wikimedia.org/wiki/File:En-us-cap.ogg) â word "cap" -- [En-us-cat.ogg](https://commons.wikimedia.org/wiki/File:En-us-cat.ogg) â word "cat" -- [En-us-chick.ogg](https://commons.wikimedia.org/wiki/File:En-us-chick.ogg) â word "chick" -- [En-us-clock.ogg](https://commons.wikimedia.org/wiki/File:En-us-clock.ogg) â word "clock" -- [En-us-coat.ogg](https://commons.wikimedia.org/wiki/File:En-us-coat.ogg) â word "coat" -- [En-us-cook.ogg](https://commons.wikimedia.org/wiki/File:En-us-cook.ogg) â word "cook" -- [En-us-crab.ogg](https://commons.wikimedia.org/wiki/File:En-us-crab.ogg) â word "crab" -- [En-us-cup.ogg](https://commons.wikimedia.org/wiki/File:En-us-cup.ogg) â word "cup" -- [En-us-cut.ogg](https://commons.wikimedia.org/wiki/File:En-us-cut.ogg) â word "cut" -- [En-us-dad.ogg](https://commons.wikimedia.org/wiki/File:En-us-dad.ogg) â word "dad" -- [En-us-dish.ogg](https://commons.wikimedia.org/wiki/File:En-us-dish.ogg) â word "dish" -- [En-us-dog.ogg](https://commons.wikimedia.org/wiki/File:En-us-dog.ogg) â word "dog" -- [En-us-dress.ogg](https://commons.wikimedia.org/wiki/File:En-us-dress.ogg) â word "dress" -- [En-us-drum.ogg](https://commons.wikimedia.org/wiki/File:En-us-drum.ogg) â word "drum" -- [En-us-duck.ogg](https://commons.wikimedia.org/wiki/File:En-us-duck.ogg) â word "duck" -- [En-us-egg.ogg](https://commons.wikimedia.org/wiki/File:En-us-egg.ogg) â word "egg" -- [En-us-feet.ogg](https://commons.wikimedia.org/wiki/File:En-us-feet.ogg) â word "feet" -- [En-us-fox.ogg](https://commons.wikimedia.org/wiki/File:En-us-fox.ogg) â word "fox" -- [En-us-frog.ogg](https://commons.wikimedia.org/wiki/File:En-us-frog.ogg) â word "frog" -- [En-us-gem.ogg](https://commons.wikimedia.org/wiki/File:En-us-gem.ogg) â word "gem" -- [En-us-gift.ogg](https://commons.wikimedia.org/wiki/File:En-us-gift.ogg) â word "gift" -- [En-us-goat.ogg](https://commons.wikimedia.org/wiki/File:En-us-goat.ogg) â word "goat" -- [En-us-green.ogg](https://commons.wikimedia.org/wiki/File:En-us-green.ogg) â word "green" -- [En-us-ham.ogg](https://commons.wikimedia.org/wiki/File:En-us-ham.ogg) â word "ham" -- [En-us-hand.ogg](https://commons.wikimedia.org/wiki/File:En-us-hand.ogg) â word "hand" -- [En-us-hat.ogg](https://commons.wikimedia.org/wiki/File:En-us-hat.ogg) â word "hat" -- [En-us-hen.ogg](https://commons.wikimedia.org/wiki/File:En-us-hen.ogg) â word "hen" -- [En-us-hill.ogg](https://commons.wikimedia.org/wiki/File:En-us-hill.ogg) â word "hill" -- [En-us-hook.ogg](https://commons.wikimedia.org/wiki/File:En-us-hook.ogg) â word "hook" -- [En-us-hot.ogg](https://commons.wikimedia.org/wiki/File:En-us-hot.ogg) â word "hot" -- [En-us-hug.ogg](https://commons.wikimedia.org/wiki/File:En-us-hug.ogg) â word "hug" -- [En-us-hut.ogg](https://commons.wikimedia.org/wiki/File:En-us-hut.ogg) â word "hut" -- [En-us-jam.ogg](https://commons.wikimedia.org/wiki/File:En-us-jam.ogg) â word "jam" -- [En-us-kid.ogg](https://commons.wikimedia.org/wiki/File:En-us-kid.ogg) â word "kid" -- [En-us-king.ogg](https://commons.wikimedia.org/wiki/File:En-us-king.ogg) â word "king" -- [En-us-leg.ogg](https://commons.wikimedia.org/wiki/File:En-us-leg.ogg) â word "leg" -- [En-us-lip.ogg](https://commons.wikimedia.org/wiki/File:En-us-lip.ogg) â word "lip" -- [En-us-lock.ogg](https://commons.wikimedia.org/wiki/File:En-us-lock.ogg) â word "lock" -- [En-us-log.ogg](https://commons.wikimedia.org/wiki/File:En-us-log.ogg) â word "log" -- [En-us-lunch.ogg](https://commons.wikimedia.org/wiki/File:En-us-lunch.ogg) â word "lunch" -- [En-us-mad.ogg](https://commons.wikimedia.org/wiki/File:En-us-mad.ogg) â word "mad" -- [En-us-map.ogg](https://commons.wikimedia.org/wiki/File:En-us-map.ogg) â word "map" -- [En-us-milk.ogg](https://commons.wikimedia.org/wiki/File:En-us-milk.ogg) â word "milk" -- [En-us-mom.ogg](https://commons.wikimedia.org/wiki/File:En-us-mom.ogg) â word "mom" -- [En-us-moon.ogg](https://commons.wikimedia.org/wiki/File:En-us-moon.ogg) â word "moon" -- [En-us-mug.ogg](https://commons.wikimedia.org/wiki/File:En-us-mug.ogg) â word "mug" -- [En-us-nest.ogg](https://commons.wikimedia.org/wiki/File:En-us-nest.ogg) â word "nest" -- [En-us-net.ogg](https://commons.wikimedia.org/wiki/File:En-us-net.ogg) â word "net" -- [En-us-nut.ogg](https://commons.wikimedia.org/wiki/File:En-us-nut.ogg) â word "nut" -- [En-us-pan.ogg](https://commons.wikimedia.org/wiki/File:En-us-pan.ogg) â word "pan" -- [En-us-pants.ogg](https://commons.wikimedia.org/wiki/File:En-us-pants.ogg) â word "pants" -- [En-us-pen.ogg](https://commons.wikimedia.org/wiki/File:En-us-pen.ogg) â word "pen" -- [En-us-pig.ogg](https://commons.wikimedia.org/wiki/File:En-us-pig.ogg) â word "pig" -- [En-us-plant.ogg](https://commons.wikimedia.org/wiki/File:En-us-plant.ogg) â word "plant" -- [En-us-plug.ogg](https://commons.wikimedia.org/wiki/File:En-us-plug.ogg) â word "plug" -- [En-us-pot.ogg](https://commons.wikimedia.org/wiki/File:En-us-pot.ogg) â word "pot" -- [En-us-ram.ogg](https://commons.wikimedia.org/wiki/File:En-us-ram.ogg) â word "ram" -- [En-us-rat.ogg](https://commons.wikimedia.org/wiki/File:En-us-rat.ogg) â word "rat" -- [En-us-red.ogg](https://commons.wikimedia.org/wiki/File:En-us-red.ogg) â word "red" -- [En-us-ring.ogg](https://commons.wikimedia.org/wiki/File:En-us-ring.ogg) â word "ring" -- [En-us-road.ogg](https://commons.wikimedia.org/wiki/File:En-us-road.ogg) â word "road" -- [En-us-rock.ogg](https://commons.wikimedia.org/wiki/File:En-us-rock.ogg) â word "rock" -- [En-us-run.ogg](https://commons.wikimedia.org/wiki/File:En-us-run.ogg) â word "run" -- [En-us-sad.ogg](https://commons.wikimedia.org/wiki/File:En-us-sad.ogg) â word "sad" -- [En-us-seed.ogg](https://commons.wikimedia.org/wiki/File:En-us-seed.ogg) â word "seed" -- [En-us-sheep.ogg](https://commons.wikimedia.org/wiki/File:En-us-sheep.ogg) â word "sheep" -- [En-us-shell.ogg](https://commons.wikimedia.org/wiki/File:En-us-shell.ogg) â word "shell" -- [En-us-ship.ogg](https://commons.wikimedia.org/wiki/File:En-us-ship.ogg) â word "ship" -- [En-us-shop.ogg](https://commons.wikimedia.org/wiki/File:En-us-shop.ogg) â word "shop" -- [En-us-sock.ogg](https://commons.wikimedia.org/wiki/File:En-us-sock.ogg) â word "sock" -- [En-us-spoon.ogg](https://commons.wikimedia.org/wiki/File:En-us-spoon.ogg) â word "spoon" -- [En-us-sun.ogg](https://commons.wikimedia.org/wiki/File:En-us-sun.ogg) â word "sun" -- [En-us-swim.ogg](https://commons.wikimedia.org/wiki/File:En-us-swim.ogg) â word "swim" -- [En-us-teeth.ogg](https://commons.wikimedia.org/wiki/File:En-us-teeth.ogg) â word "teeth" -- [En-us-tent.ogg](https://commons.wikimedia.org/wiki/File:En-us-tent.ogg) â word "tent" -- [En-us-tree.ogg](https://commons.wikimedia.org/wiki/File:En-us-tree.ogg) â word "tree" -- [En-us-truck.ogg](https://commons.wikimedia.org/wiki/File:En-us-truck.ogg) â word "truck" -- [En-us-van.ogg](https://commons.wikimedia.org/wiki/File:En-us-van.ogg) â word "van" -- [En-us-web.ogg](https://commons.wikimedia.org/wiki/File:En-us-web.ogg) â word "web" -- [En-us-zap.ogg](https://commons.wikimedia.org/wiki/File:En-us-zap.ogg) â word "zap" - -### Karmosin~commonswiki â [CC BY-SA 3.0](http://creativecommons.org/licenses/by-sa/3.0/) - -- [Alveolar_lateral_approximant.ogg](https://commons.wikimedia.org/wiki/File:Alveolar_lateral_approximant.ogg) â phoneme /l/ â cut 0.21â0.50s of the original -- [Bilabial_nasal.ogg](https://commons.wikimedia.org/wiki/File:Bilabial_nasal.ogg) â phoneme /m/ â cut 0.20â0.38s of the original -- [Palatal_approximant.ogg](https://commons.wikimedia.org/wiki/File:Palatal_approximant.ogg) â phoneme /y/ â cut 0.24â0.43s of the original -- [Velar_nasal.ogg](https://commons.wikimedia.org/wiki/File:Velar_nasal.ogg) â phoneme /ng/ â cut 0.19â0.33s of the original -- [Voiced_alveolar_plosive.ogg](https://commons.wikimedia.org/wiki/File:Voiced_alveolar_plosive.ogg) â phoneme /d/ â cut 0.22â0.28s of the original -- [Voiced_alveolar_sibilant.ogg](https://commons.wikimedia.org/wiki/File:Voiced_alveolar_sibilant.ogg) â phoneme /z/ â cut 0.21â0.43s of the original -- [Voiced_bilabial_plosive.ogg](https://commons.wikimedia.org/wiki/File:Voiced_bilabial_plosive.ogg) â phoneme /b/ â cut 0.22â0.28s of the original -- [Voiced_labio-velar_approximant.ogg](https://commons.wikimedia.org/wiki/File:Voiced_labio-velar_approximant.ogg) â phoneme /w/ â cut 0.02â0.23s of the original -- [Voiced_labiodental_fricative.ogg](https://commons.wikimedia.org/wiki/File:Voiced_labiodental_fricative.ogg) â phoneme /v/ â cut 0.28â0.46s of the original -- [Voiced_velar_plosive.ogg](https://commons.wikimedia.org/wiki/File:Voiced_velar_plosive.ogg) â phoneme /g/ â cut 0.22â0.28s of the original -- [Voiceless_alveolar_plosive.ogg](https://commons.wikimedia.org/wiki/File:Voiceless_alveolar_plosive.ogg) â phoneme /t/ â cut 0.23â0.29s of the original -- [Voiceless_glottal_fricative.ogg](https://commons.wikimedia.org/wiki/File:Voiceless_glottal_fricative.ogg) â phoneme /h/ â cut 0.22â0.48s of the original -- [Voiceless_labiodental_fricative.ogg](https://commons.wikimedia.org/wiki/File:Voiceless_labiodental_fricative.ogg) â phoneme /f/ â cut 0.16â0.52s of the original -- [Voiceless_velar_plosive.ogg](https://commons.wikimedia.org/wiki/File:Voiceless_velar_plosive.ogg) â phoneme /k/ â cut 0.24â0.30s of the original - -### Denelson83 â [CC BY-SA 3.0](http://creativecommons.org/licenses/by-sa/3.0/) - -- [Close-mid_back_rounded_vowel.ogg](https://commons.wikimedia.org/wiki/File:Close-mid_back_rounded_vowel.ogg) â phoneme /oa/ â cut 0.02â0.47s of the original -- [Close_back_rounded_vowel.ogg](https://commons.wikimedia.org/wiki/File:Close_back_rounded_vowel.ogg) â phoneme /oo/ â cut 0.01â0.46s of the original -- [Close_front_unrounded_vowel.ogg](https://commons.wikimedia.org/wiki/File:Close_front_unrounded_vowel.ogg) â phoneme /ee/ â cut 0.02â0.47s of the original -- [Near-close_near-back_rounded_vowel.ogg](https://commons.wikimedia.org/wiki/File:Near-close_near-back_rounded_vowel.ogg) â phoneme /uu/ â cut 0.01â0.46s of the original -- [Near-close_near-front_unrounded_vowel.ogg](https://commons.wikimedia.org/wiki/File:Near-close_near-front_unrounded_vowel.ogg) â phoneme /i/ â cut 0.02â0.47s of the original -- [Near-open_front_unrounded_vowel.ogg](https://commons.wikimedia.org/wiki/File:Near-open_front_unrounded_vowel.ogg) â phoneme /a/ â cut 0.03â0.48s of the original -- [Open-mid_back_unrounded_vowel.ogg](https://commons.wikimedia.org/wiki/File:Open-mid_back_unrounded_vowel.ogg) â phoneme /u/ â cut 0.03â0.48s of the original -- [Open-mid_front_unrounded_vowel.ogg](https://commons.wikimedia.org/wiki/File:Open-mid_front_unrounded_vowel.ogg) â phoneme /e/ â cut 0.03â0.48s of the original -- [Open_back_unrounded_vowel.ogg](https://commons.wikimedia.org/wiki/File:Open_back_unrounded_vowel.ogg) â phoneme /o/ â cut 0.01â0.46s of the original - -### Dvortygirl â Public domain - -- [En-us-brick.ogg](https://commons.wikimedia.org/wiki/File:En-us-brick.ogg) â word "brick" -- [En-us-fish.ogg](https://commons.wikimedia.org/wiki/File:En-us-fish.ogg) â word "fish" -- [En-us-flag.ogg](https://commons.wikimedia.org/wiki/File:En-us-flag.ogg) â word "flag" -- [En-us-tap.ogg](https://commons.wikimedia.org/wiki/File:En-us-tap.ogg) â word "tap" - -### Peter Isotalo â [CC BY-SA 3.0](http://creativecommons.org/licenses/by-sa/3.0/) - -- [Alveolar_nasal.ogg](https://commons.wikimedia.org/wiki/File:Alveolar_nasal.ogg) â phoneme /n/ â cut 0.25â0.50s of the original -- [Voiceless_alveolar_sibilant.ogg](https://commons.wikimedia.org/wiki/File:Voiceless_alveolar_sibilant.ogg) â phoneme /s/ â cut 0.24â0.45s of the original -- [Voiceless_dental_fricative.ogg](https://commons.wikimedia.org/wiki/File:Voiceless_dental_fricative.ogg) â phoneme /th/ â cut 0.08â0.29s of the original -- [Voiceless_palato-alveolar_sibilant.ogg](https://commons.wikimedia.org/wiki/File:Voiceless_palato-alveolar_sibilant.ogg) â phoneme /sh/ â cut 0.18â0.45s of the original - -### Muke â Public domain - -- [En-us-bin.ogg](https://commons.wikimedia.org/wiki/File:En-us-bin.ogg) â word "bin" -- [En-us-pin.ogg](https://commons.wikimedia.org/wiki/File:En-us-pin.ogg) â word "pin" - -### TheDaveRoss â [CC BY-SA 3.0](http://creativecommons.org/licenses/by-sa/3.0/) - -- [En-us-six.ogg](https://commons.wikimedia.org/wiki/File:En-us-six.ogg) â word "six" -- [En-us-ten.ogg](https://commons.wikimedia.org/wiki/File:En-us-ten.ogg) â word "ten" - -### Erutuon â [CC BY-SA 3.0](http://creativecommons.org/licenses/by-sa/3.0/) - -- [Alveolar_approximant.ogg](https://commons.wikimedia.org/wiki/File:Alveolar_approximant.ogg) â phoneme /r/ â cut 0.29â0.49s of the original - -### Joni~commonswiki â [CC BY-SA 3.0](http://creativecommons.org/licenses/by-sa/3.0/) - -- [Voiceless_bilabial_plosive.ogg](https://commons.wikimedia.org/wiki/File:Voiceless_bilabial_plosive.ogg) â phoneme /p/ â cut 0.01â0.07s of the original - -### Peter Isotalo â [CC BY-SA 3.0](https://creativecommons.org/licenses/by-sa/3.0) - -- [Voiceless_palato-alveolar_affricate.ogg](https://commons.wikimedia.org/wiki/File:Voiceless_palato-alveolar_affricate.ogg) â phoneme /ch/ â cut 0.22â0.35s of the original - -### Sylvanmoon â [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0) - -- [En-us-yam.ogg](https://commons.wikimedia.org/wiki/File:En-us-yam.ogg) â word "yam" - -### The original uploader was Octane at English Wikipedia. â Public domain - -- [Voiced_postalveolar_affricate.ogg](https://commons.wikimedia.org/wiki/File:Voiced_postalveolar_affricate.ogg) â phoneme /j/ â cut 0.09â0.22s of the original - -### user:Dvortygirl â [CC BY-SA 3.0](http://creativecommons.org/licenses/by-sa/3.0/) - -- [En-us-soap.ogg](https://commons.wikimedia.org/wiki/File:En-us-soap.ogg) â word "soap" - -## Speech - -Instructions, customer chatter, and any word without a recording are voiced by the browser's built-in speech synthesis (Web Speech API); nothing is sent to a server. diff --git a/games/sound-soup/assets/audio/phonemes/a.mp3 b/games/sound-soup/assets/audio/phonemes/a.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/phonemes/b.mp3 b/games/sound-soup/assets/audio/phonemes/b.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/phonemes/ch.mp3 b/games/sound-soup/assets/audio/phonemes/ch.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/phonemes/d.mp3 b/games/sound-soup/assets/audio/phonemes/d.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/phonemes/e.mp3 b/games/sound-soup/assets/audio/phonemes/e.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/phonemes/ee.mp3 b/games/sound-soup/assets/audio/phonemes/ee.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/phonemes/f.mp3 b/games/sound-soup/assets/audio/phonemes/f.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/phonemes/g.mp3 b/games/sound-soup/assets/audio/phonemes/g.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/phonemes/h.mp3 b/games/sound-soup/assets/audio/phonemes/h.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/phonemes/i.mp3 b/games/sound-soup/assets/audio/phonemes/i.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/phonemes/j.mp3 b/games/sound-soup/assets/audio/phonemes/j.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/phonemes/k.mp3 b/games/sound-soup/assets/audio/phonemes/k.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/phonemes/l.mp3 b/games/sound-soup/assets/audio/phonemes/l.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/phonemes/m.mp3 b/games/sound-soup/assets/audio/phonemes/m.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/phonemes/n.mp3 b/games/sound-soup/assets/audio/phonemes/n.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/phonemes/ng.mp3 b/games/sound-soup/assets/audio/phonemes/ng.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/phonemes/o.mp3 b/games/sound-soup/assets/audio/phonemes/o.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/phonemes/oa.mp3 b/games/sound-soup/assets/audio/phonemes/oa.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/phonemes/oo.mp3 b/games/sound-soup/assets/audio/phonemes/oo.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/phonemes/p.mp3 b/games/sound-soup/assets/audio/phonemes/p.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/phonemes/r.mp3 b/games/sound-soup/assets/audio/phonemes/r.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/phonemes/s.mp3 b/games/sound-soup/assets/audio/phonemes/s.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/phonemes/sh.mp3 b/games/sound-soup/assets/audio/phonemes/sh.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/phonemes/t.mp3 b/games/sound-soup/assets/audio/phonemes/t.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/phonemes/th.mp3 b/games/sound-soup/assets/audio/phonemes/th.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/phonemes/u.mp3 b/games/sound-soup/assets/audio/phonemes/u.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/phonemes/uu.mp3 b/games/sound-soup/assets/audio/phonemes/uu.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/phonemes/v.mp3 b/games/sound-soup/assets/audio/phonemes/v.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/phonemes/w.mp3 b/games/sound-soup/assets/audio/phonemes/w.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/phonemes/y.mp3 b/games/sound-soup/assets/audio/phonemes/y.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/phonemes/z.mp3 b/games/sound-soup/assets/audio/phonemes/z.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/bee.mp3 b/games/sound-soup/assets/audio/words/bee.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/bin.mp3 b/games/sound-soup/assets/audio/words/bin.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/book.mp3 b/games/sound-soup/assets/audio/words/book.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/box.mp3 b/games/sound-soup/assets/audio/words/box.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/cup.mp3 b/games/sound-soup/assets/audio/words/cup.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/green.mp3 b/games/sound-soup/assets/audio/words/green.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/ham.mp3 b/games/sound-soup/assets/audio/words/ham.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/hand.mp3 b/games/sound-soup/assets/audio/words/hand.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/hill.mp3 b/games/sound-soup/assets/audio/words/hill.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/leg.mp3 b/games/sound-soup/assets/audio/words/leg.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/lip.mp3 b/games/sound-soup/assets/audio/words/lip.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/pin.mp3 b/games/sound-soup/assets/audio/words/pin.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/plant.mp3 b/games/sound-soup/assets/audio/words/plant.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/red.mp3 b/games/sound-soup/assets/audio/words/red.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/ring.mp3 b/games/sound-soup/assets/audio/words/ring.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/road.mp3 b/games/sound-soup/assets/audio/words/road.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/rock.mp3 b/games/sound-soup/assets/audio/words/rock.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/seed.mp3 b/games/sound-soup/assets/audio/words/seed.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/shell.mp3 b/games/sound-soup/assets/audio/words/shell.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/ship.mp3 b/games/sound-soup/assets/audio/words/ship.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/shop.mp3 b/games/sound-soup/assets/audio/words/shop.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/sun.mp3 b/games/sound-soup/assets/audio/words/sun.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/teeth.mp3 b/games/sound-soup/assets/audio/words/teeth.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/yam.mp3 b/games/sound-soup/assets/audio/words/yam.mp3 Binary files differ. diff --git a/games/sound-soup/assets/img/1f30a.svg b/games/sound-soup/assets/img/1f30a.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#269" d="M33.398 23.678c-7.562 4.875-20.062-.438-18.375-8.062 1.479-6.684 9.419-4.763 11.225-3.861 1.806.902.713-3.889-3.475-5.327C17.1 4.48 10.156 4.893 7.961 14.678c-1.5 6.687 1.438 16.062 12.719 16.187 11.281.125 12.718-7.187 12.718-7.187z"/><path fill="#55ACEE" d="M35.988 25.193c0-2.146-2.754-2.334-4-1.119-2.994 2.919-7.402 4.012-13.298 2.861-10.25-2-10.341-14.014-3.333-17.441 3.791-1.854 8.289.341 9.999 1.655 1.488 1.143 4.334 2.66 4.185.752C29.223 7.839 21.262-.86 10.595 4.64-.071 10.14 0 22.553 0 24.803v7.25C0 34.262 1.814 36 4.023 36h28C34.232 36 36 34.262 36 32.053c0 0-.004-6.854-.012-6.86z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f319.svg b/games/sound-soup/assets/img/1f319.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#FFD983" d="M30.312.776C32 19 20 32 .776 30.312c8.199 7.717 21.091 7.588 29.107-.429C37.9 21.867 38.03 8.975 30.312.776z"/><path d="M30.705 15.915c-.453.454-.453 1.189 0 1.644.454.453 1.189.453 1.643 0 .454-.455.455-1.19 0-1.644-.453-.454-1.189-.454-1.643 0zm-16.022 14.38c-.682.681-.682 1.783 0 2.465.68.682 1.784.682 2.464 0 .681-.682.681-1.784 0-2.465-.68-.682-1.784-.682-2.464 0zm13.968-2.147c-1.135 1.135-2.974 1.135-4.108 0-1.135-1.135-1.135-2.975 0-4.107 1.135-1.136 2.974-1.136 4.108 0 1.135 1.133 1.135 2.973 0 4.107z" fill="#FFCC4D"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f31f.svg b/games/sound-soup/assets/img/1f31f.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#FFAC33" d="M28.84 17.638c-.987 1.044-1.633 3.067-1.438 4.493l.892 6.441c.197 1.427-.701 2.087-1.996 1.469l-5.851-2.796c-1.295-.62-3.408-.611-4.7.018l-5.826 2.842c-1.291.629-2.193-.026-2.007-1.452l.843-6.449c.186-1.427-.475-3.444-1.47-4.481l-4.494-4.688c-.996-1.037-.655-2.102.755-2.365l6.37-1.188c1.41-.263 3.116-1.518 3.793-2.789L16.762.956c.675-1.271 1.789-1.274 2.473-.009L22.33 6.66c.686 1.265 2.4 2.507 3.814 2.758l6.378 1.141c1.412.252 1.761 1.314.774 2.359l-4.456 4.72z"/><path fill="#FFD983" d="M9.783 2.181c1.023 1.413 2.446 4.917 1.717 5.447-.728.531-3.607-1.91-4.63-3.323-1.022-1.413-.935-2.668-.131-3.254.804-.587 2.02-.282 3.044 1.13zm19.348 2.124C28.109 5.718 25.23 8.16 24.5 7.627c-.729-.53.695-4.033 1.719-5.445C27.242.768 28.457.463 29.262 1.051c.803.586.89 1.841-.131 3.254zM16.625 33.291c-.001-1.746.898-5.421 1.801-5.421.897 0 1.798 3.675 1.797 5.42 0 1.747-.804 2.712-1.8 2.71-.994.002-1.798-.962-1.798-2.709zm16.179-9.262c-1.655-.539-4.858-2.533-4.579-3.395.277-.858 4.037-.581 5.69-.041 1.655.54 2.321 1.605 2.013 2.556-.308.95-1.469 1.42-3.124.88zM2.083 20.594c1.655-.54 5.414-.817 5.694.044.276.857-2.928 2.854-4.581 3.392-1.654.54-2.818.07-3.123-.88-.308-.95.354-2.015 2.01-2.556z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f320.svg b/games/sound-soup/assets/img/1f320.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#269" d="M36 32c0 2.209-1.791 4-4 4H4c-2.209 0-4-1.791-4-4V4c0-2.209 1.791-4 4-4h28c2.209 0 4 1.791 4 4v28z"/><path fill="#F4900C" d="M11 9s7.29-4.557 21-4.969c.652-.02 2-.031 2 .969-6 9-9 23-9 23L11 9z"/><path fill="#FFAC33" d="M12 16S24 5 32 4.031C32.647 3.952 34 4 34 5c-7 4-12 19-12 19l-10-8z"/><path fill="#FFD983" d="M15.156 12.438c.826.727 2.388 1.164 3.471.972l4.892-.866c1.084-.192 1.613.478 1.178 1.488l-1.968 4.563c-.436 1.01-.369 2.63.148 3.602l2.335 4.384c.518.972.044 1.682-1.051 1.58l-4.947-.463c-1.095-.102-2.616.462-3.379 1.254l-3.45 3.577c-.763.792-1.585.562-1.827-.512L9.469 27.17c-.241-1.073-1.248-2.345-2.237-2.827l-4.467-2.175c-.989-.481-1.024-1.335-.078-1.896l4.274-2.534c.946-.561 1.845-1.911 1.997-3.001l.689-4.92c.152-1.09.953-1.387 1.779-.66l3.73 3.281z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f331.svg b/games/sound-soup/assets/img/1f331.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#77B255" d="M22.911 14.398c-1.082.719-2.047 1.559-2.88 2.422-.127-4.245-1.147-9.735-6.772-12.423C12.146-1.658-.833 1.418.328 2.006c2.314 1.17 3.545 4.148 5.034 5.715 2.653 2.792 5.603 2.964 7.071.778 3.468 2.254 3.696 6.529 3.59 11.099-.012.505-.023.975-.023 1.402v14c0 1.104 4 1.104 4 0V23.51c.542-.954 2.122-3.505 4.43-5.294 1.586 1.393 4.142.948 6.463-1.495 1.489-1.567 2.293-4.544 4.607-5.715 1.221-.618-12.801-3.994-12.589 3.392z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f333.svg b/games/sound-soup/assets/img/1f333.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#662113" d="M22 33c0 2.209-1.791 3-4 3s-4-.791-4-3l1-9c0-2.209.791-2 3-2s3-.209 3 2l1 9z"/><path fill="#5C913B" d="M34 17c0 8.837-7.163 12-16 12-8.836 0-16-3.163-16-12C2 8.164 11 0 18 0s16 8.164 16 17z"/><g fill="#3E721D"><ellipse cx="6" cy="21" rx="2" ry="1"/><ellipse cx="30" cy="21" rx="2" ry="1"/><ellipse cx="10" cy="25" rx="2" ry="1"/><ellipse cx="14" cy="22" rx="2" ry="1"/><ellipse cx="10" cy="16" rx="2" ry="1"/><ellipse cx="7" cy="12" rx="2" ry="1"/><ellipse cx="29" cy="12" rx="2" ry="1"/><ellipse cx="14" cy="10" rx="2" ry="1"/><ellipse cx="22" cy="10" rx="2" ry="1"/><ellipse cx="26" cy="16" rx="2" ry="1"/><ellipse cx="18" cy="17" rx="2" ry="1"/><ellipse cx="22" cy="22" rx="2" ry="1"/><ellipse cx="18" cy="26" rx="2" ry="1"/><ellipse cx="26" cy="25" rx="2" ry="1"/></g></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f334.svg b/games/sound-soup/assets/img/1f334.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#C1694F" d="M21.978 20.424c-.054-.804-.137-1.582-.247-2.325-.133-.89-.299-1.728-.485-2.513-.171-.723-.356-1.397-.548-2.017-.288-.931-.584-1.738-.852-2.4-.527-1.299-.943-2.043-.943-2.043l-3.613.466s.417.87.868 2.575c.183.692.371 1.524.54 2.495.086.49.166 1.012.238 1.573.1.781.183 1.632.242 2.549.034.518.058 1.058.074 1.619.006.204.015.401.018.611.01.656-.036 1.323-.118 1.989-.074.6-.182 1.197-.311 1.789-.185.848-.413 1.681-.67 2.475-.208.643-.431 1.261-.655 1.84-.344.891-.69 1.692-.989 2.359-.502 1.119-.871 1.863-.871 2.018 0 .49.35 1.408 2.797 2.02 3.827.956 4.196-.621 4.196-.621s.243-.738.526-2.192c.14-.718.289-1.605.424-2.678.081-.642.156-1.348.222-2.116.068-.8.125-1.667.165-2.605.03-.71.047-1.47.055-2.259.002-.246.008-.484.008-.737 0-.64-.03-1.261-.071-1.872z"/><path fill="#D99E82" d="M18.306 30.068c-1.403-.244-2.298-.653-2.789-.959-.344.891-.69 1.692-.989 2.359.916.499 2.079.895 3.341 1.114.729.127 1.452.191 2.131.191.414 0 .803-.033 1.176-.08.14-.718.289-1.605.424-2.678-.444.157-1.548.357-3.294.053zm1.06-4.673c-1.093-.108-1.934-.348-2.525-.602-.185.848-.413 1.681-.67 2.475.864.326 1.881.561 2.945.666.429.042.855.064 1.27.064.502 0 .978-.039 1.435-.099.068-.8.125-1.667.165-2.605-.628.135-1.509.21-2.62.101zm.309-2.133c.822 0 1.63-.083 2.366-.228.002-.246.008-.484.008-.737 0-.641-.029-1.262-.071-1.873-.529.138-1.285.272-2.352.286-1.084-.005-1.847-.155-2.374-.306.006.204.015.401.018.611.01.656-.036 1.323-.118 1.989.763.161 1.605.253 2.461.257l.062.001zm-.249-4.577c.825-.119 1.59-.333 2.304-.585-.133-.89-.299-1.728-.485-2.513-.496.204-1.199.431-2.181.572-.91.132-1.605.124-2.129.077.1.781.183 1.632.242 2.549.152.006.29.029.446.029.588.001 1.2-.043 1.803-.129zm1.271-5.116c-.288-.931-.584-1.738-.852-2.4-.443.222-1.004.456-1.737.659-.795.221-1.437.309-1.951.339.183.692.371 1.524.54 2.495.681-.068 1.383-.179 2.094-.376.679-.188 1.31-.44 1.906-.717z"/><path fill="#3E721D" d="M32.61 4.305c-.044-.061-4.48-5.994-10.234-3.39-2.581 1.167-4.247 3.074-4.851 5.535-1.125-1.568-2.835-2.565-5.093-2.968C6.233 2.376 2.507 9.25 2.47 9.32c-.054.102-.031.229.056.305s.217.081.311.015c.028-.02 2.846-1.993 7.543-1.157 4.801.854 8.167 1.694 8.201 1.702.02.005.041.007.061.007.069 0 .136-.028.184-.08.032-.035 3.22-3.46 6.153-4.787 4.339-1.961 7.298-.659 7.326-.646.104.046.227.018.298-.07.072-.087.075-.213.007-.304z"/><path fill="#5C913B" d="M27.884 7.63c-4.405-2.328-7.849-1.193-9.995.22-2.575-.487-7.334-.459-11.364 4.707-4.983 6.387-.618 14.342-.573 14.422.067.119.193.191.327.191.015 0 .031-.001.046-.003.151-.019.276-.127.316-.274.015-.054 1.527-5.52 5.35-10.118 2.074-2.496 4.55-4.806 6.308-6.34 1.762.298 4.327.947 6.846 2.354 4.958 2.773 7.234 7.466 7.257 7.513.068.144.211.226.379.212.158-.018.289-.133.325-.287.02-.088 1.968-8.8-5.222-12.597z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f33a.svg b/games/sound-soup/assets/img/1f33a.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#77B255" d="M19.602 32.329c6.509 6.506 17.254-7.669 15.72-7.669-7.669 0-22.227 1.161-15.72 7.669z"/><path fill="#77B255" d="M15.644 33.372C9.612 39.404-.07 26.263 1.352 26.263c3.81 0 9.374-.348 12.79.867 2.958 1.052 4.304 3.442 1.502 6.242z"/><path fill="#F4ABBA" d="M34.613 15.754c-.052-.901-.175-2.585-1.398-4.227-1.16-1.549-3.805-3.371-5.534-2.585.516-1.676-.264-4.125-1.191-5.49-1.179-1.736-4.262-3.843-8.146-3.026-1.754.369-4.18 2.036-4.632 3.864-1.18-1.471-4.22-1.675-6.015-1.222-2.026.511-3.154 1.777-3.739 2.461l.003-.005-.03.034-.027.033c-.583.689-1.656 1.994-1.847 4.074-.193 2.146.75 5.832 3.026 6.042.149.014.324.031.514.051-2.271.098-3.572 3.654-3.595 5.8-.022 2.102.926 3.506 1.443 4.243l-.003-.004c.008.01.019.024.025.036.007.011.02.023.026.036.523.733 1.525 2.094 3.515 2.776 1.958.669 5.553.656 6.567-1.236-.273 2.244 3.027 4.077 5.169 4.438 2.115.358 3.71-.358 4.55-.753l-.005.003c.013-.008.028-.015.041-.021l.041-.02c.838-.4 2.398-1.178 3.462-3.04.729-1.282 1.27-3.403.951-5.015l.192.127c1.826 1.224 4.63-1.119 5.705-2.938 1.044-1.761.932-4.424.932-4.436z"/><path fill="#EA596E" d="M27.542 13.542c-1.786-.997-4.874-.434-6.792.308-.266-.468-.621-.875-1.051-1.196 1.393-1.607 3.526-4.593 1.468-6.362-2.191-1.883-3.74 2.154-3.575 5.605-.068-.003-.132-.02-.201-.02-1.019 0-1.94.402-2.632 1.045-1.401-2.277-3.942-4.244-5.314-2.392-1.482 2.002 1.148 3.153 4.222 4.2-.09.329-.154.668-.154 1.025 0 .456.093.887.238 1.293-2.541.732-6.236 2.718-4.21 4.91 2.122 2.296 4.472-1.238 5.604-3.053.635.454 1.407.727 2.247.727.225 0 .441-.029.655-.066-.109 4.802 1.443 7.07 4.036 5.892 2.295-1.043-.137-5.299-1.781-7.165.316-.362.564-.779.729-1.241 7.008 2.544 8.589-2.351 6.511-3.51z"/><path fill="#BE1931" d="M17.707 17.459c-.679 0-.668-.562-.832-1.25-.532-2.233-2.381-6.308-4.601-9.163-.509-.654-.391-1.596.263-2.105.654-.508 1.596-.391 2.105.263 2.439 3.136 3.264 7.404 3.982 10.421.191.806.237 1.601-.569 1.792-.116.028-.233.042-.348.042z"/><path fill="#FFCC4D" d="M15.904 5.327c.498.684.079 1.838-.936 2.578l-.475.347c-1.016.739-2.243.785-2.741.101l-2.78-3.817c-.498-.684-.079-1.838.936-2.577l.475-.347c1.015-.739 2.242-.785 2.74-.101l2.781 3.816z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f33b.svg b/games/sound-soup/assets/img/1f33b.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#3E721D" d="M28 27c-8 0-8 6-8 6V22h-4v11s0-6-8-6c-4 0-7-2-7-2s0 9 9 9h6s0 2 2 2 2-2 2-2h6c9 0 9-9 9-9s-3 2-7 2z"/><path fill="#FFAC33" d="M21.125 27.662c-.328 0-.651-.097-.927-.283l-2.323-1.575-2.322 1.575c-.277.186-.601.283-.929.283-.143 0-.287-.018-.429-.057-.462-.123-.851-.441-1.06-.874l-1.225-2.527-2.797.204c-.04.002-.079.004-.119.004-.438 0-.86-.174-1.17-.484-.34-.342-.516-.81-.481-1.288l.201-2.8-2.523-1.225c-.432-.209-.751-.598-.876-1.062-.125-.464-.042-.958.228-1.356l1.573-2.323-1.573-2.322c-.27-.398-.353-.892-.228-1.357.125-.462.444-.851.876-1.06L7.544 7.91l-.201-2.797c-.034-.48.142-.951.481-1.289.31-.312.732-.485 1.17-.485.04 0 .079 0 .119.003l2.797.201 1.225-2.523c.209-.432.598-.751 1.06-.876.142-.038.285-.057.429-.057.328 0 .651.098.929.285l2.322 1.573L20.198.372c.275-.188.599-.285.927-.285.144 0 .29.02.428.057.465.125.854.444 1.062.876l1.225 2.523 2.8-.201c.037-.003.078-.003.116-.003.438 0 .858.173 1.172.485.338.338.515.809.48 1.289l-.204 2.797 2.527 1.225c.433.209.751.598.874 1.06.124.465.043.96-.227 1.357l-1.575 2.322 1.575 2.323c.269.398.351.892.227 1.356-.123.464-.441.852-.874 1.062l-2.527 1.225.204 2.8c.034.478-.143.946-.48 1.288-.313.311-.734.484-1.172.484-.038 0-.079-.002-.116-.004l-2.8-.204-1.225 2.527c-.209.433-.598.751-1.062.874-.139.04-.284.057-.428.057z"/><circle fill="#732700" cx="18" cy="14" r="7"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f34d.svg b/games/sound-soup/assets/img/1f34d.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#5C913B" d="M18.241 9.633c-.277-3.307 2.17-4.72 2.17-4.72-3.199.113-4.894 2.826-4.894 2.826-.752-1.3.946-4.012 2.169-4.719-3.198.113-3.67 2.12-3.67 2.12-1.503-2.601-1.03-4.607-1.03-4.607-1.121.647-1.767 2.113-2.141 3.512l-2.318-2.67c-.23 1.044.157 3.174.573 4.959-3.055-1.79-5.903-.15-5.903-.15 3.95 1.188 5.45 3.788 5.45 3.788s-3.948-1.187-5.646 1.526c2.597-.092 4.5.499 5.856 1.23-1.163.289-3.145-.236-4.355 1.371 0 0 3.198-.113 3.851 1.055-2.172.614-3.575 2.251-3.575 2.251 4.422-.818 9.123 1.669 9.123 1.669l6.119-3.532c-1.029-4.607 2.642-6.727 2.642-6.727-2.724-1.895-4.421.818-4.421.818z"/><path fill="#FFAC33" d="M29.56 22.88c2.488 4.309 1.218 9.7-2.837 12.041-4.055 2.341-9.359.746-11.846-3.562l-1.589-2.753c-2.488-4.31-1.217-9.699 2.837-12.04 4.055-2.341 9.359-.747 11.846 3.562l1.589 2.752z"/><path fill="#FFCC4D" d="M30.562 25.364c-.209-.848-.54-1.685-1.002-2.484l-.186-.323v.001l-1.951-.828.624-1.471-.075-.131c-2.025-3.51-5.92-5.217-9.486-4.466l-.818 1.926-1.884-.8c-3.28 2.122-4.567 6.319-3.262 10.128l1.006.427-.401.946c.055.105.102.212.163.315l1.589 2.753c.685 1.187 1.59 2.16 2.618 2.909l.229-.538 1.951.828-.324.764c.665.277 1.357.465 2.061.572l.215-.507 1.403.595c1.144-.048 2.28-.336 3.343-.883l-.017-.007.828-1.951 1.189.504c.522-.521.973-1.104 1.334-1.736l-1.693-.72.828-1.951 1.667.707c.191-.7.301-1.427.316-2.167l-1.155-.49.828-1.951.062.029zm-6.212-7.243l1.95.829-.828 1.95-1.951-.828.829-1.951zm.294 4.731l-.828 1.95-1.951-.827.828-1.951 1.951.828zm-4.197-6.387l1.951.828-.829 1.952-1.951-.828.829-1.952zm-1.656 3.902l1.951.828-.828 1.951-1.95-.828.827-1.951zm-3.902-1.655l1.95.828-.828 1.949-1.95-.828.828-1.949zm-2.484 5.853l.828-1.952 1.951.828-.829 1.952-1.95-.828zm4.196 6.385l-1.951-.827.828-1.951 1.951.828-.828 1.95zm-.295-4.73l.829-1.951 1.951.827-.829 1.952-1.951-.828zm4.196 6.386l-1.95-.828.828-1.95 1.95.829-.828 1.949zm-.294-4.73l.828-1.952 1.951.829-.828 1.952-1.951-.829zm4.197 6.388l-1.951-.828.828-1.951 1.951.828-.828 1.951zm1.657-3.904l-1.95-.827.828-1.952 1.95.828-.828 1.951zm1.656-3.901l-1.951-.828.828-1.95 1.951.827-.828 1.951z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f356.svg b/games/sound-soup/assets/img/1f356.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#FFD983" d="M14.017 10.368L8.173 4.524c.847-1.017.805-2.525-.15-3.479-1.011-1.011-2.65-1.011-3.662 0-.621.621-.856 1.478-.715 2.282-.804-.141-1.661.094-2.282.715-1.011 1.011-1.011 2.651 0 3.662.954.955 2.462.996 3.479.15l5.844 5.844 3.33-3.33zm11.918 11.919l5.845 5.844c1.017-.846 2.524-.805 3.479.15 1.012 1.011 1.012 2.65 0 3.661-.621.621-1.479.856-2.282.716.142.804-.095 1.661-.716 2.281-1.011 1.012-2.65 1.012-3.661 0-.955-.954-.996-2.462-.149-3.479l-5.845-5.844 3.329-3.329z"/><path fill="#C1694F" d="M30.021 15.879c-1.632-1.633-4.113-1.543-6.234-3.665-2.121-2.121-2.032-4.602-3.665-6.235-3.515-3.515-7.018-1.467-11.313 2.829-4.296 4.296-6.343 7.799-2.829 11.313 1.633 1.633 4.114 1.544 6.235 3.665 2.122 2.121 2.032 4.603 3.665 6.234 3.515 3.515 7.018 1.468 11.314-2.828s6.341-7.798 2.827-11.313z"/><path fill="#662113" d="M14 23c-.104 0-.211-.017-.316-.052-.524-.174-.807-.74-.632-1.265 1.109-3.328 5.305-7.523 8.632-8.632.528-.176 1.09.108 1.265.632.175.524-.108 1.09-.632 1.265-2.686.895-6.473 4.682-7.368 7.368-.14.419-.53.684-.949.684z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f360.svg b/games/sound-soup/assets/img/1f360.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#B90053" d="M9.441 14.595c.458-1.372 7.41-7.525 13.286-10.22 1.528-.701 6.602-2.834 8.35-3.271C34.681.203 36.167.09 35.666 3.75c-.708 5.171-4.619 12.596-8.707 19.751-2.68 4.213-4.232 6.423-10.365 3.356-6.13-3.064-9.196-6.13-7.153-12.262z"/><path fill="#FFCC4D" d="M22.491 14.76c3.816 3.768 4.498 9.263 1.524 12.276-2.974 3.014-8.477 2.4-12.294-1.365-3.816-3.768-4.499-9.264-1.525-12.276 2.975-3.013 8.479-2.401 12.295 1.365z"/><path fill="#8E0042" d="M26.96 6.377c1.14.57 2.703 3.605 2.703 4.506 0 .901-.901-.901-2.703-2.704-1.803-1.802-1.803-2.703 0-1.802zm.181 8.03c.607.578 1.009 2.787.836 3.354-.173.567-.395-.74-1.183-2.22-.789-1.48-.615-2.047.347-1.134z"/><path fill="#B90053" d="M21 32c0 2.209-4.477 4-10 4S1 34 1 32c0-.371-.203-2.6.084-2.958C2.34 27.469 6.502 28 11 28c4.908 0 9.133-.865 9.979 1 .107.233.021 2.754.021 3z"/><ellipse fill="#FFD671" cx="11" cy="29" rx="10" ry="4"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f371.svg b/games/sound-soup/assets/img/1f371.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#292F33" d="M31 0H5C2.791 0 1 1.791 1 4v28c0 2.209 1.791 4 4 4h26c2.209 0 4-1.791 4-4V4c0-2.209-1.791-4-4-4z"/><path fill="#292F33" d="M14 11c0 2.209-1.791 4-4 4H9c-2.209 0-4-1.791-4-4V9c0-2.209 1.791-4 4-4h1c2.209 0 4 1.791 4 4v2z"/><path fill="#BE1931" d="M22 27c0 1.104-.896 2-2 2H5c-1.104 0-2-.896-2-2v-8c0-1.104.896-2 2-2h15c1.104 0 2 .896 2 2v8zm11-8c0-1.104-.896-2-2-2h-5c-1.104 0-2 .896-2 2v8c0 1.104.896 2 2 2h5c1.104 0 2-.896 2-2v-8zM16 4c0-1.104.896-2 2-2h13c1.104 0 2 .896 2 2v9c0 1.104-.896 2-2 2H18c-1.104 0-2-.896-2-2V4zm-2 9c0 1.104-.896 2-2 2H5c-1.104 0-2-.896-2-2V4c0-1.104.896-2 2-2h7c1.104 0 2 .896 2 2v9z"/><path fill="#DD2E44" d="M14 13c0 1.104-.896 2-2 2H5c-1.104 0-2-.896-2-2V7c0-1.104.896-2 2-2h7c1.104 0 2 .896 2 2v6zm19 0c0 1.104-.896 2-2 2H18c-1.104 0-2-.896-2-2V7c0-1.104.896-2 2-2h13c1.104 0 2 .896 2 2v6z"/><path fill="#E1E8ED" d="M22 27c0 1.104-.896 2-2 2H5c-1.104 0-2-.896-2-2v-5c0-1.104.896-2 2-2h15c1.104 0 2 .896 2 2v5z"/><path fill="#DD2E44" d="M33 27c0 1.104-.896 2-2 2h-5c-1.104 0-2-.896-2-2v-5c0-1.104.896-2 2-2h5c1.104 0 2 .896 2 2v5z"/><path fill="#FFAC33" d="M27.015 9.24c1.315 1.478 1.908 3.098 1.322 3.619l-1.059.941c-.585.522-2.126-.255-3.44-1.733l-3.571-4.013c-1.314-1.479-1.907-3.099-1.321-3.62l1.059-.941c.586-.521 2.124.255 3.438 1.733l3.572 4.014z"/><path fill="#FFD983" d="M22.28 11.076c-.598 1.539-1.477 2.633-1.961 2.444l-.877-.341c-.485-.188-.393-1.588.205-3.126l1.625-4.178c.599-1.538 1.477-2.632 1.962-2.444l.876.341c.485.188.394 1.588-.205 3.126l-1.625 4.178z"/><path fill="#FFD983" d="M23.113 11.536c-1.562.535-2.965.568-3.133.076l-.305-.891c-.169-.492.962-1.324 2.522-1.858l4.242-1.45c1.562-.534 2.964-.567 3.133-.075l.304.89c.169.493-.961 1.325-2.523 1.858l-4.24 1.45z"/><path fill="#5C913B" d="M6.442 7.873c.373-4.106 4.907-.961 7.135 1.049-1.34 1.485-7.586 3.93-7.135-1.049z"/><path fill="#5C913B" d="M6.584 7.784c3.739-1.737 3.293 3.763 2.672 6.698-1.956-.414-7.206-4.59-2.672-6.698z"/><path fill="#77B255" d="M5 8c-1.2-1.6 1-5 6-5 1 1-3 9-6 5z"/><path fill="#B2C2CC" d="M6 27c-.256 0-.512-.098-.707-.293-.391-.391-.391-1.023 0-1.414l1-1c.391-.391 1.023-.391 1.414 0s.391 1.023 0 1.414l-1 1C6.512 26.902 6.256 27 6 27zm12.417.499c-.256 0-.512-.098-.707-.293l-1-1c-.391-.391-.391-1.024 0-1.414.391-.391 1.024-.391 1.415 0l1 1c.391.391.391 1.023 0 1.414-.196.195-.452.293-.708.293zm-6.167-4.291c-.255 0-.511-.098-.707-.292l-.459-.458c-.391-.391-.392-1.023-.002-1.414.389-.392 1.023-.392 1.415-.002l.459.458c.391.391.392 1.023.002 1.414-.195.196-.451.294-.708.294z"/><path fill="#B26304" d="M32.149 23.412c.707.848.592 2.109-.257 2.816l-2.305 1.921c-.848.707-2.109.592-2.816-.257l-1.921-2.305c-.707-.848-.592-2.109.257-2.816l2.305-1.921c.848-.707 2.109-.592 2.816.257l1.921 2.305z"/><path fill="#F4900C" d="M32.149 21.412c.707.848.592 2.109-.257 2.816l-2.305 1.921c-.848.707-2.109.592-2.816-.257l-1.921-2.305c-.707-.848-.592-2.109.257-2.816l2.305-1.921c.848-.707 2.109-.592 2.816.257l1.921 2.305z"/><circle fill="#CCD6DD" cx="12" cy="26" r="1"/><circle fill="#CCD6DD" cx="20" cy="22" r="1"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f372.svg b/games/sound-soup/assets/img/1f372.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#292F33" d="M13.037 34.816C.794 33.013 1 20.719 1 17h34c0 1.914.087 15.77-13.125 17.918-2.042.332-6.458.249-8.838-.102z"/><path fill="#66757F" d="M33.388 13.247c2.467-5.574-8.331-5.254-11.035-4.382-1.392-.162-2.846-.254-4.353-.254-9.389 0-17 3.228-17 8.444 0 1.009.291 1.979.818 2.891-4.327 5.712 4.246 6.786 9.184 5.71 2.136.537 4.5.844 6.998.844 9.389 0 17-4.229 17-9.444 0-1.43-.589-2.704-1.612-3.809z"/><ellipse fill="#FFCC4D" cx="18" cy="18" rx="15" ry="7"/><path fill="#77B255" d="M4 16c-.542-2.169 2-6 7-5 1-1 2.838-2 6-2 2 0 5.612 1.775 4 5-1 2-5 5-10 3-1 1-6 3-7-1z"/><path fill="#5C913B" d="M12 17c-.249.036-.471.073-.678.111C16.17 18.857 20.022 15.956 21 14c1.078-2.156-.181-3.66-1.731-4.423.394 1.822-1.128 6.546-7.269 7.423z"/><path fill="#D99E82" d="M5 16c.525-1.313 5-3 10 3 1 1 4.586 1.586 6 3 1 1 0 2-4 2S3 21 5 16z"/><path fill="#B27962" d="M6.041 20.018C8.589 22.561 14.231 24 17 24c4 0 5-1 4-2-.326-.326-.77-.607-1.268-.86-3.204 1.733-11.68-.144-13.691-1.122z"/><path fill="#FFE8B6" d="M27 19.001c-.209 0-.423-.034-.633-.104l-3-1c-1.048-.35-1.614-1.482-1.265-2.53.349-1.047 1.479-1.614 2.53-1.265l3 1c1.048.35 1.614 1.482 1.265 2.53-.279.839-1.059 1.369-1.897 1.369zM26 22h-2c-.553 0-1-.447-1-1s.447-1 1-1h2c.553 0 1 .447 1 1s-.447 1-1 1z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f373.svg b/games/sound-soup/assets/img/1f373.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#292F33" d="M34.238 27.931c-.704-.703-3.857-2.661-6.935-4.364C28.999 21.137 30 18.187 30 15c0-8.284-6.716-15-15-15C6.715 0 0 6.716 0 15c0 8.284 6.715 15 15 15 3.159 0 6.086-.981 8.504-2.649 1.705 3.081 3.666 6.24 4.37 6.944C29.288 35.709 31 36.002 32 36c1.177-.002 4.055-2.881 4-4-.049-.999-.348-2.655-1.762-4.069z"/><circle fill="#66757F" cx="15" cy="15" r="12"/><path fill="#F5F8FA" d="M16 25c4.863-1.533 4-5 4-9s4.001-6.708.334-9.75c-2.177-1.806-6.75-2.417-8.875-1.625C9.506 5.352 7 6.833 7 9.833s-.058 2.927-1.708 5.625C2.667 19.75 9.125 27.167 16 25z"/><circle fill="#FFAC33" cx="13" cy="18" r="4"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f37d.svg b/games/sound-soup/assets/img/1f37d.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#E1E8ED" d="M36 18c0 9.941-8.059 18-18 18S0 27.941 0 18 8.059 0 18 0s18 8.059 18 18z"/><path fill="#CCD6DD" d="M30 18c0 6.628-5.372 12-12 12S6 24.628 6 18 11.372 6 18 6s12 5.372 12 12z"/><path fill="#E1E8ED" d="M29 18c0 6.075-4.925 11-11 11-6.076 0-11-4.925-11-11 0-6.076 4.925-11 11-11s11 4.924 11 11z"/><g fill="#99AAB5"><circle cx="1" cy="1" r="1"/><path d="M0 1h2v7H0z"/><circle cx="5" cy="1" r="1"/><path d="M4 1h2v7H4z"/><circle cx="9" cy="1" r="1"/><path d="M8 1h2v7H8zM3 14h4v20H3z"/><circle cx="5" cy="34" r="2"/><path d="M8 8c0 .552-.448 1-1 1s-1-.448-1-1H4c0 .552-.448 1-1 1s-1-.448-1-1H0c0 3.866 1 7 5 7s5-3.134 5-7H8z"/><circle cx="1" cy="8" r="1"/><circle cx="5" cy="8" r="1"/><circle cx="9" cy="8" r="1"/></g><g fill="#99AAB5"><path d="M30 14h4v20h-4z"/><circle cx="32" cy="34" r="2"/><path d="M32 0c1 0 2 1 2 3v16s-7 2-7-8c0-6 3-11 5-11z"/></g></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f381.svg b/games/sound-soup/assets/img/1f381.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#FDD888" d="M33 31c0 2.2-1.8 4-4 4H7c-2.2 0-4-1.8-4-4V14c0-2.2 1.8-4 4-4h22c2.2 0 4 1.8 4 4v17z"/><path fill="#FDD888" d="M36 11c0 2.2-1.8 4-4 4H4c-2.2 0-4-1.8-4-4s1.8-4 4-4h28c2.2 0 4 1.8 4 4z"/><path fill="#FCAB40" d="M3 15h30v2H3z"/><path fill="#DA2F47" d="M19 3h-2c-1.657 0-3 1.343-3 3v29h8V6c0-1.656-1.343-3-3-3z"/><path fill="#DA2F47" d="M16 7c1.1 0 1.263-.516.361-1.147L9.639 1.147c-.902-.631-2.085-.366-2.631.589L4.992 5.264C4.446 6.219 4.9 7 6 7h10zm4 0c-1.1 0-1.263-.516-.361-1.147l6.723-4.706c.901-.631 2.085-.366 2.631.589l2.016 3.527C31.554 6.219 31.1 7 30 7H20z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f38b.svg b/games/sound-soup/assets/img/1f38b.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path d="M20.416 14.94c-1.003-1.821-1.381-3.77-1.685-5.336-.083-.427 1.579-2.199 1.701-1.66.092.401.175.83.263 1.279.291 1.503.621 3.207 1.473 4.752.267.485-1.569 1.296-1.752.965z"/><path fill="#BE1931" d="M18.456 17.272c.875-.524 5.387-2.693 6.682-3.288.524 1.679 2.903 15.461 9.234 15.146 1.469.069.104 1.504-1.085 1.644-1.189.141-2.901-.174-3.602-.489.21.734.272 2.973.244 3.742-.034.945-1.129.59-2.169-.035-6.226-3.742-9.304-16.72-9.304-16.72z"/><path fill="#A0041E" d="M25.138 13.985c-1.295.595-5.807 2.763-6.682 3.288 0 0 .148.624.445 1.637 1.477-.676 4.959-2.242 6.705-3.026-.222-.916-.375-1.601-.468-1.899z"/><path fill="#FFAC33" d="M18.212 17.448c1.135-.454 6.191-2.764 7.205-3.253 1.015-.49.7-2.344-.489-1.924-1.189.42-6.052 2.763-7.205 3.253-1.155.489-.386 2.273.489 1.924z"/><path fill="#A6D388" d="M6.195 14.044c-1.895-1.62 4.242-5.004 7.798-6.138-.251 1.072-5.064 8.475-7.798 6.138zm19.344-9.658c.964-2.299 6.06 2.512 8.225 5.55-1.096.089-9.615-2.233-8.225-5.55z"/><path fill="#5C913B" d="M3.083 35.625c-.829 0-1.5-.672-1.5-1.5 0-.192-.005-.438-.011-.734C1.486 29.226 1.513 16.86 10 2.583c.424-.712 1.056-1.254 1.767-.831.712.423.323 1.369-.1 2.081C3.618 17.372 4.49 29.376 4.571 33.328c.007.322.012.589.012.797 0 .828-.671 1.5-1.5 1.5z"/><path fill="#3E721D" d="M2.082 33.041c-.787 0-1.448-.613-1.496-1.41-.05-.827.58-1.537 1.407-1.587l2.083-.125c.825-.06 1.538.58 1.587 1.407.05.827-.58 1.537-1.407 1.587l-2.083.125c-.03.002-.061.003-.091.003z"/><path fill="#5C913B" d="M7.71 35.583c-.095 0-.19-.009-.287-.027-.813-.158-1.345-.945-1.187-1.759.035-.18.078-.448.132-.793C9.768 11.71 19.884 4.735 28.167 2.167c.799-.245 2.104-.359 2.349.431.245.791-.6 1.282-1.391 1.527-7.472 2.317-16.638 9.579-19.794 29.352-.062.388-.111.689-.15.892-.139.717-.767 1.214-1.471 1.214z"/><path fill="#3E721D" d="M9.916 8.625c-.199 0-.4-.04-.594-.123l-2.125-.917c-.76-.328-1.111-1.21-.783-1.971.328-.762 1.211-1.111 1.972-.783l2.125.916c.761.328 1.111 1.211.783 1.972-.245.567-.797.906-1.378.906zM5.459 20.916c-.125 0-.251-.016-.377-.048l-2.25-.583c-.802-.208-1.283-1.026-1.076-1.829.208-.802 1.025-1.285 1.829-1.076l2.25.583c.802.208 1.283 1.026 1.076 1.828-.176.677-.785 1.125-1.452 1.125zM22.668 8.75c-.481 0-.955-.232-1.245-.662l-1.292-1.916c-.463-.687-.281-1.619.405-2.082.687-.462 1.619-.282 2.083.405l1.292 1.917c.463.687.281 1.619-.405 2.082-.258.173-.549.256-.838.256zm-8.003 9.542c-.235 0-.473-.056-.695-.172l-2.542-1.333c-.734-.385-1.017-1.292-.631-2.025.385-.733 1.292-1.017 2.025-.631l2.542 1.333c.734.385 1.017 1.292.631 2.025-.268.511-.79.803-1.33.803zM9.917 29.958c-.127 0-.255-.016-.384-.05l-2.208-.583c-.801-.211-1.279-1.032-1.067-1.833.212-.801 1.035-1.279 1.833-1.067l2.208.583c.801.211 1.279 1.032 1.067 1.833-.177.673-.784 1.117-1.449 1.117z"/><path fill="#A6D388" d="M4.958 15.208c-2.252 1.068-2.834-5.917-2.417-9.625.876.667 5.667 8.084 2.417 9.625z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f3a8.svg b/games/sound-soup/assets/img/1f3a8.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#D99E82" d="M18 3.143c-9.941 0-18 6.908-18 15.428 0 1.066.126 2.107.367 3.112C2.146 24.744 3.377 22.812 9 20c5.727-2.864 0 4-2 8-.615 1.23-.282 2.271.56 3.124C10.506 32.928 14.104 34 18 34c9.941 0 18-6.907 18-15.429 0-8.52-8.059-15.428-18-15.428zm2.849 24.447c-.395 1.346-2.46 1.924-4.613 1.291-2.153-.632-3.578-2.234-3.183-3.581.395-1.346 2.46-1.924 4.613-1.29 2.153.631 3.578 2.233 3.183 3.58z"/><circle fill="#5C913B" cx="10" cy="11" r="3"/><circle fill="#269" cx="20" cy="9" r="3"/><circle fill="#DD2E44" cx="29" cy="15" r="3"/><circle fill="#FFCC4D" cx="28" cy="24" r="3"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f3a9.svg b/games/sound-soup/assets/img/1f3a9.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#31373D" d="M30.198 27.385L32 3.816c0-.135-.008-.263-.021-.373.003-.033.021-.075.021-.11C32 1.529 25.731.066 18 .066c-7.732 0-14 1.462-14 3.267 0 .035.017.068.022.102-.014.11-.022.23-.022.365l1.802 23.585C2.298 28.295 0 29.576 0 31c0 2.762 8.611 5 18 5s18-2.238 18-5c0-1.424-2.298-2.705-5.802-3.615z"/><path fill="#66757F" d="M17.536 6.595c-4.89 0-8.602-.896-10.852-1.646-.524-.175-.808-.741-.633-1.265.175-.524.739-.808 1.265-.633 2.889.963 10.762 2.891 21.421-.016.529-.142 1.082.168 1.227.702.146.533-.169 1.083-.702 1.228-4.406 1.202-8.347 1.63-11.726 1.63z"/><path fill="#744EAA" d="M30.198 27.385l.446-5.829c-7.705 2.157-17.585 2.207-25.316-.377l.393 5.142c.069.304.113.65.113 1.076 0 1.75 1.289 2.828 2.771 3.396 4.458 1.708 13.958 1.646 18.807.149 1.467-.453 2.776-1.733 2.776-3.191 0-.119.015-.241.024-.361l-.014-.005z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f3c3.svg b/games/sound-soup/assets/img/1f3c3.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#FFDC5D" d="M15.087 24.856l-1.728-3.593c-.428.233-2.257 1.253-3.35 2.237-.635.572-.549 1.002-.36 1.944l.017.085c.189.952 2.205 7.351 2.47 8.187l1.345-.334c.011-.446-.04-2.803-.272-4.27-.073-.463-.147-1.025-.214-1.522-.071-.543-.263-1.087-.309-1.323-.064-.334.13-.493.433-.64.862-.417 1.715-.66 1.968-.771zm8.793 1.905c-.546-.067-1.107-.08-1.346-.095-.339-.021-.577-.253-.643-.584-.188-.939-.225-1.767-.268-2.039l-3.912.767c.118.473.644 2.5 1.322 3.806.393.757.831.782 1.79.838l.087.005c.975.057 7.669-.284 8.545-.329l.015-1.385c-.43-.124-2.59-.664-4.069-.808-.464-.045-1.025-.115-1.521-.176z"/><path fill="#8899A6" d="M27.805 29.682l-.178-.328c-.022-.053.004-.113.058-.132.171-.059.518-.217.639-.281-.105.24-.225.624-.359.74-.052.043-.134.063-.16.001z"/><path fill="#DD551F" d="M30.398 27.138c-.139-.111-.457-.166-.657-.185-.138-.013-.498-.024-.799-.008-.136.008-.291.024-.355.043-.198.057-.285.141-.332.198-.057.069-.062.144-.049.181.052.153.304.418.414.67.037.084.058.166.05.243-.027.276-.379.678-.643.976-.158.179-.285.32-.29.375-.006.059.011.109.037.158v.002c.072.132.229.269.337.629.069.232.395 1.291.395 1.291.078.27.182.336.226.481.091.297.251 1.094.331 1.346.08.252.271.36.418.381s.232-.014.232-.014.345-.155.574-1.449c.165-.935.019-1.079-.105-2.483s.216-2.835.216-2.835z"/><path fill="#FA743E" d="M30.109 29.226c-.235-.096-.771-.45-.922-.831-.136-.344-.229-1.273-.246-1.449.288-.026.731-.007.873.007.188.018.404.033.534.143l.05.043c0-.001-.229 1.473-.289 2.087z"/><path fill="#CCD6DD" d="M28.048 30.27c-.073 0-.142-.041-.175-.111-.046-.097-.005-.213.092-.258l.342-.162c.097-.046.212-.005.259.092.046.097.005.213-.092.258l-.342.162c-.028.013-.056.019-.084.019zm.218.625c-.082 0-.158-.053-.185-.135-.032-.102.024-.211.126-.244l.38-.121c.097-.033.21.023.243.126.032.102-.024.211-.126.244l-.38.121c-.018.007-.038.009-.058.009zm.184.637c-.091 0-.171-.063-.19-.155-.021-.105.046-.207.152-.229l.356-.072c.111-.023.207.047.229.152.021.105-.046.207-.152.229l-.356.072-.039.003zm2.776-4.13c-.006-.028-.027-.137-.101-.27-.025-.045-.082-.059-.14-.066-.072-.009-.6-.085-.61.022 0 0-.003.159-.077.268-.075.109-.127.377-.146.577-.019.199-.105.951-.085 1.478.02.528.183 1.482.19 1.8.008.318 0 .814-.068 1.18s-.14.692-.196.893c-.072.259-.168.471-.279.619 0 0 .417-.028.736-.645.252-.487.393-.955.446-1.411.004-.035.113-1.252.165-1.86.038-.445.102-1.155.102-1.155.081-.841.1-1.269.063-1.43z"/><path fill="#8899A6" d="M11.211 32.283l.269-.259c.045-.036.11-.027.143.021.102.149.348.441.441.541-.26-.037-.661-.051-.809-.149-.056-.038-.096-.112-.044-.154z"/><path fill="#DD551F" d="M14.355 34.104c.07-.163.038-.485.003-.683-.024-.136-.109-.486-.206-.772-.043-.129-.101-.274-.136-.33-.108-.176-.212-.237-.28-.267-.082-.036-.155-.022-.188.001-.134.091-.322.405-.535.578-.071.058-.144.1-.22.113-.273.048-.755-.184-1.112-.359-.214-.105-.385-.189-.438-.18-.059.01-.102.039-.143.078l-.002.001h.001c-.108.104-.199.293-.516.492-.206.128-1.14.724-1.14.724-.24.147-.275.265-.403.346-.262.167-.987.534-1.209.678-.222.144-.275.357-.255.505.02.148.076.22.076.22s.242.291 1.549.166c.945-.09 1.045-.269 2.365-.763 1.32-.493 2.789-.548 2.789-.548z"/><path fill="#FA743E" d="M12.266 34.382c.03-.252.228-.863.555-1.11.295-.222 1.166-.56 1.331-.624.102.271.202.703.226.843.033.186.076.398.004.553l-.028.059c.001.001-1.481.173-2.088.279z"/><path fill="#CCD6DD" d="M11.144 33.103c-.054 0-.109-.023-.147-.067l-.247-.287c-.07-.081-.061-.204.02-.274.081-.07.205-.061.274.02l.247.287c.07.081.061.204-.02.274-.038.032-.083.047-.127.047zm-.575.425c-.063 0-.125-.031-.163-.088l-.218-.334c-.058-.09-.033-.21.057-.269.09-.058.209-.034.269.056l.218.334c.058.09.033.21-.057.269-.033.022-.069.032-.106.032zm-.617.337c-.07 0-.138-.039-.173-.106l-.165-.324c-.048-.095-.01-.212.085-.261.095-.049.211-.011.261.085l.165.324c.048.096.01.212-.085.261-.028.015-.059.021-.088.021zm4.369 1.108c.026-.014.125-.062.234-.17.036-.036.035-.095.026-.153-.01-.072-.078-.601-.184-.582 0 0-.154.04-.279-.003-.125-.043-.398-.021-.595.013-.197.035-.944.152-1.447.312-.503.16-1.379.571-1.684.664-.305.092-.784.217-1.156.249-.371.032-.705.05-.913.049-.269 0-.498-.036-.671-.103 0 0 .138.394.818.537.537.113 1.025.124 1.479.053.034-.005 1.237-.225 1.836-.337.439-.082 1.14-.21 1.14-.21.833-.146 1.25-.241 1.396-.319z"/><path fill="#C63900" d="M12.654 21.244c.751-.398 3.235-1.653 4.947-1.804.156-.014.298.088.352.235l1.328 3.635c.068.186-.027.391-.216.451-.781.25-2.74.915-4.22 1.719-.157.085-.347.041-.448-.106-.456-.664-1.642-2.477-1.923-3.76-.032-.15.045-.298.18-.37z"/><path fill="#DD551F" d="M17.514 25.488c-.196-.827-.785-3.547-.501-5.242.026-.155.16-.266.316-.281l3.853-.37c.198-.019.371.125.382.323.046.818.196 2.882.601 4.517.043.173-.048.346-.216.407-.758.274-2.811.965-4.123.914-.153-.007-.276-.118-.312-.268z"/><path fill="#FFDC5D" d="M15.015 10.618c-.085.612.05 1.546 1.466 1.787 1.416.241 2.812.059 3.411-.108.599-.167.569.884.747 1.872.179.992.301 1.768.252 1.973-.082.347-.809 1.011-.517 1.612s.757 1.179 1.332.962c.575-.217 1.05-.475 1.203-.797s-.525-1.295-.552-1.574c-.027-.278.227-3.888.194-4.298-.04-.501-.078-1.187-.896-1.47-.818-.284-4.094-.92-4.915-1.079-.942-.185-1.612.309-1.725 1.12zM13.826.971C11.981.781 9.725 2.34 9.867 4.376c.198 2.841 2.368 4.687 4.011 4.031 1.723-.688 2.703-1.387 2.911-3.417.21-2.03-1.117-3.829-2.963-4.019z"/><path fill="#FFDC5D" d="M16.449 5.593c1.625-3.518-4.125-1.612-4.125-1.612-1.092.348.181 1.974-.058 3.122-.162.794 1.439.743 1.439.743s.685-.202.955.622l.002.008c.068.21.116.469.111.834-.027 1.808 2.503 2.205 2.528.394.01-.717-.229-1.278-.478-1.788l-.022-.045c-.372-.76-.753-1.408-.352-2.278z"/><path fill="#FFAC33" d="M16.079.962c-1.13-.88-4.156-1.091-5.51.802-1.269.129-1.411 1.408-1.123 2.041.23.506 1.567.279 2.173 1.192.156-.315.072-.847-.054-1.109.525.283.637 1.379 2.455 1.608 1.757.221 1.867 1.688 1.867 1.688s.719-.623 1.109-1.451c.745-1.582.329-3.8-.917-4.771z"/><path fill="#FA743E" d="M19.459 10.057c-.77-1.644-2.017-2.56-3.384-1.957-1.809.799-2.443 2.756-2.332 4.652.122 2.065 1.556 3.924 1.551 7.601 0 0 3.109.449 6.316.36 0 0-.298-6.699-2.151-10.656z"/><path fill="#FFDC5D" d="M16.94 10.268c-.577-.345-1.462-.572-2.304.745s-1.187 2.756-1.447 3.433c-.243.632-1.127.196-2.194-.045-1.07-.242-2.429-.654-2.614-.79-.313-.229-.199-.874-.925-.837s-1.083-.047-1.11.622c-.026.669-.035 1.339.222 1.629.258.29 1.127.04 1.516.177.287.101 3.803 1.876 4.228 2.017.519.172 1.223.425 1.854-.275s2.658-3.714 3.167-4.47c.584-.867.373-1.748-.393-2.206z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f3ca.svg b/games/sound-soup/assets/img/1f3ca.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#FFDC5D" d="M36 20.363C34.529 19.5 31.846 18 30 17c-2.638-1.429-3.53-5.558-4.14-7.737C25.256 7.11 23.201 3.737 23 3c-.263-.965-3-2-8 0S7 5 5 5 2 7 1 9s0 2 0 2c1 1 2-2 4-3s3 1 5 0c1.493-.747 4.146.009 6.488-.305.794-.107 2.005-.188 2.512-.695 0 0 .572 6.698 2 11 0 0-.209 1.075-1.209 4.075-.5 1.5-1.791 1.425-2.166.8S17 21 17 20c0-2-1-5-5-7-1.789-.894-5.658-.683-7 2-2 4 0 8 2 9s3 2 7 2c1.943 0 17.812.48 22 .364v-6.001z"/><path fill="#FA743E" d="M21 18c3.745 2.204 12.107.993 12.798.897S36 20.363 36 20.363v4.918H19.626s.348-2.123-.23-3.073C20.396 20.754 21 18 21 18z"/><path fill="#DD551F" d="M8.182 16.405c2.186-1.941 5.633-1.688 8.025.472C15.49 15.504 14.201 14.101 12 13c-1.789-.894-5.658-.683-7 2-2 4 0 8 2 9 .239.119.463.239.681.356-1.814-2.621-1.665-6.025.501-7.951z"/><path fill="#FCAB40" d="M12 18.5c-.128 0-.256-.049-.354-.146-.195-.195-.195-.512 0-.707C12.78 16.513 13.951 16.5 14 16.5c.276 0 .5.224.5.5s-.224.5-.5.5c-.028 0-.821.028-1.646.854-.098.097-.226.146-.354.146zm-4 3c-.128 0-.256-.049-.354-.146-.195-.195-.195-.512 0-.707C8.78 19.513 9.951 19.5 10 19.5c.276 0 .5.224.5.5s-.224.5-.5.5c-.028.001-.821.028-1.646.854-.098.097-.226.146-.354.146z"/><path fill="#55ACEE" d="M0 24h36v12H0z"/><path fill="#55ACEE" d="M6 24c3-2 7-5 6-3s-1.001 3 1 2c2-1 5-2 4 0s2 1 3 0 4 0 3 1-9 2-12 2-5-2-5-2z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f3dd.svg b/games/sound-soup/assets/img/1f3dd.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><ellipse fill="#3B88C3" cx="18" cy="30.54" rx="18" ry="5.36"/><path fill="#88C9F9" d="M33.812 28.538c0 1.616-2.5 2.587-14.482 2.587-10.925 0-13.612-.971-13.612-2.587s5.683-2.926 13.612-2.926 14.482 1.31 14.482 2.926z"/><path fill="#F4900C" d="M7 28.25c0-1 1-5 11-5 12 0 15 4 15 5s0 2-14 2c-12 0-12-1-12-2z"/><circle fill="#FFCC4D" cx="7" cy="6.25" r="5"/><circle fill="#FFAC33" cx="7" cy="6.25" r="4"/><path fill="#C1694F" d="M26.949 14.467c-.039-.575-.098-1.131-.177-1.662-.095-.636-.214-1.235-.347-1.797-.122-.517-.255-.999-.392-1.442-.205-.666-.417-1.243-.609-1.716-.377-.929-.674-1.46-.674-1.46l-2.583.333s.298.622.621 1.841c.131.495.265 1.09.386 1.784.061.351.118.724.17 1.125.072.558.131 1.167.173 1.823.024.37.041.756.053 1.158.004.146.011.287.013.437.007.469-.026.946-.084 1.422-.053.429-.13.856-.223 1.279-.133.606-.296 1.202-.479 1.77-.149.46-.308.901-.468 1.316-.246.637-.493 1.209-.707 1.687-.358.798-.622 1.33-.622 1.441 0 .351.25 1.007 2 1.444 2.736.684 3-.444 3-.444s.174-.528.376-1.567c.1-.513.206-1.147.303-1.915.058-.459.112-.964.158-1.513.049-.572.089-1.192.118-1.863.021-.508.034-1.051.039-1.615.002-.176.006-.346.006-.527 0-.459-.021-.903-.051-1.339z"/><path fill="#D99E82" d="M24.324 21.362c-1.003-.175-1.643-.467-1.994-.686-.246.637-.493 1.209-.707 1.687.655.356 1.487.64 2.389.796.521.091 1.038.136 1.523.136.296 0 .574-.023.841-.057.1-.513.206-1.147.303-1.915-.318.113-1.107.257-2.355.039zm.758-3.342c-.782-.077-1.383-.249-1.805-.43-.133.606-.296 1.202-.479 1.77.618.233 1.345.401 2.106.476.307.03.611.045.908.045.359 0 .7-.028 1.026-.071.049-.572.089-1.192.118-1.863-.45.098-1.08.152-1.874.073zm.221-1.524c.588 0 1.165-.059 1.692-.163.001-.176.005-.346.005-.527 0-.458-.021-.902-.051-1.339-.378.099-.919.194-1.682.205-.775-.003-1.32-.111-1.698-.219.004.146.011.287.013.437.007.469-.026.946-.084 1.422.545.115 1.148.181 1.76.184h.045zm-.178-3.273c.59-.085 1.137-.238 1.648-.418-.095-.636-.214-1.235-.347-1.797-.354.146-.857.308-1.56.409-.651.094-1.147.088-1.522.055.072.558.131 1.167.173 1.823.109.004.207.021.319.021.42 0 .857-.031 1.289-.093zm.909-3.658c-.206-.665-.418-1.242-.61-1.715-.317.159-.718.326-1.242.471-.568.158-1.027.221-1.395.243.131.495.265 1.09.386 1.784.487-.049.989-.128 1.497-.269.486-.135.937-.316 1.364-.514z"/><path fill="#3E721D" d="M34.549 2.943c-.032-.042-3.202-4.283-7.313-2.423-1.847.835-3.038 2.2-3.469 3.963-.804-1.125-2.026-1.839-3.644-2.127-4.421-.788-7.092 4.123-7.118 4.172-.039.072-.022.16.039.214.062.053.151.057.217.01.02-.015 2.037-1.427 5.396-.828 3.433.611 5.84 1.211 5.864 1.217.015.003.028.005.043.005.048 0 .095-.02.129-.056.022-.024 2.303-2.475 4.4-3.423 3.104-1.404 5.221-.472 5.24-.462.072.033.158.013.209-.049.051-.061.054-.148.007-.213z"/><path fill="#5C913B" d="M31.129 5.401c-3.134-1.655-5.57-.831-7.083.174-1.813-.351-5.201-.357-8.072 3.324-3.525 4.518-.433 10.152-.402 10.209.031.056.09.089.153.089l.021-.001c.071-.009.129-.06.147-.128.011-.039 1.097-3.967 3.843-7.27 1.501-1.805 3.293-3.474 4.554-4.573 1.27.211 3.136.678 4.969 1.702 3.573 1.998 5.212 5.379 5.228 5.413.032.066.101.112.177.099.074-.008.135-.062.151-.134.014-.063 1.392-6.222-3.686-8.904z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f3ea.svg b/games/sound-soup/assets/img/1f3ea.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#AAB8C2" d="M17 34c0 1.104.896 2 2 2h14c1.104 0 2-.896 2-2V18c0-1.104-.896-2-2-2H19c-1.104 0-2 .896-2 2v16z"/><path fill="#292F33" d="M33 16H23v2h12c0-1.104-.896-2-2-2z"/><path fill="#3B88C3" d="M3 30h30v4H3z"/><path fill="#CCD6DD" d="M3 16c-1.104 0-2 .896-2 2v16c0 1.104.896 2 2 2h20V16H3z"/><path fill="#66757F" d="M3 16c-1.104 0-2 .896-2 2h22v-2H3z"/><path fill="#55ACEE" d="M3 20h4v4H3zm14 0h4v4h-4zm-7 0h4v4h-4z"/><path fill="#3B88C3" d="M29 20h4v4h-4zm-6 0h4v4h-4z"/><path fill="#55ACEE" d="M3 30h18v6H3z"/><path fill="#3B88C3" d="M7 30h10v6H7z"/><path fill="#DD2E44" d="M1 26h22v4H1z"/><path fill="#F4ABBA" d="M7 27h10v2H7z"/><path fill="#FFF" d="M9 27h6v2H9z"/><path fill="#A0041E" d="M23 26h12v4H23z"/><path fill="#292F33" d="M5 14h2v2H5zm12 0h2v2h-2z"/><path fill="#DD2E44" d="M21 12c0 1.104-.896 2-2 2H5c-1.104 0-2-.896-2-2V2c0-1.104.896-2 2-2h14c1.104 0 2 .896 2 2v10z"/><path d="M10.561 10.151c.616 0 1.093.28 1.093.925 0 .644-.477.924-1.009.924H5.967c-.617 0-1.093-.28-1.093-.924 0-.294.182-.546.322-.714C6.359 8.975 7.62 7.714 8.685 6.173c.252-.364.49-.798.49-1.303 0-.574-.434-1.079-1.009-1.079-1.611 0-.84 2.269-2.185 2.269-.672 0-1.022-.476-1.022-1.022 0-1.765 1.569-3.18 3.292-3.18 1.723 0 3.109 1.135 3.109 2.914 0 1.947-2.171 3.88-3.362 5.379h2.563zm2.363-.35c-.687 0-.981-.462-.981-.826 0-.309.112-.477.196-.617l3.138-5.687c.308-.56.7-.813 1.429-.813.812 0 1.611.519 1.611 1.793v4.3h.238c.546 0 .98.364.98.925 0 .56-.434.924-.98.924h-.238v1.19c0 .743-.295 1.093-1.009 1.093s-1.008-.35-1.008-1.093V9.8h-3.376zM16.3 4.044h-.028l-1.891 3.908H16.3V4.044z" fill="#FFF"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f3ee.svg b/games/sound-soup/assets/img/1f3ee.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#292F33" d="M29 34c0 1.104-.896 2-2 2H9c-1.104 0-2-.896-2-2V2c0-1.104.896-2 2-2h18c1.104 0 2 .896 2 2v32z"/><path fill="#BE1931" d="M6.699 32h22.602C33.383 28.7 35 23.658 35 18c0-5.658-1.616-10.7-5.698-14H6.698C2.615 7.3 1 12.342 1 18c0 5.658 1.616 10.7 5.699 14z"/><path d="M1.301 22c.108.682.245 1.35.415 2h32.568c.17-.65.307-1.318.415-2H1.301zm-.229-2h33.855c.049-.657.073-1.324.073-2H1c0 .676.024 1.343.072 2zm31.605 8c.363-.64.684-1.306.956-2H2.367c.272.694.593 1.36.956 2h29.354zM2.366 10c-.254.646-.471 1.313-.651 2h32.569c-.18-.687-.397-1.354-.651-2H2.366zm-1.065 4c-.104.654-.179 1.321-.229 2h33.855c-.049-.679-.125-1.346-.229-2H1.301zm30.014 16H4.685c.591.721 1.26 1.391 2.014 2h22.602c.754-.609 1.422-1.279 2.014-2zM4.685 6c-.515.627-.964 1.298-1.363 2h29.356c-.398-.702-.849-1.373-1.362-2H4.685z" fill="#DD2E44"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f400.svg b/games/sound-soup/assets/img/1f400.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#EA596E" d="M30 36H6c-.552 0-1-.447-1-1s.448-1 1-1h24c2.542 0 4-1.367 4-3.75 0-1.537-1.643-3.25-4-3.25h-7c-.553 0-1-.447-1-1s.447-1 1-1h7c3.589 0 6 2.715 6 5.25 0 3.493-2.355 5.75-6 5.75z"/><path fill="#66757F" d="M12 24.5c0-3.037 3.604-5.938 1-7.5-3.407-2.044-6.879 4.293-9 5-3 1-4 3.407-4 5 0 1.838 1.986 1.725 5 2 1.906.175 5.594.125 9 1 2.942.756-2-2.462-2-5.5z"/><path fill="#66757F" d="M33 23.5C33 15 29.247 11 24 11c-10 0-14 7-14 14 0 5.247 6.753 6 12 6s11-2.253 11-7.5z"/><path fill="#66757F" d="M9.781 17.844c0-2.594-2.375-3.219-1.188-5.031.706-1.078 2.588-.635 3.051-.582-.037 1.332 1.605 1.875 2.054 1.049.378.372.71.83.962 1.361.914 1.923-.461 4.49-1.958 5.201-1.494.711-2.921.132-2.921-1.998z"/><path fill="#E6AAAA" d="M13.633 16.14c.084 1.279-.481 2.67-1.396 2.915-.914.246-1.277-.728-1.608-1.961-.331-1.233-.95-2.686-.036-2.932.915-.246 2.931.317 3.04 1.978z"/><circle fill="#292F33" cx="8" cy="23" r="1"/><path fill="#66757F" d="M10.594 18.312c-1.26-1.717-2.834-1.977-4.49-1.979-.729 0-.687.479-.104.542 1.076.115 1.963.406 2.077.667.076.175-.702-.042-1.431-.042-1.167 0-1.042.298-.167.521 1.875.479 1.833 1.219 2.896 1.188 1.063-.033 1.219-.897 1.219-.897zm15.848-6.987c-1.65-.616-3.942-2.241-6.192-1.991-.751.083-.462.519 0 .625.609.141 1.438.704 1.731.933.281.219-2.314-.558-4.065.109-.872.332-.514.562.031.5.816-.094 1.609.172 2.641.797 1.031.624 6.932-.571 5.854-.973z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f409.svg b/games/sound-soup/assets/img/1f409.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#3E721D" d="M12.434 29.833c.626-6.708-4.417-7.542-6.417-6.083-1.097.8-1.353 2.323-.479 1.521 1.542-1.416 2.083-.375.917.375s-1.375 2.145-.083 1.188c1.292-.958 1.646-.334.646.895-.605.744.042 1.438 1.167-.062.938-1.251 3.2-1.294 2.662 2.99-.222 1.756 1.453.608 1.587-.824zm7.941-21.022c-.583-3.5-1.125-5.248-4.625-5.832s-6.417 1.75-6.417 1.75.583-3.5 2.333-4.667c.686-.458 1.167 1.75 1.75 1.75s1.167-1.75 2.917-1.75c.583 0 .583 1.75 1.167 1.75.583 0 2.243-.577 2.333 0 .126.812-.167 1.729.292 2.104s1.553-.148 1.901.489c.349.636-.61 1.553-.526 1.97s.719.583.526 1.375-.65.833-.692 1.417.885 1.081.692 1.686c-.192.606-.651.688-.859 1.459-.208.771.541.649.333 1.439-.208.79-.958.991-1.208 1.766-.25.774.666.941.208 1.691s-1.291.875-1.333 1.333.209.818.042 1.555c-.167.736-1.126.362-1.209.945s.209.875.209 1.583-.709.834-.625 1.542.75.167 1.167 1-.249 1.583.209 2.083 1.083-.667 1.708-.25c.625.417.677 1.25 1.359 1.375s.891-1.292 1.391-1.25 1.625.709 2.208.417.541-1.459 1-1.959 1.042-.041 1.458-.583-.145-1.175-.062-1.967.854-1.241.812-1.866-.667-.625-.917-1.292.458-1.25.208-1.875-1.332-.833-1.291-1.458.459-1.333.25-2.042-1.084-1.166-1.042-1.707.499-1.25.583-1.646-.749-.812-.666-1.479.624-.621.832-1.223c.208-.602-.749-.901-.249-1.672s.751-.27 1.167-.688c.416-.417-.001-1.334.416-1.542.417-.208 1.25-.042 1.667-.333s.417-.708.875-.875c.458-.167 1.042.542 1.417.542s1.041-.708 1.541-.542c.5.167 1.584 1.333.917 1.688s-5.751.605-5.792 2.938 2.793 12.917 1.959 15.583-4.291 8.334-8.25 7.25c-3.959-1.084-8.667-3.501-7.542-7.209 1.125-3.709 4.749-11.296 5.458-14.773z"/><path fill="#77B255" d="M21 7.897c0 3.978-2.382 8.144-5.833 7.566-5.323-.89-5.606-2.587-6.417-1.546-2.917 3.743-4.644-.485-5.307-1.186C3.276 12.555 0 11.59 0 9.744c0-1.197 1.75-2.418 2.917-1.231 1.722-.043 8.167-6.156 12.25-6.156C19.25 2.356 21 5.435 21 7.897z"/><path fill="#292F33" d="M14.583 7.062c0 .644-.523 1.167-1.167 1.167s-1.167-.523-1.167-1.167.523-1.167 1.167-1.167c.645.001 1.167.523 1.167 1.167z"/><path fill="#3E721D" d="M2.917 10.271c0 .483-.392.292-.875.292s-.875.191-.875-.292.392-.875.875-.875.875.392.875.875z"/><path fill="#FFF" d="M11.083 11.144c0 .645-.392.583-.875.583s-.875.061-.875-.583c0-.644.392-2.333.875-2.333s.875 1.689.875 2.333zm-2.333.583c0 .645-.392.583-.875.583-.483.001-.875.062-.875-.583 0-.644.392-2.333.875-2.333s.875 1.689.875 2.333z"/><path fill="#3E721D" d="M11.001 11.152c-3.095.442-6.215 1.224-7.558 1.579.167.177.403.579.709 1.021 1.472-.38 4.253-1.051 7.015-1.444.319-.046.54-.342.495-.661-.047-.32-.344-.542-.661-.495z"/><path fill="#77B255" d="M20.946 8.937c0 4.375-1.714 8.201-2.946 11.17-1.333 3.212-1 9 4 9s6.511-3.191 7-5c1.358-5.021-2-8-2-13 0-9 8-7 8-6s-6.934 1.374-3 9S36 36 22 36 8 27.107 10 23.107c1.416-2.832 4-7.107.5-9.045-2.282-1.263 10.446-5.125 10.446-5.125z"/><path fill="#3E721D" d="M11.335 7.771c-.256 0-.512-.098-.707-.293-.391-.391-.391-1.023 0-1.414.083-.083 2.081-2.043 5.374-2.043.552 0 1 .448 1 1s-.448 1-1 1c-2.435 0-3.945 1.442-3.96 1.457-.195.195-.451.293-.707.293z"/><path fill="#5C913B" d="M10.708 25.333c.627-6.708-5.417-7.542-7.417-6.083-1.097.8-1.353 2.323-.479 1.521 1.542-1.416 2.083-.375.917.375-1.167.75-1.375 2.146-.083 1.188s1.646-.334.646.895c-.605.744.042 1.438 1.167-.062.938-1.251 4.2-1.294 3.662 2.99-.222 1.756 1.454.608 1.587-.824z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f40f.svg b/games/sound-soup/assets/img/1f40f.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#292F33" d="M25 35c-2.75 0-2-6-2-6 0-1.104 2.646-2 3.75-2S30 26.896 30 28c0 0-2.25 7-5 7z"/><path fill="#66757F" d="M28 36c-2.75 0-2-6-2-6 0-1.104 2.646-2 3.75-2S33 27.896 33 29c0 0-2.25 7-5 7z"/><path fill="#292F33" d="M14 35c-2.75 0-4-6-4-6 0-1.104 2.646-2 3.75-2S17 26.896 17 28c0 0-.25 7-3 7z"/><path fill="#66757F" d="M17 36c-2.75 0-4-6-4-6 0-1.104 2.646-2 3.75-2S20 27.896 20 29c0 0-.25 7-3 7z"/><path fill="#E1E8ED" d="M35.75 21.384c0 7.782-4.495 11.408-14.519 11.408-10.023 0-13.481-3.626-13.481-11.408 0-7.783 3.458-11.407 13.481-11.407 10.024 0 14.519 3.624 14.519 11.407z"/><path fill="#99AAB5" d="M17 17.667C17 27 12.345 29 8.042 29 3.738 29 1 22.36 1 17.667 1 12.973 3.738 12 8.042 12c4.303 0 8.958.973 8.958 5.667z"/><circle fill="#292F33" cx="4.5" cy="20.5" r="1.5"/><path fill="#E1E8ED" d="M15.75 12.75C14 16 13.622 15.356 8.622 17.356 5.545 18.587 0 18.25 0 13.5 0 11.567 3.687 11 7 11c3.314 0 10.409-1.332 8.75 1.75z"/><path fill="#FFCC4D" d="M16 13.696c3.353 2.427 5.43 6.139 3.724 11.089-1.367 3.967-6.816 3.115-9.616 1.127-2.799-1.988-2.643-4.792-1.733-6.291.909-1.497 4.158-1.229 6.024.097.934.663.287 1.559-.721 1.068-2.292-1.118-3.037 1.443-1.246 2.882 1.266 1.016 3.275 1.049 3.655-1.01.671-3.63-3.505-6.274-7.465-5.302C11 16 13.161 11.641 16 13.696z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f410.svg b/games/sound-soup/assets/img/1f410.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#FFAC33" d="M7.44 7.503c-1-4 3.687-6 8-4 .907.421.948 1.316 0 1-3-1-6 1-4 4 1.109 1.664-3.233 2.068-4-1z"/><path fill="#FFCC4D" d="M6.136 5.785c-1-4 3.687-6 8-4 .907.421.949 1.316 0 1-3-1-6 1-4 4 1.11 1.664-3.233 2.067-4-1z"/><path fill="#E1E8ED" d="M5 14.785c0 4-2 4.827-2 4 0-2-1 0-1-1v-3c0-1.657.671-3 1.5-3s1.5 1.343 1.5 3z"/><path fill="#CCD6DD" d="M35.159 10.49c-.68-1.643-2.313-2.705-4.159-2.705-.553 0-1 .448-1 1s.447 1 1 1c1.034 0 1.941.577 2.312 1.471.341.824.168 1.758-.455 2.647-.984-1.506-2.602-2.618-4.856-2.618-2.391 0-7.279.714-10.828 1.289-.052-.094-.105-.188-.172-.289-2-3-4-8.157-7-8.157-4 0-10 4.986-10 9.157 0 2.544 5.738 2.929 7.486 2.988.697 1.43 1.414 2.934 2.232 4.33.066.205.155.429.282.683 3 6 3.119 14.5 4.5 14.5s2.5-4.857 2.5-9c0-.151-.004-.299-.007-.447 3.126.649 6.607.322 9.677-.61 1.448 5.045 1.77 10.058 2.83 10.058 1.342 0 2.433-8.818 2.494-13.12C33.316 21.226 34 19.51 34 17.785c0-.605-.086-1.23-.248-1.843 1.614-1.644 2.143-3.676 1.407-5.452z"/><circle fill="#292F33" cx="7" cy="9.285" r="1"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f411.svg b/games/sound-soup/assets/img/1f411.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#FFAC33" d="M11.983 3.562c4.543-.262 7.824 1.597 8.089 6.826.215 4.19-5.184 6.79-8.046 6.879-2.462.077-1.353-1.852.61-2.334 1.576-.387 4.124-2.413 3.602-4.44-.597-2.32-4.315-2.684-6.351.292-.798 1.167-3.554.873-4.096-.434-.958-2.309 1.211-6.502 6.192-6.789z"/><path fill="#E1E8ED" d="M35.75 21.384c0-7.783-4.495-11.407-14.519-11.407-1.087 0-2.083.051-3.018.137-1.36-2.423-4.845-2.828-8.213-2.828-4.304 0-10 6.145-10 10.839 0 4.608 3.606 4.866 7.811 4.874.233 3.245 1.226 5.647 3.249 7.26C11.337 31.409 12.594 36 15 36c1.353 0 2.099-1.695 2.51-3.417 1.128.136 2.359.209 3.722.209 1.733 0 3.288-.116 4.695-.335C26.017 34.114 26.458 36 28 36c2.317 0 4.273-4.956 4.834-6.521 1.969-1.87 2.916-4.545 2.916-8.095z"/><circle fill="#292F33" cx="4.5" cy="15.786" r="1.5"/><path fill="#FFCC4D" d="M17.562 4.339c4.312 1.455 5.74 4.148 4.034 9.099-1.367 3.967-7.345 4.361-10.034 3.375C9.25 15.964 11 14.589 13 14.875c1.606.23 4.727-.698 5-2.773C18.312 9.727 15 8 12 10c-1.177.785-5.774.43-5.5-.958.638-3.226 6.335-6.298 11.062-4.703z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f414.svg b/games/sound-soup/assets/img/1f414.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#E1E8ED" d="M20.143 32.215c0 1.183-.959 2.143-2.143 2.143s-2.143-.96-2.143-2.143c0-1.184.96-2.144 2.143-2.144s2.143.96 2.143 2.144z"/><path fill="#E1E8ED" d="M27.643 15.643C27.643 10.317 23.324 6 18 6c-5.326 0-9.643 4.317-9.643 9.643 0 5.357-1.071 16.572-1.071 16.572 0 1.183.959 2.143 2.143 2.143 1.183 0 2.143-.96 2.143-2.143 0 1.183.96 2.143 2.143 2.143 1.183 0 2.142-.96 2.142-2.143h4.286c0 1.183.96 2.143 2.144 2.143 1.183 0 2.143-.96 2.143-2.143 0 1.183.959 2.143 2.143 2.143 1.184 0 2.143-.96 2.143-2.143-.002 0-1.073-11.215-1.073-16.572z"/><path fill="#DD2E44" d="M20 3c0 2.209-1.447 6-2 6-.552 0-2-3.791-2-6s1.448-3 2-3c.553 0 2 .791 2 3zm-4.365 30c0-2.612 1.711-8 2.365-8 .653 0 2.365 5.388 2.365 8S18.652 36 18 36c-.654 0-2.365-.388-2.365-3z"/><ellipse fill="#F4900C" cx="18" cy="22.5" rx="7" ry="4.5"/><path fill="#FFAC33" d="M25 22.5c0 1.5-14 1.5-14 0s4.791-4.5 7-4.5 7 3 7 4.5z"/><circle fill="#292F33" cx="11.5" cy="18.5" r="1.5"/><circle fill="#292F33" cx="24.5" cy="18.5" r="1.5"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f419.svg b/games/sound-soup/assets/img/1f419.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#553788" d="M10 12c3 5 0 10.692-3 9.692s-4 2-1 3 9.465-.465 13-4c1-1 2-1 2-1L10 12z"/><path fill="#553788" d="M26 12c-3 5 0 10.692 3 9.692s4 2 1 3-9.465-.465-13-4c-1-1-2-1-2-1L26 12z"/><path fill="#744EAA" d="M30.188 16c-3 5 0 10.692 3 9.692s4 2 1 3-9.465-.465-13-4c-1-1-2-1-2-1l11-7.692zM5.812 16c3 5 0 10.692-3 9.692s-4 2-1 3 9.465-.465 13-4c1-1 2-1 2-1L5.812 16z"/><path fill="#9266CC" d="M33.188 31.375c-2.729.91-6.425-5.626-4.812-10.578C30.022 17.554 31 13.94 31 11c0-7.18-5.82-11-13-11S5 3.82 5 11c0 2.94.978 6.554 2.624 9.797 1.613 4.952-2.083 11.488-4.812 10.578-3-1-4 3-1 4s8.31-.627 12-4c2.189-2 4.189-2 4.189-2s2 0 4.188 2c3.69 3.373 9 5 12 4s1.999-5-1.001-4z"/><circle fill="#292F33" cx="14" cy="21" r="2"/><circle fill="#292F33" cx="22" cy="21" r="2"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f41a.svg b/games/sound-soup/assets/img/1f41a.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#CCD6DD" d="M29.049 7.033c-.07-.07-.141-.126-.211-.188-1.08-1.087-2.497-1.564-4.008-1.373l-3.812.483c-.382-3.003-2.25-4.446-4.351-4.181l-4.24.536C12.075.767 11.011.034 9.827.185L5.033.79C3.71.958 2.773 2.438 2.94 4.097l.606 6.007c.152 1.501 1.425 2.591 2.403 2.693l.514 5.093c.258 2.553 2.509 4.365 4.117 4.38l.498 4.926c.164 1.622.928 3.027 1.917 4.063.23.339.471.656.737.922 4.423 4.423 12.183 5.064 16.607.64 4.42-4.422 3.133-21.365-1.29-25.788z"/><path fill="#66757F" d="M30.609 14.396c.801 2.371 1.545 11.57-1.532 15.543-3.079 3.972-8.043-1.389-5.312-6.205 2.73-4.814 6.036-11.733 6.844-9.338z"/><path fill="#99AAB5" d="M21.018 5.954c.009.072.028.133.036.206.318 3.154-7.686 15.799-10.204 16.119-.085.011-.181-.008-.271-.008l.401 3.971c3.694-1.936 12.412-15.71 13.087-20.673l-3.049.385zm-8.59-3.645c.042.182.079.369.1.574.197 1.942-4.732 9.725-6.28 9.923-.094.012-.196.001-.299-.009l.353 3.502c2.78-2.096 8.093-10.489 9.094-14.365l-2.968.375z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f41b.svg b/games/sound-soup/assets/img/1f41b.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#AA8DD8" d="M7.415 4c1-1 .25-4-1.438-4s-1.562 2-.562 2 1 2 1 3 1-1 1-1zM3.232 5.72c1-1 .25-4-1.438-4s-1.562 2-.562 2 1 2 1 3 1-1 1-1z"/><path fill="#744EAA" d="M29.607 32.856c.189.808 1.227 2.28 2.032 2.091.806-.19 1.077-1.971.888-2.777-.189-.808-.998-1.307-1.804-1.117-.805.19-1.306.997-1.116 1.803zm-5.434.649c.003.83.681 2.498 1.509 2.495.828-.004 1.494-1.677 1.491-2.506-.003-.829-.677-1.497-1.505-1.495-.828.004-1.498.677-1.495 1.506zm-5.271-2.446c-.326.764-.365 2.563.396 2.89.763.324 2.037-.947 2.363-1.71.325-.762-.028-1.643-.789-1.97-.762-.325-1.643.027-1.97.79zm-5.031-11.04c-.812.172-2.308 1.173-2.139 1.983.173.81 1.945 1.124 2.757.953.811-.171 1.329-.967 1.16-1.777-.172-.81-.965-1.33-1.778-1.159zm-2.037-5c-.812.172-2.308 1.173-2.139 1.983.173.81 1.945 1.124 2.757.953.811-.171 1.329-.967 1.16-1.777-.172-.81-.966-1.33-1.778-1.159z"/><path fill="#9266CC" d="M27.244 34c2.831 0 4.926-.877 5.266-1.027 2.271-1.01 3.293-3.669 2.284-5.939-.999-2.25-3.616-3.275-5.872-2.314-.133.05-1.793.649-3.343-.072-1.851-.861-2.995-3.309-3.628-5.209-.144-.43-.385-1.274-.686-2.316-1.866-6.471-2.832-8.285-3.983-9.82C13.571 2.352 7.69 2.007 4.799 2.587c-2.437.487-4.018 2.858-3.53 5.295.488 2.438 2.86 4.02 5.296 3.53.162-.026 2.204-.306 3.424 1.169.643 1.287 2.08 5.128 2.63 7.034.346 1.199.629 2.171.794 2.667 1.773 5.317 4.666 8.893 8.603 10.629C23.868 33.729 25.67 34 27.244 34z"/><circle fill="#FFF" cx="6.682" cy="5" r="1"/><path d="M30.923 32.964c-.089 0-.18-.012-.269-.036-.532-.148-.844-.699-.696-1.231.757-2.722-.77-4.542-.785-4.56-.359-.419-.312-1.051.107-1.41.418-.359 1.051-.312 1.41.107.092.107 2.234 2.66 1.195 6.397-.122.443-.525.733-.962.733zm-7.199-.563c-.245 0-.49-.089-.684-.27-.403-.378-.424-1.011-.047-1.414 1.485-1.587 1.453-4.193 1.452-4.22-.016-.552.419-1.012.972-1.028.542-.005 1.013.419 1.028.972.004.139.073 3.437-1.991 5.644-.197.21-.464.316-.73.316zm-8.542-8.696c-.48 0-.904-.347-.985-.837-.09-.544.278-1.06.823-1.149 3.074-.51 4.774-2.377 4.791-2.396.365-.41.998-.449 1.409-.084.412.365.453.993.091 1.406-.087.099-2.156 2.415-5.964 3.046-.056.01-.111.014-.165.014zm2.921 4.857c-.425 0-.819-.272-.954-.698-.166-.526.126-1.089.652-1.255 2.693-.853 3.88-2.83 3.892-2.85.28-.477.892-.637 1.368-.357.477.279.636.893.357 1.368-.063.108-1.595 2.663-5.013 3.744-.101.033-.203.048-.302.048zm-4.619-10.67c-.496 0-.927-.369-.991-.875-.069-.548.319-1.048.867-1.118 2.689-.34 4.657-1.971 4.676-1.988.422-.354 1.053-.303 1.408.119.356.421.305 1.05-.115 1.407-.097.083-2.42 2.029-5.718 2.446-.043.006-.085.009-.127.009zm-1.821-4.767c-.425 0-.819-.273-.954-.701-.165-.527.127-1.088.654-1.254 3.13-.983 4.253-2.692 4.299-2.764.295-.462.911-.605 1.375-.312.465.292.611.899.324 1.366-.06.097-1.506 2.396-5.399 3.619-.099.031-.2.046-.299.046z" fill="#744EAA"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f41c.svg b/games/sound-soup/assets/img/1f41c.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#31373D" d="M7.5 24.516c-.256 0-.512-.098-.707-.293-.391-.391-.391-1.024 0-1.414L22.81 6.793c.391-.391 1.023-.391 1.414 0s.391 1.024 0 1.414L8.207 24.223c-.196.195-.452.293-.707.293zm8.282-11.738c-.219 0-.438-.071-.623-.218-.431-.344-.502-.971-.161-1.403.113-.144 2.571-3.361.971-7.572-.196-.517.063-1.094.579-1.29.518-.196 1.094.064 1.29.579 2.017 5.307-1.139 9.357-1.274 9.527-.197.248-.488.377-.782.377zm3.89 3.888c-.221 0-.441-.072-.626-.221-.429-.345-.499-.97-.156-1.4.138-.172 3.435-4.197 9.323-2.726.536.134.862.677.727 1.213-.134.535-.679.861-1.212.728-4.623-1.157-7.172 1.905-7.278 2.036-.197.243-.487.37-.778.37zM6.454 18.511c-1.096 0-2.301-.186-3.58-.671-.517-.196-.776-.773-.58-1.29.195-.517.775-.775 1.29-.58 4.219 1.604 7.439-.866 7.574-.972.434-.338 1.062-.266 1.403.166.342.432.271 1.058-.159 1.4-.129.103-2.495 1.947-5.948 1.947zm1.564-8.066c-1.829 0-4.968-.482-7.542-3.332-.37-.41-.338-1.042.072-1.412.411-.371 1.043-.337 1.412.072C4.984 9.122 8.976 8.392 9.146 8.36c.538-.105 1.066.246 1.173.787.107.54-.241 1.064-.781 1.174-.069.014-.644.124-1.52.124z"/><path fill="#31373D" d="M9.341 10.341c-.066 0-.132-.006-.199-.02-.541-.109-.89-.635-.783-1.175.034-.173.76-4.163-2.587-7.185C5.362 1.591 5.33.959 5.7.549 6.069.14 6.702.106 7.113.477c4.214 3.806 3.251 8.849 3.207 9.062-.096.475-.513.802-.979.802zm3.948 18.63c-.448 0-.856-.303-.969-.758-1.473-5.895 2.554-9.186 2.726-9.323.431-.344 1.061-.273 1.405.159.343.431.273 1.058-.156 1.402-.14.114-3.187 2.667-2.035 7.277.134.535-.192 1.078-.728 1.212-.081.021-.163.031-.243.031z"/><path fill="#31373D" d="M29.844 19.167c-2.96-2.959-6.095-3.569-8.915-2.069-.157-1.874-1.166-4.007-2.521-5.363-1.488-1.488-3.008-1.653-4.405-1.044 1.058-2.465.6-5.43-.826-6.856-1.843-1.843-4.594-.411-7.174 2.168-2.58 2.58-4.012 5.331-2.168 7.174 1.425 1.426 4.391 1.883 6.856.826-.61 1.397-.444 2.918 1.044 4.405 1.354 1.354 3.489 2.363 5.363 2.521-1.5 2.82-.891 5.954 2.069 8.915 3.75 3.749 12.204 5.527 14.204 3.527s.223-10.455-3.527-14.204z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f41d.svg b/games/sound-soup/assets/img/1f41d.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#31373D" d="M27.816 23.464c.299-1.148.465-2.318.465-3.464 0-4.161-2.122-6.779-5.258-8.035.417-1.008.665-2.108.665-3.2 0-1.581-.495-2.498-1.315-3.032 1.091-.482 2.517-1.5 3.027-2.011.781-.781.94-1.889.354-2.475-.586-.586-1.693-.428-2.475.354-.611.611-1.948 2.53-2.223 3.619C20.172 5.025 19.126 5 18 5c-1.125 0-2.172.025-3.055.219-.275-1.089-1.612-3.007-2.223-3.619-.781-.781-1.889-.94-2.475-.354-.585.587-.427 1.694.354 2.476.511.511 1.937 1.529 3.027 2.011-.821.533-1.316 1.451-1.316 3.032 0 1.093.248 2.192.665 3.2C9.841 13.221 7.719 15.839 7.719 20c0 1.146.167 2.317.465 3.464v.023l.006-.003c1.209 4.621 4.643 8.847 8.812 9.44L17 33c0 1.657.448 3 1 3 .553 0 1-1.343 1-3 0-.026-.002-.049-.003-.075 4.169-.593 7.604-4.819 8.813-9.44l.006.003v-.024z"/><path d="M14.624 19h6.752c-1.462-1.424-2.615-2.881-3.106-4h-.54c-.491 1.119-1.644 2.576-3.106 4zm-5.378 4c-.361.187-.715.349-1.062.488.32 1.219.799 2.407 1.401 3.512h16.83c.602-1.105 1.082-2.293 1.401-3.512-.347-.139-.701-.301-1.063-.488H9.246zm3.593 8c1.518 1.234 3.278 2 5.161 2 1.884 0 3.643-.766 5.162-2H12.839z" fill="#FFCC4D"/><path fill="#CCD6DD" d="M18 13.505c0 2.647-7.858 8.584-12 8.584s-6-2.443-6-5.09c0-2.646 1.858-4.495 6-4.495 4.143.001 12-1.644 12 1.001zm0 0c0 2.647 7.857 8.584 12 8.584s6-2.443 6-5.09c0-2.646-1.857-4.495-6-4.495s-12-1.644-12 1.001z"/><path fill="#99AAB5" d="M2 19c-.552 0-1-.447-1-1 0-.551.446-.999.998-1 .048 0 4.949-.07 13.596-3.914.505-.227 1.096.002 1.32.507.225.505-.003 1.096-.507 1.32C7.317 18.953 2.213 19 2 19zm32 0c-.213 0-5.316-.047-14.406-4.086-.505-.224-.731-.815-.508-1.32.225-.504.817-.732 1.32-.507C29.054 16.93 33.954 17 34.003 17c.551.003.997.452.996 1.003-.002.551-.448.997-.999.997z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f41f.svg b/games/sound-soup/assets/img/1f41f.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#3B88C3" d="M32.153 24c0-1 1.523-6.212 3.047-7.735 1.522-1.523 0-3.166-1.523-3.166-3.405 0-9.139 6.901-9.139 10.901 0 5 5.733 10.424 9.139 10.424 1.523 0 3.046-1.404 1.523-2.928C33.677 29.974 32.153 26 32.153 24z"/><path fill="#3B88C3" d="M9.021 14.384c0-3.046 1.497-6.093 3.02-6.093 4.569 0 13.322 4.823 14.845 12.439 1.524 7.616-17.865-6.346-17.865-6.346zm4.854 18.278c1.523 1.523 4.57 3.047 7.617 3.047 3.046 0-3.111-4.189-1.523-6.092 2.18-2.617-6.094 3.045-6.094 3.045z"/><path d="M2.071 28.727c.761-2.285.19-3.935-1.143-5.584-1.333-1.651 3.872-1.904 5.585.381s5.713 6.281 2.158 6.22c-3.553-.065-6.6-1.017-6.6-1.017z"/><path fill="#55ACEE" d="M.168 23.488c.959.874 7.223 4.309 7.165 5.137-.058.828-2.279-.088-3.105-.279-1.485-.342-1.905-.598-2.317-.526-.84.321-.554 1.201-.242 1.704 1.498 2.61 7.286 4.662 12.16 4.662 8.412 0 16.802-7.615 16.802-10.662 0-3.046-9.345-10.663-17.757-10.663C4.483 12.86.18 18.922.168 23.488z"/><path d="M7 17c1.104 0 2 .894 2 2 0 1.105-.896 2-2 2-1.105 0-2-.896-2-2 0-1.106.895-2 2-2z"/><path fill="#269" d="M15.08 29.98c-.156 0-.314-.036-.462-.113-.49-.256-.679-.86-.423-1.35 1.585-3.034 2.218-5.768.154-9.243-.282-.475-.126-1.088.349-1.371.475-.283 1.088-.124 1.371.349 2.693 4.535 1.46 8.202-.102 11.191-.178.342-.527.537-.887.537z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f422.svg b/games/sound-soup/assets/img/1f422.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#77B255" d="M9.842 19.922c0 9.842 6.575 9.673 5.158 10.078-7 2-8.803-7.618-9.464-7.618-2.378 0-5.536-.423-5.536-2.46C0 17.883 2.46 15 6.151 15c2.379 0 3.691 2.883 3.691 4.922zM36 28.638c0 1.104-3.518-.741-5 0-2 1-2-.896-2-2s1.343-1 3-1 4 1.895 4 3z"/><path fill="#77B255" d="M16.715 33.143c0 2.761-1.279 2.857-2.857 2.857S11 35.903 11 33.143c0-.489.085-1.029.234-1.587.69-2.59 2.754-5.556 4.052-5.556 1.578 0 1.429 4.382 1.429 7.143zm8.571 0c0 2.761 1.278 2.857 2.856 2.857C29.721 36 31 35.903 31 33.143c0-.489-.085-1.029-.234-1.587-.691-2.59-2.754-5.556-4.052-5.556-1.578 0-1.428 4.382-1.428 7.143z"/><path fill="#3E721D" d="M32 27c0 4-5.149 4-11.5 4S9 31 9 27c0-6.627 5.149-12 11.5-12S32 20.373 32 27z"/><circle fill="#292F33" cx="5" cy="18" r="1"/><path fill="#5C913B" d="M23.667 25.1c0 3.591-1.418 3.9-3.167 3.9s-3.167-.31-3.167-3.9S18.75 17 20.5 17s3.167 4.51 3.167 8.1zM30 24c.871 3.482-.784 4-2.533 4-1.749 0-2.533.69-2.533-2.9s-1.116-6.5.633-6.5C27.315 18.6 29 20 30 24zm-13.933 1.1c0 3.591-.785 2.9-2.534 2.9s-3.404-.518-2.533-4c1-4 3.251-5.4 5-5.4 1.75 0 .067 2.91.067 6.5z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f423.svg b/games/sound-soup/assets/img/1f423.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#99AAB5" d="M2.75 19.333L5 16l3 3 4-2 3 3 4-6 4 3 3-1 2 3 3-3 2 4-2 7-11 5-11-2-6-6z"/><path fill="#FFCC4D" d="M29.956 14.712c.021-.287.044-.574.044-.866 0-5.943-4.324-10.864-9.996-11.819 1.189-.26 2.031-.509.996-1.027-1.115-.558-2.23.129-2.999.846h-.002C17.23 1.129 16.116.442 15 1c-1.035.518-.193.767.996 1.026C10.325 2.981 6 7.902 6 13.846c0 .292.023.579.044.867C5.562 21.313-.259 31 18 31s12.438-9.687 11.956-16.288z"/><path fill="#F4900C" d="M21 14c0 1.657-1.343 2-3 2s-3-.343-3-2 1.343-3 3-3 3 1.343 3 3z"/><circle fill="#662113" cx="11.5" cy="11.5" r="1.5"/><circle fill="#662113" cx="24.5" cy="11.5" r="1.5"/><path fill="#E1E8ED" d="M31 25l-3-1-4 6-3-5-2 2-4-2-3 4-3-6-3 2-3.294-7.765C2.249 18.584 2 20.016 2 21.5 2 29.509 9.164 36 18 36s16-6.491 16-14.5c0-1.244-.191-2.444-.517-3.597L31 25z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f424.svg b/games/sound-soup/assets/img/1f424.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#F4900C" d="M8.512 15.93c0 1.657 1.974 2-.512 2-2.485 0-6-1-6-2s1.526-3 4.012-3 2.5 1.343 2.5 3z"/><path fill="#FFAC33" d="M27.88 33.027c-.098-.18-.25-.302-.418-.391C25.865 30.931 27 28.93 27 28.93c0-.553 1-2 0-2l-1 1c-1 1-1 4-1 4h-2c-.553 0-1 .447-1 1 0 .553.447 1 1 1h1.107l-.222.12c-.486.263-.667.869-.404 1.355s.869.667 1.356.404l2.639-1.427c.486-.262.667-.868.404-1.355zm-7 0c-.098-.18-.25-.302-.418-.391C18.865 30.931 20 28.93 20 28.93c0-.553 1-2 0-2l-1 1c-1 1-1 4-1 4h-2c-.553 0-1 .447-1 1 0 .553.447 1 1 1h1.108l-.222.12c-.486.263-.667.869-.404 1.355s.869.667 1.356.404l2.639-1.427c.485-.262.666-.868.403-1.355z"/><path fill="#FFCC4D" d="M35.217 17.305c-1.342 0-7.904 4.613-11.553-5.375-.005-.014-.012-.024-.018-.038-.956-3.453-3.809-6.113-7.367-6.789l-.037-.042C18.389 3.684 19 4.707 19 3.93c0-1.179-2.083-1.303-4 .012-1.917-1.315-4-1.191-4-.012 0 .761.583-.208 2.623 1.044C8.788 5.417 5 9.479 5 14.43c0 4.478 3.103 8.223 7.273 9.227 1.003 4.171 4.748 7.273 9.227 7.273 3.953 0 6.934-1.678 8.99-3.999.555-.626.111.602.842.235 4.24-2.132 5.168-9.861 3.885-9.861z"/><circle fill="#662113" cx="12.5" cy="13.43" r="1.5"/><path fill="#FFAC33" d="M21.81 15.93s-2 10 10 10c1 0 0 2-4 2s-9.999-5-7-11c.633-1.265 1-1 1-1z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f425.svg b/games/sound-soup/assets/img/1f425.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#FFAC33" d="M17.38 33.098c-.097-.18-.25-.302-.417-.391C15.366 31.001 16.5 29 16.5 29c0-.553 1-2 0-2l-1 1c-1 1-1 4-1 4h-2c-.553 0-1 .447-1 1s.447 1 1 1h1.108l-.222.12c-.486.263-.667.869-.404 1.355s.869.667 1.356.404l2.639-1.427c.485-.262.666-.868.403-1.354zM23.5 32h-2s0-3-1-4l-1-1c-1 0 0 1.447 0 2 0 0 1.135 2.001-.462 3.707-.168.089-.32.211-.418.391-.263.486-.082 1.093.404 1.355l2.639 1.427c.486.263 1.093.082 1.356-.404.263-.486.082-1.093-.404-1.355L22.393 34H23.5c.553 0 1-.447 1-1s-.447-1-1-1z"/><path fill="#FFCC4D" d="M34 24c-1-2-3-5-4-10h-.008c.001-.052.008-.102.008-.154 0-5.943-4.324-10.864-9.996-11.819 1.189-.26 2.031-.509.996-1.027-1.115-.558-2.23.129-2.999.846h-.002C17.23 1.129 16.116.442 15 1c-1.035.518-.193.767.996 1.026C10.325 2.981 6 7.902 6 13.846c0 .052.007.102.008.154H6c-1 5-3 8-4 10-.447.895 0 3 2 2 1.191-.596 2.377-1.912 3.357-3.715C8.902 26.94 12.066 31 18 31c5.934 0 9.098-4.06 10.643-8.715.98 1.803 2.166 3.119 3.357 3.715 2 1 2.447-1.105 2-2z"/><path fill="#F4900C" d="M21 14c0 1.657-1.343 2-3 2s-3-.343-3-2 1.343-3 3-3 3 1.343 3 3z"/><circle fill="#662113" cx="11.5" cy="11.5" r="1.5"/><circle fill="#662113" cx="24.5" cy="11.5" r="1.5"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f426.svg b/games/sound-soup/assets/img/1f426.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#FFAC33" d="M8.916 12.88c-.111 1.652 1.768 3.126-.712 2.959-2.48-.167-7.836-2.533-7.768-3.53s3.708-2.757 6.188-2.59c2.48.166 2.404 1.508 2.292 3.161zm20.122 16.049c-.202-.032-.392.015-.564.095-2.325.232-3.225-1.885-3.225-1.885-.439-.336-.981-2.009-1.589-1.215l.187 1.402c.187 1.402 2.57 3.224 2.57 3.224l-1.215 1.589c-.336.439-.252 1.066.187 1.402.439.336 1.066.252 1.402-.187l.673-.88-.039.249c-.087.546.285 1.058.831 1.145.546.087 1.058-.285 1.145-.831l.47-2.963c.085-.546-.287-1.058-.833-1.145zm-6.278.623c-.196-.058-.39-.037-.572.018-2.335-.082-2.944-2.3-2.944-2.3-.39-.392-.703-2.123-1.412-1.417l-.003 1.414c-.003 1.414 2.115 3.539 2.115 3.539l-1.417 1.412c-.392.39-.393 1.023-.003 1.414.39.392 1.023.393 1.414.003l.785-.782-.073.242c-.159.529.141 1.086.67 1.246.529.159 1.087-.141 1.246-.67l.862-2.873c.162-.53-.138-1.087-.668-1.246z"/><path fill="#DD2E44" d="M35.009 6.729c-.383-.17-.758-.057-1.05.244-.054.056-4.225 6.306-14.532 4.944-.34-.045 3.139 11.968 3.199 11.962.124-.014 3.07-.368 6.14-2.553 2.818-2.005 6.284-5.991 6.797-13.598.028-.418-.171-.828-.554-.999z"/><path fill="#DD2E44" d="M34.477 21.108c-.204-.336-.59-.56-.979-.471-1.293.295-3.197.543-4.53.453-6.357-.428-9.361-4.129-9.392-4.16-.275-.282.466 11.552.816 11.576 9.194.62 13.862-6.027 14.057-6.31.222-.326.233-.751.028-1.088z"/><path fill="#DD2E44" d="M24.586 19.016c-.371 5.51 1.316 9.861-4.194 9.489-5.51-.371-10.145-4.92-9.774-10.431s14.34-4.568 13.968.942z"/><path fill="#DD2E44" d="M23.257 12.412c-.353 5.235-3.922 9.257-9.156 8.904-5.235-.353-9.193-4.882-8.84-10.117.353-5.235 4.832-8.444 10.067-8.091 4.001.269 8.24 4.683 7.929 9.304z"/><circle fill="#292F33" cx="10.67" cy="8.989" r="2"/><path fill="#A0041E" d="M18.179 16.645s7.63 5.648 12.387-4.459c.396-.842 1.685.793.099 4.162s-8.175 6.44-12.04 1.536c-.815-1.035-.446-1.239-.446-1.239z"/><path fill="#DD2E44" d="M15.327 3.107s6.246.254 7.798-.477.136 2.932-3.262 3.789-4.536-3.312-4.536-3.312z"/><path fill="#DD2E44" d="M17.428 5.788s4.501.136 6.054-.594.136 2.932-3.262 3.789c-3.399.857-2.792-3.195-2.792-3.195z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f427.svg b/games/sound-soup/assets/img/1f427.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#FFAC33" d="M28.068 31.355c-2.229 0-8.468.785-10.068 1.832-1.601-1.047-7.84-1.832-10.069-1.832-2.564 0-1.161 1.039-1.161 2.322C6.77 34.96 5.367 36 7.931 36c2.229 0 8.468-.785 10.069-1.832C19.601 35.215 25.84 36 28.068 36c2.565 0 1.161-1.04 1.161-2.322 0-1.283 1.405-2.323-1.161-2.323z"/><path fill="#292F33" d="M31.73 15.866c-1.25-2.499-3.152-4.995-4.942-6.723C24.337 3.711 20.759 0 18 0s-6.337 3.71-8.788 9.143c-1.791 1.729-3.693 4.224-4.943 6.724-2.438 4.876-3.116 11.426-2.078 11.944.873.437 2.324-1.552 4.208-5.082C6.667 33.604 13.446 33.678 18 33.678c4.553 0 11.333-.073 11.601-10.947 1.884 3.528 3.335 5.517 4.207 5.08 1.038-.519.361-7.069-2.078-11.945z"/><path fill="#F5F8FA" d="M21.675 6.943c-.85.607-2.172 1.186-3.675 1.186s-2.825-.578-3.675-1.185c-3.302 2.137-5.615 7.06-5.615 12.798 0 7.695 4.159 13.936 9.29 13.936 5.132 0 9.291-6.24 9.291-13.936 0-5.738-2.313-10.662-5.616-12.799z"/><path fill="#FFAC33" d="M28.452 6h-5.808C18.797 6 18 5.22 18 4.257c0-.962-.364-1.742 3.483-1.742C27.291 2.516 29.613 6 28.452 6z"/><path fill="#F5F8FA" d="M16.839 3.483c0 .642-.52 1.162-1.161 1.162-.642 0-1.161-.521-1.161-1.162 0-.641.52-1.161 1.161-1.161s1.161.52 1.161 1.161z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f428.svg b/games/sound-soup/assets/img/1f428.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#99AAB5" d="M36 13.533C36 8.867 32.866 7 29 7c-1.621 0-3.285.354-4.676 1.027C22.523 6.798 20.405 6.062 18 6.062s-4.523.736-6.324 1.965C10.285 7.354 8.621 7 7 7c-3.866 0-7 1.867-7 6.533 0 3.844 2.128 6.417 5.038 7.206-.043.536-.068 1.073-.068 1.611 0 5.729 4.52 7.675 8.581 8.326C14.649 31.487 16.232 32 18 32s3.351-.513 4.449-1.323c4.062-.651 8.581-2.597 8.581-8.326 0-.538-.025-1.075-.068-1.611 2.91-.79 5.038-3.363 5.038-7.207z"/><path fill="#66757F" d="M9.554 9.854C9.236 9.821 8.917 9.8 8.6 9.8 5.507 9.8 3 11.293 3 15.026c0 2.154.838 3.808 2.138 4.824.473-3.797 2.005-7.416 4.416-9.996zM27.4 9.8c-.317 0-.637.021-.955.054 2.412 2.58 3.943 6.199 4.416 9.997C32.162 18.834 33 17.181 33 15.026c0-3.733-2.507-5.226-5.6-5.226z"/><path fill="#292F33" d="M21.728 24.807C21.728 28.152 20 29 18 29s-3.727-.848-3.727-4.193c0-3.345 1.727-6.057 3.727-6.057s3.728 2.712 3.728 6.057z"/><circle fill="#292F33" cx="12.5" cy="19.875" r="1.5"/><circle fill="#292F33" cx="23.5" cy="19.875" r="1.5"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f42c.svg b/games/sound-soup/assets/img/1f42c.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#4292E0" d="M30.584 7.854c.27-1.729 1.028-3.908 2.975-5.854.704-.704.25-2-1-2 0 0-6.061.007-9.893 3.327C21.663 3.115 20.625 3 19.559 3c-8 0-12 4-14 12-.444 1.778-.865 1.399-3 3-1.195.896-2.117 3 1 3 3 0 5 .954 9 1 3.629.042 9.504-3.229 11.087-1.292 2.211 2.706 1.396 5.438.597 6.666-2.904 3.396-5.939.541-8.685-.374-3-1-1 1 0 2s1.312 4 0 6 3 0 5-3c.011-.017.022-.028.032-.045C28.392 31.5 34.559 25.936 34.559 18c0-3.918-1.515-7.474-3.975-10.146z"/><circle fill="#1F2326" cx="13.117" cy="14" r="2"/><path fill="#77BCF7" d="M10.396 21.896s4-.876 7.167-2.688c4.625-2.646 7.26-2.594 8.885-.823s1.99 6.594-2.885 9.677c2.604-2.75 1.146-8.349-2.014-7.588-8.153 1.964-8.903 1.547-11.153 1.422z"/><path fill="#4292E0" d="M19.383 17.744l-2.922 1.285c-.254.064-.433.3-.412.561.122 1.504.756 3.625 2.263 4.629 2.354 1.569 2.367 1.897 3 0 .768-2.303-.182-4.462-1.333-6.24-.127-.196-.37-.293-.596-.235z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f42e.svg b/games/sound-soup/assets/img/1f42e.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#99AAB5" d="M4 8s-4 2-4 11c0 0 6-1 7-3 0 0 2-12.25-3-8zm27.995.043s4 2 4 11c0 0-6-.999-7-2.999 0 0-2-12.251 3-8.001z"/><path fill="#FFE8B6" d="M1 1c-1.01.99 1 8 5 9s4-5 3-5C5 5 3.042-1 1 1zm34.297 0c1.011.99-1 8-5 9s-4-5-3-5c4 0 5.959-6 8-4z"/><path fill="#CCD6DD" d="M21.976 31h-7.951C8.488 31 4 26.512 4 20.976v-8.951C4 6.488 8.488 2 14.025 2h7.951C27.512 2 32 6.488 32 12.025v8.951C32 26.512 27.512 31 21.976 31z"/><path fill="#E6AAAA" d="M35 28c0 5.522-4.478 8-10 8H11c-5.523 0-10-2.478-10-8s4.477-10 10-10h14c5.522 0 10 4.478 10 10z"/><ellipse fill="#C1694F" cx="9.5" cy="26" rx="1.5" ry="3"/><ellipse fill="#C1694F" cx="26.5" cy="26" rx="1.5" ry="3"/><path fill="#272B2B" d="M11 12s0-2 2-2 2 2 2 2v2s0 2-2 2-2-2-2-2v-2zm10 0s0-2 2-2 2 2 2 2v2s0 2-2 2-2-2-2-2v-2z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f42f.svg b/games/sound-soup/assets/img/1f42f.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><circle fill="#FFCC4D" cx="7" cy="6" r="6"/><circle fill="#FFCC4D" cx="18" cy="30" r="6"/><circle fill="#DD2E44" cx="18" cy="30" r="4"/><circle fill="#FFCC4D" cx="29" cy="6" r="6"/><circle fill="#E6AAAA" cx="7" cy="6" r="4"/><circle fill="#E6AAAA" cx="29" cy="6" r="4"/><path fill="#FFCC4D" d="M34 22c0 7-4.923 7-4.923 7H6.923S2 29 2 22C2 22 3.231 0 18 0c14.77 0 16 22 16 22z"/><path fill="#272B2B" d="M11 17s0-2 2-2 2 2 2 2v2s0 2-2 2-2-2-2-2v-2zm10 0s0-2 2-2 2 2 2 2v2s0 2-2 2-2-2-2-2v-2z"/><path fill="#FFF" d="M23.678 23c-2.402 0-4.501.953-5.678 2.378C16.823 23.953 14.723 23 12.321 23 2 23 2.043 23.421 2 26.182c-.087 5.61 6.63 6.9 10.321 6.818 2.401-.053 4.502-.989 5.679-2.397 1.177 1.408 3.276 2.345 5.678 2.397 3.691.082 10.409-1.208 10.321-6.818-.043-2.761 0-3.182-10.321-3.182z"/><path fill="#272B2B" d="M33.66 25.242c.204.279.333.588.339.939.03 1.905-.745 3.303-1.915 4.327L26.999 31l6.661-5.758zM15 25c-1 1 2 4 3 4s4-3 3-4-5-1-6 0zM10 3c2.667 2 8 4 8 4s5.333-2 8-4l-8 1-8-1zm8-1s1.652-.62 3.576-1.514C20.48.178 19.295 0 18 0s-2.481.178-3.576.486C16.348 1.38 18 2 18 2zm-7 7c3 2 7 4 7 4s4-2 7-4l-7 1-7-1zm20.645 2.285L27 15l6.006.75c-.334-1.401-.777-2.928-1.361-4.465zm1.911 7.159L28 24h5.835c.102-.595.165-1.251.165-2 0 0-.081-1.43-.444-3.556zm-31.112 0C2.082 20.57 2 22 2 22c0 .748.063 1.405.165 2H8l-5.556-5.556zm-.105 6.798c-.204.279-.333.588-.339.94-.03 1.905.745 3.303 1.916 4.327L9 31l-6.661-5.758zM9 15l-4.644-3.715c-.584 1.537-1.028 3.064-1.361 4.466L9 15z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f430.svg b/games/sound-soup/assets/img/1f430.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#99AAB5" d="M33.799.005c-.467-.178-7.998 3.971-9.969 9.131-1.166 3.052-1.686 6.058-1.652 8.112C20.709 16.459 19.257 16 18 16s-2.709.458-4.178 1.249c.033-2.055-.486-5.061-1.652-8.112C10.2 3.977 2.668-.173 2.201.005c-.455.174 4.268 16.044 7.025 20.838C6.805 23.405 5 26.661 5 29.828c0 3.234 1.635 5.14 4 5.94 2.531.857 5-.94 9-.94s6.469 1.798 9 .94c2.365-.801 4-2.706 4-5.94 0-3.166-1.805-6.423-4.225-8.984C29.53 16.049 34.255.179 33.799.005z"/><path fill="#F4ABBA" d="M12.692 17.922c-.178-1.54-.68-3.55-1.457-5.584-1.534-4.016-5.686-7.245-6.049-7.107-.319.122 2.627 10.14 4.783 14.863.866-.824 1.786-1.563 2.723-2.172zm13.338 2.172c2.156-4.723 5.102-14.741 4.784-14.862-.363-.139-4.516 3.091-6.05 7.107-.777 2.034-1.279 4.043-1.457 5.583.937.609 1.857 1.348 2.723 2.172z"/><path fill="#CCD6DD" d="M25 30c0 2.762-3.06 5-6.834 5-3.773 0-6.833-2.238-6.833-5s3.06-5 6.833-5C21.94 25 25 27.238 25 30z"/><path fill="#FFF" d="M21 30.578c0 2.762-.238 3-3 3-2.761 0-3-.238-3-3 0-1 6-1 6 0z"/><circle fill="#292F33" cx="12.5" cy="24.328" r="1.5"/><circle fill="#292F33" cx="23.5" cy="24.328" r="1.5"/><path fill="#F4ABBA" d="M21 25.828c0 1.657-2 3-3 3s-3-1.343-3-3 6-1.657 6 0z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f431.svg b/games/sound-soup/assets/img/1f431.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#FFCC4D" d="M32.348 13.999s3.445-8.812 1.651-11.998c-.604-1.073-8 1.998-10.723 5.442 0 0-2.586-.86-5.276-.86s-5.276.86-5.276.86C10.001 3.999 2.605.928 2.001 2.001.207 5.187 3.652 13.999 3.652 13.999c-.897 1.722-1.233 4.345-1.555 7.16-.354 3.086.35 5.546.658 6.089.35.617 2.123 2.605 4.484 4.306 3.587 2.583 8.967 3.445 10.761 3.445s7.174-.861 10.761-3.445c2.361-1.701 4.134-3.689 4.484-4.306.308-.543 1.012-3.003.659-6.089-.324-2.814-.659-5.438-1.556-7.16z"/><path fill="#F18F26" d="M2.359 2.971c.2-.599 5.348 2.173 6.518 5.404 0 0-3.808 2.624-4.528 4.624 0 0-2.99-7.028-1.99-10.028z"/><path fill="#FFCC4D" d="M5.98 7.261c0-1.414 5.457 2.733 4.457 3.733s-1.255.72-2.255 1.72S5.98 8.261 5.98 7.261z"/><path fill="#F18F26" d="M33.641 2.971c-.2-.599-5.348 2.173-6.518 5.404 0 0 3.808 2.624 4.528 4.624 0 0 2.99-7.028 1.99-10.028z"/><path fill="#FFCC4D" d="M30.02 7.261c0-1.414-5.457 2.733-4.457 3.733s1.255.72 2.255 1.72 2.202-4.453 2.202-5.453z"/><path fill="#292F33" d="M14.001 20.001c0 1.105-.896 1.999-2 1.999s-2-.894-2-1.999c0-1.104.896-1.999 2-1.999s2 .896 2 1.999zm11.998 0c0 1.105-.896 1.999-2 1.999-1.105 0-2-.894-2-1.999 0-1.104.895-1.999 2-1.999s2 .896 2 1.999z"/><path fill="#FEE7B8" d="M2.201 30.458c-.148 0-.294-.065-.393-.19-.171-.217-.134-.531.083-.702.162-.127 4.02-3.12 10.648-2.605.275.021.481.261.46.536-.021.275-.257.501-.537.46-6.233-.474-9.915 2.366-9.951 2.395-.093.07-.202.106-.31.106zm8.868-4.663c-.049 0-.1-.007-.149-.022-4.79-1.497-8.737-.347-8.777-.336-.265.081-.543-.07-.623-.335-.079-.265.071-.543.335-.622.173-.052 4.286-1.247 9.362.338.264.083.411.363.328.627-.066.213-.263.35-.476.35zm22.73 4.663c.148 0 .294-.065.393-.19.171-.217.134-.531-.083-.702-.162-.127-4.02-3.12-10.648-2.605-.275.021-.481.261-.46.536.022.275.257.501.537.46 6.233-.474 9.915 2.366 9.951 2.395.093.07.202.106.31.106zm-8.868-4.663c.049 0 .1-.007.149-.022 4.79-1.497 8.737-.347 8.777-.336.265.081.543-.07.623-.335.079-.265-.071-.543-.335-.622-.173-.052-4.286-1.247-9.362.338-.264.083-.411.363-.328.627.066.213.263.35.476.35z"/><path fill="#67757F" d="M24.736 30.898c-.097-.258-.384-.392-.643-.294-.552.206-1.076.311-1.559.311-1.152 0-1.561-.306-2.033-.659-.451-.338-.956-.715-1.99-.803v-2.339c0-.276-.224-.5-.5-.5s-.5.224-.5.5v2.373c-.81.115-1.346.439-1.816.743-.568.367-1.059.685-2.083.685-.482 0-1.006-.104-1.558-.311-.258-.095-.547.035-.643.294-.097.259.035.547.293.644.664.247 1.306.373 1.907.373 1.319 0 2.014-.449 2.627-.845.524-.339.98-.631 1.848-.635.992.008 1.358.278 1.815.621.538.403 1.147.859 2.633.859.601 0 1.244-.126 1.908-.373.259-.097.391-.385.294-.644z"/><path fill="#E75A70" d="M19.4 24.807h-2.8c-.64 0-1.163.523-1.163 1.163 0 .639.523 1.163 1.163 1.163h.237v.345c0 .639.523 1.163 1.163 1.163s1.163-.523 1.163-1.163v-.345h.237c.639 0 1.163-.523 1.163-1.163s-.524-1.163-1.163-1.163z"/><path fill="#F18F26" d="M18.022 17.154c-.276 0-.5-.224-.5-.5V8.37c0-.276.224-.5.5-.5s.5.224.5.5v8.284c0 .277-.223.5-.5.5zM21 15.572c-.276 0-.5-.224-.5-.5 0-2.882 1.232-5.21 1.285-5.308.13-.244.435-.334.677-.204.243.13.334.433.204.677-.012.021-1.166 2.213-1.166 4.835 0 .276-.224.5-.5.5zm-6 0c-.276 0-.5-.224-.5-.5 0-2.623-1.155-4.814-1.167-4.835-.13-.244-.038-.546.205-.677.242-.131.545-.039.676.204.053.098 1.285 2.426 1.285 5.308.001.276-.223.5-.499.5z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f436.svg b/games/sound-soup/assets/img/1f436.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#DD2E44" d="M15 27v6s0 3 3 3 3-3 3-3v-6h-6z"/><path fill="#BE1931" d="M15 33l.001.037c1.041-.035 2.016-.274 2.632-1.286.171-.281.563-.281.735 0 .616 1.011 1.591 1.251 2.632 1.286V27h-6v6z"/><path fill="#D99E82" d="M31.954 21.619c0 6.276-5 6.276-5 6.276h-18s-5 0-5-6.276c0-6.724 5-18.619 14-18.619s14 12.895 14 18.619z"/><path fill="#F4C7B5" d="M18 20c-7 0-10 3.527-10 6.395 0 3.037 2.462 5.5 5.5 5.5 1.605 0 3.042-.664 4.049-2.767.185-.386.716-.386.901 0 1.007 2.103 2.445 2.767 4.049 2.767 3.038 0 5.5-2.463 5.5-5.5C28 23.527 25 20 18 20z"/><path fill="#292F33" d="M15 22.895c-1 1 2 4 3 4s4-3 3-4-5-1-6 0zM13 19c-1.1 0-2-.9-2-2v-2c0-1.1.9-2 2-2s2 .9 2 2v2c0 1.1-.9 2-2 2zm10 0c-1.1 0-2-.9-2-2v-2c0-1.1.9-2 2-2s2 .9 2 2v2c0 1.1-.9 2-2 2z"/><path fill="#662113" d="M15 3.608C13.941 2.199 11.681.881 2.828 4.2-1.316 5.754.708 17.804 3.935 18.585c1.106 0 4.426 0 4.426-8.852 0-.22-.002-.423-.005-.625C10.35 6.298 12.5 4.857 15 3.608zm18.172.592C24.319.881 22.059 2.199 21 3.608c2.5 1.25 4.65 2.691 6.644 5.501-.003.201-.005.404-.005.625 0 8.852 3.319 8.852 4.426 8.852 3.227-.782 5.251-12.832 1.107-14.386z"/><circle fill="#D99E82" cx="23.5" cy="25.5" r=".5"/><circle fill="#D99E82" cx="11.5" cy="25.5" r=".5"/><circle fill="#D99E82" cx="25.5" cy="27.5" r=".5"/><circle fill="#D99E82" cx="10.5" cy="27.5" r=".5"/><circle fill="#D99E82" cx="23" cy="28" r="1"/><circle fill="#D99E82" cx="13" cy="28" r="1"/><path fill="#380F09" d="M9.883 7.232c-.259-.673-.634-1.397-1.176-1.939-.391-.391-1.023-.391-1.414 0s-.391 1.023 0 1.414c.57.57 1.066 1.934 1.068 2.346.145-.404.839-1.15 1.522-1.821zm16.217 0c.259-.672.634-1.397 1.176-1.939.391-.391 1.023-.391 1.414 0s.391 1.023 0 1.414c-.57.57-1.066 1.934-1.068 2.346-.145-.404-.839-1.15-1.522-1.821z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f437.svg b/games/sound-soup/assets/img/1f437.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#F4ABBA" d="M34.193 13.329c.387-.371.733-.795 1.019-1.28 1.686-2.854.27-10.292-.592-10.8-.695-.411-5.529 1.05-8.246 3.132C23.876 2.884 21.031 2 18 2c-3.021 0-5.856.879-8.349 2.367C6.93 2.293 2.119.839 1.424 1.249c-.861.508-2.276 7.947-.592 10.8.278.471.615.884.989 1.249C.666 15.85 0 18.64 0 21.479 0 31.468 8.011 34 18 34s18-2.532 18-12.521c0-2.828-.66-5.606-1.807-8.15z"/><path fill="#EA596E" d="M7.398 5.965c-2.166-1.267-4.402-2.08-4.8-1.845-.57.337-1.083 4.998-.352 8.265 1.273-2.483 3.04-4.682 5.152-6.42zm26.355 6.419c.733-3.267.219-7.928-.351-8.265-.398-.235-2.635.578-4.801 1.845 2.114 1.739 3.88 3.938 5.152 6.42zM28 23.125c0 4.487-3.097 9.375-10 9.375-6.904 0-10-4.888-10-9.375S11.096 17.5 18 17.5c6.903 0 10 1.138 10 5.625z"/><path fill="#662113" d="M15 24.6c0 1.857-.34 2.4-1.5 2.4s-1.5-.543-1.5-2.4c0-1.856.34-2.399 1.5-2.399s1.5.542 1.5 2.399zm9 0c0 1.857-.34 2.4-1.5 2.4s-1.5-.543-1.5-2.4c0-1.856.34-2.399 1.5-2.399s1.5.542 1.5 2.399z"/><circle fill="#292F33" cx="7" cy="17" r="2"/><circle fill="#292F33" cx="29" cy="17" r="2"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f438.svg b/games/sound-soup/assets/img/1f438.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#C6E5B3" d="M36 22c0 7.456-8.059 12-18 12S0 29.456 0 22 8.059 7 18 7s18 7.544 18 15z"/><path fill="#77B255" d="M31.755 12.676C33.123 11.576 34 9.891 34 8c0-3.313-2.687-6-6-6-2.861 0-5.25 2.004-5.851 4.685-1.288-.483-2.683-.758-4.149-.758-1.465 0-2.861.275-4.149.758C13.25 4.004 10.861 2 8 2 4.687 2 2 4.687 2 8c0 1.891.877 3.576 2.245 4.676C1.6 15.356 0 18.685 0 22c0 7.456 8.059 1 18 1s18 6.456 18-1c0-3.315-1.6-6.644-4.245-9.324z"/><circle fill="#FFF" cx="7.5" cy="7.5" r="3.5"/><circle fill="#292F33" cx="7.5" cy="7.5" r="1.5"/><circle fill="#FFF" cx="28.5" cy="7.5" r="3.5"/><circle fill="#292F33" cx="28.5" cy="7.5" r="1.5"/><circle fill="#5C913B" cx="14" cy="20" r="1"/><circle fill="#5C913B" cx="22" cy="20" r="1"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f43a.svg b/games/sound-soup/assets/img/1f43a.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#66757F" d="M14.858 9.497c.475 2.326-.182 4.236-2.921 4.638-2.741.403-6.7 3.898-8.848-1.798C1.844 9.038 1.092 2.234 2.628 2.009c1.537-.226 11.756 5.162 12.23 7.488z"/><path fill="#CCD6DD" d="M12.784 9.851c.865 1.392-2.205 3.833-3.844 4.568-1.639.736-2.915-.66-4.173-4.1-.55-1.503-1.234-5.532-.634-5.802.599-.268 7.785 3.942 8.651 5.334z"/><path fill="#66757F" d="M21.372 9.497c-.458 2.326.176 4.236 2.818 4.638 2.644.403 6.464 3.898 8.536-1.798 1.201-3.3 1.927-10.103.445-10.329-1.483-.225-11.342 5.163-11.799 7.489z"/><path fill="#CCD6DD" d="M23.373 9.851c-.835 1.392 2.127 3.833 3.708 4.568 1.581.736 2.812-.66 4.026-4.1.531-1.503 1.19-5.532.611-5.802-.577-.268-7.509 3.942-8.345 5.334z"/><path fill="#66757F" d="M32.347 26.912c0-.454-.188-1.091-.407-1.687.585.028 1.519.191 2.77.817-.008-.536-.118-.984-.273-1.393.041.02.075.034.116.055-1.104-3.31-3.309-5.517-3.309-5.517h2.206c-2.331-4.663-4.965-8.015-8.075-9.559-1.39-.873-3.688-1.338-7.373-1.339h-.003c-3.696 0-5.996.468-7.385 1.346-3.104 1.547-5.734 4.896-8.061 9.552H4.76s-2.207 2.206-3.311 5.517l.084-.039c-.201.392-.307.847-.282 1.377 1.263-.632 2.217-.792 2.813-.818-.189.513-.343 1.044-.386 1.475-.123.371-.191.812-.135 1.343 3.207-1.458 4.707-1.25 6.457-.375C11.213 31.29 14.206 34 18.001 34c3.793 0 6.746-2.794 7.958-6.416 1.458-1.25 3.708-.875 6.416.416.066-.413.036-.773-.036-1.093l.008.005z"/><path fill="#CCD6DD" d="M34.553 24.704c-.437-1.313-3.665-3.101-6.973-4.513.26-.664.42-1.401.42-2.191 0-2.761-1.791-5-4-5s-4 2.239-4 5c0 3 4 10-2.001 11.118-5.125-.955-2.954-6.201-2.212-9.58.072-.276.125-.559.158-.853.034-.245.055-.476.055-.685 0-2.761-1.791-5-4-5s-4 2.239-4 5c0 .79.16 1.527.421 2.191-3.308 1.412-6.535 3.2-6.973 4.513C3.655 23.6 4.759 23.6 4.759 23.6s-1.104 2.208-1.104 3.312c2.67-1.78 5.339-2.122 7.429-.452C12.297 30.083 14 33 18.001 30.124c3.999 2.876 5.7-.04 6.912-3.662 2.092-1.673 4.763-1.33 7.434.45 0-1.104-1.103-3.312-1.103-3.312s1.103.001 3.309 1.104z"/><path fill="#292F33" d="M11 17s0-1.5 1.5-1.5S14 17 14 17v1.5s0 1.5-1.5 1.5-1.5-1.5-1.5-1.5V17zm11 0s0-1.5 1.5-1.5S25 17 25 17v1.5s0 1.5-1.5 1.5-1.5-1.5-1.5-1.5V17zm-7.061 9.156c-1.021.208 2.041 3.968 3.062 3.968 1.02 0 4.082-3.76 3.062-3.968s-5.103-.208-6.124 0z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f43b.svg b/games/sound-soup/assets/img/1f43b.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><circle fill="#C1694F" cx="7" cy="6" r="6"/><circle fill="#C1694F" cx="29" cy="6" r="6"/><circle fill="#E6AAAA" cx="7" cy="6" r="4"/><circle fill="#E6AAAA" cx="29" cy="6" r="4"/><path fill="#C1694F" d="M35 22S33.692 0 18 0 1 22 1 22c0 5.872 4.499 10.323 12.216 11.61C14.311 35.06 16.044 36 18 36s3.688-.94 4.784-2.39C30.501 32.323 35 27.872 35 22z"/><circle fill="#DD2E44" cx="18" cy="30" r="4"/><path fill="#D99E82" d="M18 20S7 23.687 7 27s2.687 6 6 6c2.088 0 3.925-1.067 5-2.685C19.074 31.933 20.912 33 23 33c3.313 0 6-2.687 6-6s-11-7-11-7z"/><path fill="#272B2B" d="M11 17s0-2 2-2 2 2 2 2v2s0 2-2 2-2-2-2-2v-2zm10 0s0-2 2-2 2 2 2 2v2s0 2-2 2-2-2-2-2v-2zm-7.875 8c-1.624 1 3.25 4 4.875 4s6.499-3 4.874-4-8.124-1-9.749 0z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f43c.svg b/games/sound-soup/assets/img/1f43c.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><circle fill="#272B2B" cx="7" cy="6" r="6"/><circle fill="#272B2B" cx="29" cy="6" r="6"/><circle fill="#66757F" cx="7" cy="6" r="4"/><circle fill="#66757F" cx="29" cy="6" r="4"/><path fill="#EEE" d="M35 22c0 7-6.375 12-17 12S1 29 1 22C1 22 2.308 0 18 0s17 22 17 22z"/><circle fill="#CCD6DD" cx="18" cy="30" r="6"/><circle fill="#DD2E44" cx="18" cy="30" r="4"/><path fill="#272B2B" d="M20.709 12.654C25.163 9.878 32 17 26.952 22.67 23.463 26.591 20 25 20 25s-2.636-10.26.709-12.346zm-5.442.011C10.813 9.888 3.976 17.01 9.023 22.681c3.49 3.92 6.953 2.329 6.953 2.329s2.636-10.26-.709-12.345z"/><path fill="#66757F" d="M11 17s0-2 2-2 2 2 2 2v2s0 2-2 2-2-2-2-2v-2z"/><path fill="#FFF" d="M18 20S7 23.687 7 27s2.687 6 6 6c2.088 0 3.925-1.067 5-2.685C19.074 31.933 20.912 33 23 33c3.313 0 6-2.687 6-6s-11-7-11-7z"/><path fill="#66757F" d="M21 17s0-2 2-2 2 2 2 2v2s0 2-2 2-2-2-2-2v-2z"/><path fill="#272B2B" d="M13.125 25c-1.624 1 3.25 4 4.875 4s6.499-3 4.874-4-8.124-1-9.749 0z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f444.svg b/games/sound-soup/assets/img/1f444.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#E75A70" d="M29 12c-4.688-3.515-6-6-8-6s-2 1-3 1-1-1-3-1-3.313 2.485-8 6c-4 3-7 3-7 5s5 6 8 9c2 2 5 4 10 4s8-2 10-4c3-3 8-7 8-9s-3-2-7-5z"/><path fill="#662113" d="M28.389 15.5C23.963 14.035 21 13.417 18 13.417s-5.962.618-10.389 2.083C3.833 16.75 1 16.167 1 17c0 .834 9 5 17 5s17-4.166 17-5c0-.833-2.834-.25-6.611-1.5z"/><path fill="#FFF" d="M28.389 15.5C23.963 14.035 21 13.417 18 13.417s-5.962.618-10.389 2.083c-3.624 1.199-6.371.715-6.59 1.408 2.454.385 9.119-.644 16.979-.644s14.524 1.029 16.979.644c-.219-.693-2.967-.209-6.59-1.408z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f456.svg b/games/sound-soup/assets/img/1f456.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#3B88C3" d="M20.182 0H10v4.568L3.818 33.682 11.545 36l6.41-23.182z"/><path fill="#3B88C3" d="M15.818 0H26v4.568l6.182 29.114L24.454 36l-6.409-23.182z"/><path fill="#269" d="M10 2h16v2H10zm9 10h-2l-4-2h10z"/><path fill="#269" d="M12 0h2v3h-2zm10 0h2v3h-2zm-6 4h2v7h-2z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f457.svg b/games/sound-soup/assets/img/1f457.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#55ACEE" d="M22.42 11.847L26 2s-.398.002-1 .034V0h-2v2.219c-2.271.302-5 1.028-5 2.781 0-1.753-2.729-2.479-5-2.781V0h-2v2.034C10.398 2.002 10 2 10 2l3.581 9.847L1 25.643S7 36 18 36s17-10.357 17-10.357L22.42 11.847z"/><path fill="#BBDDF5" d="M13 11h10v2H13z"/><path fill="#3B88C3" d="M15 13s-6.734 8.106-5.051 9.006C11.633 22.907 15 13 15 13zm6.096 0s6.734 8.105 5.051 9.007c-1.684.9-5.051-9.007-5.051-9.007zM18 13s-2 11 0 11 0-11 0-11z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f45c.svg b/games/sound-soup/assets/img/1f45c.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#FFAC33" d="M28 22c-.553 0-1-.447-1-1v-6c0-6.065-4.037-11-9-11-4.962 0-9 4.935-9 11v6c0 .553-.448 1-1 1s-1-.447-1-1v-6C7 7.832 11.935 2 18 2s11 5.832 11 13v6c0 .553-.447 1-1 1z"/><path fill="#9266CC" d="M33.386 12.972C33.126 10.788 31.114 9 28.914 9H7.086c-2.2 0-4.212 1.788-4.472 3.972L.472 31.028C.212 33.213 1.8 35 4 35h28c2.2 0 3.788-1.787 3.528-3.972l-2.142-18.056z"/><path fill="#FFD983" d="M28 20c-.553 0-1-.447-1-1v-6c0-6.065-4.037-11-9-11-4.962 0-9 4.935-9 11v6c0 .553-.448 1-1 1s-1-.447-1-1v-6C7 5.832 11.935 0 18 0s11 5.832 11 13v6c0 .553-.447 1-1 1z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f466.svg b/games/sound-soup/assets/img/1f466.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#FFDC5D" d="M6 20c0 2.209-1.119 4-2.5 4S1 22.209 1 20s1.119-4 2.5-4S6 17.791 6 20zm29 0c0 2.209-1.119 4-2.5 4S30 22.209 30 20s1.119-4 2.5-4 2.5 1.791 2.5 4z"/><path fill="#FFDC5D" d="M4 20.562c0-8.526 6.268-15.438 14-15.438s14 6.912 14 15.438S25.732 35 18 35 4 29.088 4 20.562z"/><path fill="#662113" d="M12 22c-.552 0-1-.447-1-1v-2c0-.552.448-1 1-1s1 .448 1 1v2c0 .553-.448 1-1 1zm12 0c-.553 0-1-.447-1-1v-2c0-.552.447-1 1-1s1 .448 1 1v2c0 .553-.447 1-1 1z"/><path fill="#C1694F" d="M18 30c-4.188 0-6.357-1.06-6.447-1.105-.494-.247-.694-.848-.447-1.342.247-.492.843-.692 1.337-.449.051.024 1.925.896 5.557.896 3.665 0 5.54-.888 5.559-.897.496-.241 1.094-.034 1.336.457.243.493.045 1.089-.447 1.335C24.356 28.94 22.188 30 18 30zm1-5h-2c-.552 0-1-.447-1-1s.448-1 1-1h2c.553 0 1 .447 1 1s-.447 1-1 1z"/><path fill="#FFAC33" d="M18 .354C8.77.354 3 6.816 3 12.2c0 5.385 1.154 7.539 2.308 5.385l2.308-4.308s3.791-.124 6.099-2.278c0 0-1.071 4 6.594.124 0 0-.166 3.876 5.191-.124 0 0 4.039 1.201 5.191 6.586.32 1.494 2.309 0 2.309-5.385C33 6.816 28.385.354 18 .354z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f467.svg b/games/sound-soup/assets/img/1f467.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#FFAC33" d="M29.96 23.087C34 27 34.043 34.021 33.021 34.021s-4.115-1.852-6.068-3.937C25 28 25.203 23.306 25.203 23.306l1.586-4.319c0-.001-.869.187 3.171 4.1z"/><path fill="#FFAC33" d="M26.96 23.087C31 27 31.043 34.021 30.021 34.021s-4.115-1.852-6.068-3.937C22 28 22.203 23.306 22.203 23.306l1.586-4.319c0-.001-.869.187 3.171 4.1zM3 34c-1 0-1-7 3-11s3-4 3-4l2 4s0 5-2 7-5 4-6 4z"/><path fill="#FFAC33" d="M6 34c-1 0-1-7 3-11s3-4 3-4l2 4s0 5-2 7-5 4-6 4z"/><path fill="#FFDC5D" d="M6.914 18.353c-.571-2.134-2.116-3.575-3.45-3.217-1.334.358-1.95 2.378-1.379 4.511.571 2.135 2.116 3.574 3.45 3.217 1.334-.358 1.951-2.378 1.379-4.511zm27.001 1.294c.571-2.134-.046-4.154-1.38-4.512-1.333-.356-2.878 1.083-3.449 3.218-.572 2.134.045 4.153 1.379 4.511 1.334.358 2.879-1.083 3.45-3.217z"/><path fill="#FFDC5D" d="M31 19c0-9.389-5.82-16-13-16S5 9.611 5 19s5.82 15 13 15 13-5.611 13-15z"/><path fill="#DF1F32" d="M18 27.651c-2.42 0-4.274-.687-4.352-.715-.517-.194-.779-.771-.584-1.288.194-.517.769-.779 1.286-.585.016.006 1.61.588 3.65.588 2.041 0 3.635-.582 3.65-.588.516-.194 1.094.071 1.285.587.193.517-.067 1.092-.584 1.286-.077.029-1.93.715-4.351.715z"/><path fill="#C1694F" d="M19 23h-2c-.552 0-1-.447-1-1s.448-1 1-1h2c.553 0 1 .447 1 1s-.447 1-1 1z"/><path fill="#662113" d="M12 20c-.552 0-1-.447-1-1v-2c0-.552.448-1 1-1s1 .448 1 1v2c0 .553-.448 1-1 1zm12 0c-.553 0-1-.447-1-1v-2c0-.552.447-1 1-1s1 .448 1 1v2c0 .553-.447 1-1 1z"/><path fill="#FFAC33" d="M32 10c-2-7-7-9-10-9-2 0-4 2-4 2s-2-2-4-2c-3 0-8 2-10 9-1.648 5.769 1 11 1 11 0-3.001 2-9 7-9s6-4 6-4 .786 4 5.786 4S31 18 31 21c0 0 2.648-5.231 1-11z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f468-200d-1f467.svg b/games/sound-soup/assets/img/1f468-200d-1f467.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#3B88C3" d="M36 32c0 2.209-1.791 4-4 4H4c-2.209 0-4-1.791-4-4V4c0-2.209 1.791-4 4-4h28c2.209 0 4 1.791 4 4v28z"/><path fill="#FFF" d="M20.285 14.196c-.196 0-.264-.255-.098-.353 1.425-.863 2.392-2.5 2.392-4.245 0-1.22-.483-2.389-1.341-3.251a4.57 4.57 0 0 0-6.476 0 4.608 4.608 0 0 0-1.34 3.251c0 1.745.966 3.382 2.391 4.245.166.098.098.353-.098.353-2.606 0-4.715 2.118-4.715 4.735v9.343c0 .402.322.726.723.726h12.554c.4 0 .723-.323.723-.726v-9.343c0-2.608-2.109-4.735-4.715-4.735Z"/><path fill="#3B88C3" d="M18.414 23.78a4.761 4.761 0 0 1 4.76-4.76c.224 0 .472.01.725.064a4.757 4.757 0 0 1 3.644 2.796v.001c.727 1.684.402 3.64-.707 5.047a5.393 5.393 0 0 1 1.733 2.982 1.944 1.944 0 0 1-1.905 2.34h-6.97c-.581 0-1.133-.25-1.503-.711a1.932 1.932 0 0 1-.402-1.627v-.001a5.467 5.467 0 0 1 1.734-2.99 5.065 5.065 0 0 1-1.109-3.141Z"/><path fill="#FFF" d="M27.334 30.15a4.22 4.22 0 0 0-2.34-2.96c-.1-.05-.11-.19-.02-.25 1.4-.92 2.16-2.84 1.41-4.58a3.527 3.527 0 0 0-4.187-1.965 3.508 3.508 0 0 0-2.543 3.375c0 1.28.68 2.48 1.7 3.16.09.06.09.21-.02.25a4.181 4.181 0 0 0-2.34 2.96c-.09.44.24.85.68.85h6.97c.46.01.78-.4.69-.84Z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f468.svg b/games/sound-soup/assets/img/1f468.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#FFDC5D" d="M8 19c0 2.209-1.119 4-2.5 4S3 21.209 3 19s1.119-4 2.5-4S8 16.791 8 19zm25 0c0 2.209-1.119 4-2.5 4S28 21.209 28 19s1.119-4 2.5-4 2.5 1.791 2.5 4z"/><path fill="#FFDC5D" d="M5 20.562c0-8.526 5.82-15.438 13-15.438s13 6.912 13 15.438S25.18 36 18 36 5 29.088 5 20.562z"/><path fill="#662113" d="M13 20c-.552 0-1-.447-1-1v-2c0-.552.448-1 1-1s1 .448 1 1v2c0 .553-.448 1-1 1zm10 0c-.553 0-1-.447-1-1v-2c0-.552.447-1 1-1s1 .448 1 1v2c0 .553-.447 1-1 1z"/><path fill="#C1694F" d="M19 24h-2c-.552 0-1-.447-1-1s.448-1 1-1h2c.553 0 1 .447 1 1s-.447 1-1 1z"/><path fill="#FFAC33" d="M25.274 27.038l-3.294-.941c.003-.034.02-.063.02-.097 0-.553-.447-1-1-1h-6c-.552 0-1 .447-1 1 0 .034.016.063.019.097l-3.294.941c-.531.152-.838.706-.686 1.236.125.44.525.726.961.726.091 0 .184-.013.275-.038l1.931-.552c-.216.293-.274.688-.1 1.037.175.351.528.553.895.553.15 0 .303-.034.446-.105l1.577-.788c.036.326.213.631.529.788.143.071.296.105.446.105.367 0 .72-.202.896-.553l.105-.211.105.211c.176.351.529.553.896.553.15 0 .303-.034.446-.105.315-.157.493-.462.529-.788l1.576.788c.144.071.297.105.447.105.367 0 .72-.202.896-.553.174-.349.116-.744-.1-1.037l1.931.552c.091.025.183.038.275.038.434 0 .835-.286.961-.726.151-.53-.156-1.084-.688-1.236zM18 0c8.615 0 14 6.358 14 11.656 0 5.298-1.077 7.417-2.154 5.298l-2.153-4.238s-6.462 0-8.615-2.12c0 0 3.23 6.358-3.231 0 0 0 1.077 4.239-5.385-1.06 0 0-3.23 2.12-4.308 7.417C5.855 18.423 4 16.954 4 11.656 4 6.357 8.308 0 18 0z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f469-200d-1f467.svg b/games/sound-soup/assets/img/1f469-200d-1f467.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#3B88C3" d="M36 32c0 2.209-1.791 4-4 4H4c-2.209 0-4-1.791-4-4V4c0-2.209 1.791-4 4-4h28c2.209 0 4 1.791 4 4v28z"/><path fill="#FFF" d="M20.285 14.196c-.196 0-.264-.255-.098-.353 1.425-.863 2.392-2.5 2.392-4.245 0-1.22-.483-2.389-1.341-3.251a4.57 4.57 0 0 0-6.476 0 4.608 4.608 0 0 0-1.34 3.251c0 1.745.966 3.382 2.391 4.245.166.098.098.353-.098.353-2.606 0-4.715 2.118-4.715 4.735v9.343c0 .402.322.726.723.726h12.554c.4 0 .723-.323.723-.726v-9.343c0-2.608-2.109-4.735-4.715-4.735Z"/><path fill="#3B88C3" d="M18.414 23.78a4.761 4.761 0 0 1 4.76-4.76c.224 0 .472.01.725.064a4.757 4.757 0 0 1 3.644 2.796v.001c.727 1.684.402 3.64-.707 5.047a5.393 5.393 0 0 1 1.733 2.982 1.944 1.944 0 0 1-1.905 2.34h-6.97c-.581 0-1.133-.25-1.503-.711a1.932 1.932 0 0 1-.402-1.627v-.001a5.467 5.467 0 0 1 1.734-2.99 5.065 5.065 0 0 1-1.109-3.141Z"/><path fill="#FFF" d="M27.334 30.15a4.22 4.22 0 0 0-2.34-2.96c-.1-.05-.11-.19-.02-.25 1.4-.92 2.16-2.84 1.41-4.58a3.527 3.527 0 0 0-4.187-1.965 3.508 3.508 0 0 0-2.543 3.375c0 1.28.68 2.48 1.7 3.16.09.06.09.21-.02.25a4.181 4.181 0 0 0-2.34 2.96c-.09.44.24.85.68.85h6.97c.46.01.78-.4.69-.84Z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f469.svg b/games/sound-soup/assets/img/1f469.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#FFAC33" d="M18 3c6 0 16 3 16 16s0 16-3 16-7-3-13-3-9.915 3-13 3c-3.343 0-3-12-3-16C2 6 12 3 18 3z"/><path fill="#FFDC5D" d="M6 18.562c0-8.526 5.373-15.438 12-15.438s12 6.912 12 15.438S24.627 34 18 34 6 27.088 6 18.562z"/><path fill="#DF1F32" d="M18 30c-2.347 0-3.575-1.16-3.707-1.293-.391-.391-.391-1.023 0-1.414.387-.387 1.013-.39 1.404-.01.051.047.806.717 2.303.717 1.519 0 2.273-.69 2.305-.719.398-.373 1.027-.362 1.408.029.379.393.38 1.011-.006 1.397C21.575 28.84 20.347 30 18 30z"/><path fill="#C1694F" d="M19 25h-2c-.552 0-1-.447-1-1s.448-1 1-1h2c.553 0 1 .447 1 1s-.447 1-1 1z"/><path fill="#FFAC33" d="M3.064 24c-.03-.325-.064-.647-.064-1 0-5 3 .562 3-3 0-3.563 2-4 4-6l3-3s5 3 9 3 8 2 8 6 3-2 3 3c0 .355-.033.673-.058 1h1.049C34 22.523 34 20.868 34 19 34 6 24 1 18 1S2 6 2 19c0 1.158-.028 2.986.012 5h1.052z"/><path d="M13 22c-.552 0-1-.447-1-1v-2c0-.552.448-1 1-1s1 .448 1 1v2c0 .553-.448 1-1 1zm10 0c-.553 0-1-.447-1-1v-2c0-.552.447-1 1-1s1 .448 1 1v2c0 .553-.447 1-1 1z" fill="#662113"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f474.svg b/games/sound-soup/assets/img/1f474.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#D1D3D4" d="M32.733 10.633C32.733 14.702 26.137 18 18 18S3.267 14.702 3.267 10.633 4.4 2 18 2c13.6 0 14.733 4.564 14.733 8.633z"/><path fill="#FFDC5D" d="M32.167 13.466c-.318 0-.619.103-.904.258C29.903 6.108 24.486 1 18 1 11.513 1 6.097 6.108 4.737 13.724c-.285-.154-.586-.257-.904-.257C2.269 13.466 1 15.497 1 18c0 2.504 1.269 4.533 2.833 4.533.381 0 .743-.124 1.075-.342C6.503 29.574 11.753 35 18 35s11.497-5.426 13.091-12.81c.332.219.695.343 1.076.343C33.731 22.533 35 20.504 35 18c0-2.503-1.269-4.534-2.833-4.534z"/><path fill="#662113" d="M11.201 19c-.367 0-.72-.203-.895-.553-.247-.494-.046-1.095.447-1.342l2.267-1.133c.495-.247 1.095-.045 1.342.447.247.494.046 1.095-.447 1.342l-2.267 1.133c-.144.072-.297.106-.447.106zm13.598 0c-.15 0-.303-.034-.446-.105l-2.267-1.133c-.494-.247-.694-.848-.447-1.342.246-.493.849-.695 1.342-.447l2.267 1.133c.494.247.694.848.447 1.342-.175.349-.529.552-.896.552z"/><path d="M26 14h-3c-.553 0-1-.448-1-1s.447-1 1-1h3c.553 0 1 .448 1 1s-.447 1-1 1zm-13 0h-3c-.552 0-1-.448-1-1s.448-1 1-1h3c.552 0 1 .448 1 1s-.448 1-1 1z" fill="#D1D3D4"/><path fill="#C1694F" d="M18 30.333c-2.474 0-4.879-1.188-4.981-1.238-.493-.247-.693-.846-.447-1.34.246-.495.848-.692 1.34-.45.021.011 2.094 1.028 4.087 1.028 2.008 0 4.065-1.017 4.086-1.027.493-.244 1.096-.047 1.342.447.247.494.047 1.095-.447 1.342-.101.051-2.505 1.238-4.98 1.238zM19 25h-2c-.552 0-1-.447-1-1s.448-1 1-1h2c.553 0 1 .447 1 1s-.447 1-1 1z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f475.svg b/games/sound-soup/assets/img/1f475.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#6D6E71" d="M13.062 4c0-2.209 2.211-4 4.938-4 2.728 0 4.938 1.791 4.938 4S20.728 8 18 8c-2.727-.001-4.938-1.792-4.938-4z"/><path fill="#FFDC5D" d="M33 21c0-2.14-1.053-3.872-2.373-3.98C29.255 9.555 24.13 4 18 4S6.745 9.555 5.373 17.02C4.053 17.128 3 18.86 3 21c0 2.153 1.066 3.896 2.399 3.983C6.807 31.946 11.906 36 18 36c6.094 0 11.193-4.054 12.602-11.017C31.934 24.896 33 23.153 33 21z"/><path fill="#808285" d="M25 19h-3c-.553 0-1-.448-1-1s.447-1 1-1h3c.553 0 1 .448 1 1s-.447 1-1 1zm-11 0h-3c-.552 0-1-.448-1-1s.448-1 1-1h3c.552 0 1 .448 1 1s-.448 1-1 1z"/><path fill="#DF1F32" d="M18 31.651c-2.519 0-3.494-.753-3.664-.903-.413-.367-.45-.999-.083-1.412.355-.402.963-.449 1.376-.113.06.039.7.429 2.371.429 1.751 0 2.37-.428 2.376-.433.414-.367 1.026-.311 1.392.101.367.413.31 1.062-.103 1.429-.171.149-1.146.902-3.665.902z"/><path fill="#C1694F" d="M19 28h-2c-.552 0-1-.447-1-1s.448-1 1-1h2c.553 0 1 .447 1 1s-.447 1-1 1z"/><path d="M23 24c-.553 0-1-.447-1-1v-2c0-.553.447-1 1-1s1 .447 1 1v2c0 .553-.447 1-1 1zm-10 0c-.552 0-1-.447-1-1v-2c0-.553.448-1 1-1s1 .447 1 1v2c0 .553-.448 1-1 1z" fill="#662113"/><path fill="#808285" d="M33 12c-2-7-8-9-11.188-9C20 3 18 4 18 4s-2-1-4-1c-3 0-9 2-11 9-1.648 5.769 2 11 2 11 0-3.001 2-9 7-9s6-4 6-4 .786 4 5.786 4S31 20 31 23c0 0 3.648-5.231 2-11z"/><circle fill="#AA8DD8" cx="32" cy="24" r="1"/><circle fill="#AA8DD8" cx="4" cy="24" r="1"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f47d.svg b/games/sound-soup/assets/img/1f47d.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#CCD6DD" d="M35 17c0 9.389-13.223 19-17 19-3.778 0-17-9.611-17-19S8.611 0 18 0s17 7.611 17 17z"/><path fill="#292F33" d="M13.503 14.845c3.124 3.124 4.39 6.923 2.828 8.485-1.562 1.562-5.361.297-8.485-2.828-3.125-3.124-4.391-6.923-2.828-8.485s5.361-.296 8.485 2.828zm8.994 0c-3.124 3.124-4.39 6.923-2.828 8.485 1.562 1.562 5.361.297 8.485-2.828 3.125-3.125 4.391-6.923 2.828-8.485-1.562-1.562-5.361-.297-8.485 2.828zM18 31c-2.347 0-3.575-1.16-3.707-1.293-.391-.391-.391-1.023 0-1.414.387-.387 1.013-.391 1.404-.01.051.047.806.717 2.303.717 1.519 0 2.273-.689 2.305-.719.398-.374 1.027-.363 1.408.029.379.393.38 1.011-.006 1.396C21.575 29.84 20.347 31 18 31z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f48d.svg b/games/sound-soup/assets/img/1f48d.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#9AAAB4" d="M18 12c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12zm0 20c-4.418 0-8-3.582-8-8s3.582-8 8-8 8 3.582 8 8-3.582 8-8 8z"/><path fill="#5DADEC" d="M29 5l-4-5H11L7 5l11 9z"/><path fill="#8CCAF7" d="M29 5l-4-5H11L7 5h11z"/><path fill="#5DADEC" d="M29 5l-4-5h-7v5h1z"/><path fill="#8CCAF7" d="M18 5h11l-11 9z"/><path fill="#9AAAB4" d="M25 13c0 1.657-1.343 3-3 3h-8c-1.657 0-3-1.343-3-3s1.343-3 3-3h8c1.657 0 3 1.343 3 3z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f48e.svg b/games/sound-soup/assets/img/1f48e.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#BDDDF4" d="M13 3H7l-7 9h10z"/><path fill="#5DADEC" d="M36 12l-7-9h-6l3 9z"/><path fill="#4289C1" d="M26 12h10L18 33z"/><path fill="#8CCAF7" d="M10 12H0l18 21zm3-9l-3 9h16l-3-9z"/><path fill="#5DADEC" d="M18 33l-8-21h16z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f490.svg b/games/sound-soup/assets/img/1f490.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#3E721D" d="M35.01 30.967c-1.385-2.226-3.34-5.001-5.265-7.888-5.141-8.772-1.513-18.411-2.893-21.511-.56-1.259-3.879.966-5.555 4.563-.356-.106-.747-.156-1.17-.148.155-2.397.293-4.31-.193-5.403-.71-1.599-5.867 2.418-6.463 7.729-1.848 1.006-3.223 1.957-3.223 1.957s-.95 1.375-1.954 3.223c-5.312.594-9.331 5.753-7.732 6.464 1.097.488 3.015.349 5.421.192-.006.42.046.807.154 1.159-3.61 1.674-5.848 5.006-4.586 5.567 3.282 1.46 13.9-2.701 23.053 3.892.288.207.479.322.601.371 2.117 1.433 4.113 2.801 5.791 3.846 2.875 1.79 3.962-.007 3.962-.007s1.843-1.13.052-4.006z"/><path fill="#77B255" d="M35.029 34.828c.369-.545.338-1.278-.144-1.761 0 0-11.86-15.812-13.836-23.719-1.977-7.906-4.397-5.737-3.689.474.325 2.848 1.817 6.14 3.806 9.385l-8.941-8.941c-.546-.546-1.431-.546-1.977 0-.545.546-.546 1.431 0 1.977l8.94 8.941c-3.246-1.987-6.536-3.48-9.383-3.805-6.211-.709-8.381 1.712-.475 3.688 7.907 1.976 23.719 13.836 23.719 13.836.481.482 1.215.513 1.758.146.034.007.065.01.078-.004.025-.025.034-.059.058-.086.025-.022.059-.031.083-.056.014-.013.01-.044.003-.075z"/><path fill="#DD2E44" d="M13.771 6.871c1.807 1.806 1.721 4.819-.188 6.73-1.91 1.91-3.771.842-5.577-.964S5.132 8.97 7.042 7.06c1.91-1.91 4.924-1.995 6.729-.189z"/><path fill="#EA596E" d="M12.618 8.024c1.807 1.807 2.874 3.666.965 5.576-1.91 1.91-4.923 1.995-6.729.188-1.806-1.806-.964-5.576-.902-7.82.074-2.627 4.487-.124 6.666 2.056z"/><path fill="#DD2E44" d="M22.665 15.766c1.808 1.807 1.722 4.819-.188 6.729-1.91 1.91-3.77.842-5.576-.964-1.806-1.806-2.874-3.667-.964-5.577 1.91-1.91 4.924-1.994 6.728-.188z"/><path fill="#EA596E" d="M21.514 16.919c1.806 1.807 2.873 3.666.964 5.576-1.91 1.91-4.923 1.995-6.729.188-1.806-1.806-.964-5.575-.901-7.819.073-2.628 4.485-.125 6.666 2.055z"/><path fill="#F4900C" d="M11.203 16.11c2.369 1.052 3.327 3.876 2.139 6.308-1.187 2.434-3.354 2.085-5.723 1.033-2.37-1.052-4.045-2.407-2.857-4.841 1.188-2.432 4.071-3.551 6.441-2.5z"/><path fill="#FFCC4D" d="M10.486 17.578c2.368 1.052 4.043 2.408 2.855 4.84-1.187 2.434-4.071 3.553-6.439 2.501-2.37-1.051-2.857-4.84-3.57-6.943-.836-2.463 4.294-1.666 7.154-.398z"/><path fill="#F4900C" d="M24.93 7.012c1.143 2.447.082 5.359-2.365 6.5-2.446 1.14-3.88-.607-5.023-3.056-1.142-2.449-1.56-4.671.887-5.813 2.448-1.141 5.358-.081 6.501 2.369z"/><path fill="#FFCC4D" d="M23.453 7.7c1.141 2.449 1.559 4.671-.889 5.812-2.446 1.14-5.359.081-6.501-2.368-1.141-2.448.889-5.812 1.676-7.977.925-2.54 4.334 1.578 5.714 4.533z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f4a8.svg b/games/sound-soup/assets/img/1f4a8.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#BDDDF4" d="M36 15c0-4.155-3.169-7.568-7.221-7.96C27.884 3.013 24.297 0 20 0c-4.971 0-9 3.56-9 8.531 0 1.156.25 2.906 1.121 3.785C8.859 12.5 4.094 12.6 0 12.015c0 0 4.816 3.831 15.357 3.985-.151.313-.254 1.43-.306 1.786-.249.003-.595.009-1.051.009C6 17.795 0 17 0 17s3 3.234 15.406 4.106C15 21.234 15 23 15 23c-8 0-15-1-15-1 3.362 2.802 6.724 4.033 9.03 4.574-.011.142-.03.281-.03.426 0 3.313 2.686 5 6 5 .487 0 .958-.064 1.412-.174C18.009 34.333 20.807 36 24 36c4.971 0 9-4.029 9-9 0-1.788-.527-3.449-1.427-4.851C34.195 20.837 36 18.132 36 15z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f4cc.svg b/games/sound-soup/assets/img/1f4cc.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#BE1931" d="M23.651 23.297L12.702 12.348l9.386-7.821 9.385 9.385z"/><path fill="#DD2E44" d="M34.6 13.912c-1.727 1.729-4.528 1.729-6.255 0l-6.257-6.256c-1.729-1.727-1.729-4.53 0-6.258 1.726-1.727 4.528-1.727 6.257 0L34.6 7.656c1.728 1.727 1.728 4.529 0 6.256z"/><path fill="#99AAB5" d="M14 17.823S-.593 35.029.188 35.813C.97 36.596 18.177 22 18.177 22L14 17.823z"/><path fill="#DD2E44" d="M25.215 27.991c-1.726 1.729-4.528 1.729-6.258 0L8.009 17.041c-1.727-1.728-1.727-4.528 0-6.256 1.728-1.729 4.53-1.729 6.258 0l10.948 10.949c1.728 1.729 1.728 4.528 0 6.257z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f4d6.svg b/games/sound-soup/assets/img/1f4d6.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#269" d="M32 7H4c-2.209 0-4 1.791-4 4v15c0 2.209 1.791 4 4 4h11.416c.52.596 1.477 1 2.584 1s2.065-.404 2.584-1H32c2.209 0 4-1.791 4-4V11c0-2.209-1.791-4-4-4z"/><path fill="#292F33" d="M20 27c0 1.104-.896 2-2 2s-2-.896-2-2V9c0-1.104.896-2 2-2s2 .896 2 2v18z"/><path fill="#99AAB5" d="M18 26c0 1.104-.896 2-2 2H4c-1.104 0-2-.896-2-2V8c0-1.104.896-2 2-2h12c1.104 0 2 .896 2 2v18z"/><path fill="#E1E8ED" d="M18 26c-.999-1.998-3.657-2-4-2-2 0-5 2-8 2-1 0-2-.896-2-2V8c0-1.104 1-2 2-2 3.255 0 6-2 8-2 3 0 4 1.896 4 3v19z"/><path fill="#99AAB5" d="M34 26c0 1.104-.896 2-2 2H20c-1.104 0-2-.896-2-2V8c0-1.104.896-2 2-2h12c1.104 0 2 .896 2 2v18z"/><path fill="#CCD6DD" d="M18 26c.999-1.998 3.657-2 4-2 2 0 5 2 8 2 1 0 2-.896 2-2V8c0-1.104-1-2-2-2-3.256 0-6-2-8-2-3 0-4 1.896-4 3v19z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f4e6.svg b/games/sound-soup/assets/img/1f4e6.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#662113" d="M4 11v12.375c0 2.042 1.093 2.484 1.093 2.484l11.574 9.099C18.489 36.39 18 33.375 18 33.375V22L4 11z"/><path fill="#C1694F" d="M32 11v12.375c0 2.042-1.063 2.484-1.063 2.484s-9.767 7.667-11.588 9.099C17.526 36.39 18 33.375 18 33.375V22l14-11z"/><path fill="#D99E82" d="M19.289.5c-.753-.61-1.988-.61-2.742 0L4.565 10.029c-.754.61-.754 1.607 0 2.216l12.023 9.646c.754.609 1.989.609 2.743 0l12.104-9.73c.754-.609.754-1.606 0-2.216L19.289.5z"/><path fill="#D99E82" d="M18 35.75c-.552 0-1-.482-1-1.078V21.745c0-.596.448-1.078 1-1.078.553 0 1 .482 1 1.078v12.927c0 .596-.447 1.078-1 1.078z"/><path fill="#99AAB5" d="M28 18.836c0 1.104.104 1.646-1 2.442l-2.469 1.878c-1.104.797-1.531.113-1.531-.992v-2.961c0-.193-.026-.4-.278-.608C20.144 16.47 10.134 8.519 8.31 7.051l4.625-3.678c1.266.926 10.753 8.252 14.722 11.377.197.156.343.328.343.516v3.57z"/><path fill="#CCD6DD" d="M27.656 14.75C23.688 11.625 14.201 4.299 12.935 3.373l-1.721 1.368-2.904 2.31c1.825 1.468 11.834 9.419 14.412 11.544.151.125.217.25.248.371L27.903 15c-.06-.087-.146-.171-.247-.25z"/><path fill="#CCD6DD" d="M28 18.836v-3.57c0-.188-.146-.359-.344-.516-3.968-3.125-13.455-10.451-14.721-11.377l-2.073 1.649c3.393 2.669 12.481 9.681 14.86 11.573.256.204.278.415.278.608v4.836l1-.761c1.104-.797 1-1.338 1-2.442z"/><path fill="#E1E8ED" d="M27.656 14.75C23.688 11.625 14.201 4.299 12.935 3.373l-2.073 1.649c3.393 2.669 12.481 9.681 14.86 11.573.037.029.06.059.087.088L27.903 15c-.06-.087-.146-.171-.247-.25z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f50c.svg b/games/sound-soup/assets/img/1f50c.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#9AAAB4" d="M11.858 16.264c.389.389.389 1.025 0 1.414s-1.025.389-1.414 0L1.959 9.192c-.389-.389-.389-1.025 0-1.414s1.025-.389 1.414 0l8.485 8.486zm7.072-7.071c.389.389.389 1.025 0 1.414s-1.025.389-1.414 0L9.029 2.121c-.389-.389-.389-1.025 0-1.414s1.025-.389 1.414 0l8.487 8.486z"/><path fill="#31373D" d="M34 36c0-4.966-2.56-8.581-4.562-10.65.898-1.532.704-3.529-.61-4.844l-1.099-1.099c.352-1.332.021-2.806-1.022-3.851l-4.949-4.95c.78-.781.78-2.047 0-2.828-.781-.781-2.049-.781-2.829-.001l-2.826 2.826-.003.002-4.242 4.243-2.828 2.828c-.781.781-.781 2.047 0 2.828.781.781 2.047.781 2.828 0l4.95 4.95c1.044 1.044 2.519 1.373 3.85 1.021l1.101 1.101c1.318 1.318 3.323 1.51 4.857.602C28.099 29.726 30 32.404 30 36h4z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f512.svg b/games/sound-soup/assets/img/1f512.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#AAB8C2" d="M18 0C12.477 0 8 4.477 8 10v10h4V10a6 6 0 0 1 12 0v10h4V10c0-5.523-4.477-10-10-10Z"/><path fill="#FFAC33" d="M32 32a4 4 0 0 1-4 4H8a4 4 0 0 1-4-4V18a4 4 0 0 1 4-4h20a4 4 0 0 1 4 4v14Z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f514.svg b/games/sound-soup/assets/img/1f514.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#FFAC33" d="M28 13c0 11 5 10 5 15 0 0 0 2-2 2H5c-2 0-2-2-2-2 0-5 5-4 5-15C8 7.478 12.477 3 18 3s10 4.478 10 10z"/><circle fill="#FFAC33" cx="18" cy="3" r="3"/><path fill="#FFAC33" d="M18 36c2.209 0 4-1.791 4-4h-8c0 2.209 1.791 4 4 4z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f51f.svg b/games/sound-soup/assets/img/1f51f.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#3B88C3" d="M36 32c0 2.209-1.791 4-4 4H4c-2.209 0-4-1.791-4-4V4c0-2.209 1.791-4 4-4h28c2.209 0 4 1.791 4 4v28z"/><path fill="#F5F8FA" d="M7.213 10.975H5.255C3.663 10.975 3 9.814 3 8.686c0-1.16.829-2.288 2.255-2.288h4.71c1.426 0 2.223 1.028 2.223 2.354v18.875c0 1.658-1.062 2.586-2.488 2.586s-2.487-.928-2.487-2.586V10.975zm7.967 7.231C15.18 12.799 17.27 6 23.97 6c6.7 0 8.791 6.799 8.791 12.206 0 5.406-2.091 12.207-8.791 12.207s-8.79-6.801-8.79-12.207zm12.406 0c0-2.521-.232-7.696-3.616-7.696-3.383 0-3.615 5.175-3.615 7.696 0 2.355.232 7.695 3.615 7.695 3.384 0 3.616-5.339 3.616-7.695z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f578.svg b/games/sound-soup/assets/img/1f578.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#66757F" d="M6.667 30.02l.479-.479c3.97-.534 7.971 1.17 10.369 4.362v.611c0 .268.217.486.486.486.268 0 .486-.217.486-.486v-.637c2.397-3.206 6.376-4.886 10.355-4.351l.479.479c.059.059.13.093.205.115l.029.006c.158.037.33.002.453-.121s.158-.295.121-.453l-.006-.029c-.022-.075-.056-.146-.115-.205l-.479-.479c-.535-3.979 1.145-7.958 4.351-10.355h.636c.268 0 .486-.217.486-.486 0-.268-.217-.486-.486-.486h-.61c-3.193-2.399-4.896-6.399-4.362-10.369l.479-.479c.19-.19.19-.497 0-.687-.066-.066-.147-.1-.231-.12-.159-.038-.332-.004-.456.12l-.479.479c-3.97.534-7.971-1.17-10.369-4.362v-.61c0-.268-.217-.486-.486-.486-.268 0-.486.217-.486.486v.637C15.118 5.328 11.139 7.007 7.16 6.472l-.479-.479c-.059-.059-.13-.093-.205-.115l-.029-.006c-.158-.037-.329-.002-.453.121-.123.123-.158.295-.121.453l.006.029c.022.075.056.146.115.205l.479.479c.535 3.979-1.145 7.958-4.351 10.355h-.636c-.269 0-.486.217-.486.485s.217.486.486.486h.611c3.193 2.399 4.896 6.399 4.362 10.369l-.479.479c-.124.124-.159.297-.12.456.02.084.055.165.12.231.19.19.497.19.687 0zm.86-21.807l2.206 2.206c.103 1.365-.113 2.744-.66 4.041-.493 1.17-1.252 2.217-2.159 3.053H3.613c.121-.11.271-.237.384-.35 2.352-2.352 3.641-5.606 3.53-8.95zm12.098 8.171c.055.589.259 1.15.597 1.636-.156.228-.294.469-.393.735-.107.288-.159.584-.187.882-.296.028-.59.08-.876.186-.263.098-.502.235-.728.389-.239-.168-.493-.316-.773-.42-.287-.107-.582-.159-.879-.187-.054-.58-.253-1.137-.583-1.617.156-.228.295-.469.393-.735.106-.286.159-.58.186-.876.298-.027.594-.08.882-.187.265-.098.506-.236.734-.392.484.332 1.042.532 1.627.586zm-4.673 2.101c.057.078.119.153.166.238.161.286.26.601.296.928.009.082 0 .166 0 .249l-1.544 1.544c-.094-.448-.211-.894-.373-1.331-.214-.575-.486-1.116-.802-1.627h2.257zm1.164 2.086c.073.001.146-.009.22 0 .199.022.397.059.59.131.135.05.261.115.382.186.074.043.138.1.207.15v2.267c-.51-.315-1.052-.588-1.627-.802-.437-.162-.883-.279-1.331-.373l1.559-1.559zm2.37 2.77v-2.248c.09-.068.178-.138.276-.192.285-.16.6-.259.926-.296.076-.009.154.001.231 0l1.539 1.539c-.448.094-.894.211-1.331.373-.566.21-1.136.514-1.641.824zm2.123-3.42c.001-.08-.009-.159 0-.239.022-.199.06-.397.131-.59.045-.12.105-.231.166-.341.053-.095.121-.18.185-.266h2.251c-.31.505-.614 1.075-.825 1.641-.162.437-.279.883-.373 1.331l-1.535-1.536zm.447-2.407c-.054-.074-.113-.144-.159-.223-.168-.292-.27-.615-.308-.95-.009-.08 0-.162 0-.243l1.541-1.541c.094.448.211.894.373 1.331.214.575.486 1.116.802 1.627h-2.249zm-1.161-2.095c-.076-.001-.152.008-.227 0-.331-.036-.649-.136-.938-.299-.086-.048-.163-.111-.243-.17v-2.255c.51.315 1.052.588 1.627.802.437.162.883.279 1.331.373l-1.55 1.549zm-2.38-2.762v2.282c-.083.061-.163.125-.253.175-.108.06-.217.119-.335.163-.193.072-.391.109-.59.131-.08.009-.159-.001-.239 0l-1.555-1.555c.448-.094.894-.211 1.331-.373.566-.209 1.136-.512 1.641-.823zm-2.101 3.442c-.001.077.008.154 0 .231-.037.327-.136.643-.297.929-.051.09-.116.171-.178.255h-2.28c.31-.505.614-1.075.825-1.641.162-.437.279-.883.373-1.331l1.557 1.557zm-2.827 4.351c.223.6.364 1.215.442 1.834l-.62.62-1.674 1.674c-.041-1.165-.285-2.325-.751-3.431-.417-.99-.99-1.88-1.681-2.662h3.224c.434.597.795 1.254 1.06 1.965zm.509 3.141l.62-.62c.619.078 1.235.219 1.834.442.711.264 1.368.625 1.965 1.06v3.224c-.782-.691-1.673-1.265-2.662-1.681-1.106-.466-2.266-.71-3.431-.751l1.674-1.674zm5.39 4.14v-3.219c.591-.428 1.279-.825 1.979-1.085.6-.223 1.215-.364 1.834-.442l2.294 2.294c-1.165.041-2.325.285-3.431.751-.978.413-1.899 1.02-2.676 1.701zm5.121-4.812l-.62-.62c.078-.619.219-1.235.442-1.834.26-.7.657-1.388 1.085-1.979h3.219c-.682.777-1.289 1.698-1.701 2.677-.466 1.106-.71 2.266-.751 3.431l-1.674-1.675zm.868-5.405c-.435-.597-.796-1.254-1.06-1.965-.223-.6-.364-1.215-.442-1.834l.62-.62 1.674-1.674c.041 1.165.285 2.325.751 3.431.417.99.99 1.88 1.681 2.662h-3.224zm-1.57-5.106l-.62.62c-.619-.078-1.235-.219-1.834-.442-.711-.264-1.368-.625-1.965-1.06V8.301c.782.691 1.673 1.265 2.662 1.681 1.106.466 2.266.71 3.431.751l-1.674 1.675zm-5.39-4.141v3.219c-.591.428-1.279.825-1.979 1.085-.6.223-1.215.364-1.834.442l-.62-.62-1.674-1.674c1.165-.041 2.325-.285 3.431-.751.978-.412 1.899-1.019 2.676-1.701zm-4.5 5.433c-.078.619-.219 1.235-.442 1.834-.26.7-.657 1.388-1.085 1.979H8.269c.682-.777 1.289-1.698 1.701-2.677.466-1.106.71-2.266.751-3.431l2.294 2.295zm-9.004 5.121c-.118-.118-.249-.223-.371-.336h3.304c.913.839 1.649 1.863 2.145 3.039.546 1.297.762 2.676.66 4.041l-.979.978-1.229 1.229c.111-3.345-1.177-6.599-3.53-8.951zm13.168 13.168c-2.352-2.352-5.607-3.641-8.951-3.53l1.229-1.229.977-.977c1.365-.103 2.744.113 4.041.66 1.175.496 2.2 1.231 3.039 2.145v3.304c-.112-.125-.217-.256-.335-.373zm1.657.014c-.113.113-.24.264-.35.384v-3.301c.837-.908 1.883-1.666 3.053-2.159 1.297-.546 2.676-.762 4.041-.66l2.206 2.206c-3.343-.111-6.597 1.178-8.95 3.53zm9.638-4.217l-1.229-1.229-.977-.977c-.103-1.365.113-2.744.66-4.041.493-1.17 1.252-2.217 2.159-3.053h3.301c-.121.11-.271.237-.384.35-2.352 2.352-3.641 5.606-3.53 8.95zm3.516-10.608c.118.118.249.223.371.336h-3.304c-.913-.839-1.649-1.863-2.145-3.039-.546-1.297-.762-2.676-.66-4.041l.977-.977 1.229-1.229c-.109 3.343 1.18 6.598 3.532 8.95zM18.822 4.01c2.352 2.352 5.607 3.641 8.951 3.53l-1.229 1.229-.977.977c-1.365.103-2.744-.113-4.041-.66-1.175-.496-2.2-1.231-3.039-2.145V3.639c.112.122.217.254.335.371zm-1.657-.014c.113-.113.24-.264.35-.384v3.301c-.837.908-1.883 1.666-3.053 2.159-1.297.546-2.676.762-4.041.66l-.978-.977-1.229-1.229c3.344.111 6.598-1.178 8.951-3.53z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f58a.svg b/games/sound-soup/assets/img/1f58a.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#C1694F" d="M3.968 33.446C2.211 35.204.47 36.312.079 35.921c-.391-.391.718-2.132 2.475-3.889 1.757-1.758 3.498-2.866 3.889-2.475.39.391-.718 2.131-2.475 3.889z"/><path fill="#FFCC4D" d="M6.443 29.558l-2.122-2.122s-1.083 1.393-2.284 3.63C1.137 32.742.786 33.8.786 33.8l.707.707.707.707s1.059-.352 2.734-1.251c2.237-1.201 3.629-2.284 3.629-2.284l-2.12-2.121z"/><path fill="#31373D" d="M34.02 1.98c-3.535-3.536-18.164 10.297-18.871 11.004-.707.707-1.304 1.348-.928 1.724L12.1 16.829c-2.122 2.122-8.486 9.899-7.779 10.606l2.122 2.122 2.121 2.121c.707.707 8.485-5.657 10.607-7.778l2.121-2.121c.376.376 1.017-.222 1.724-.929.707-.706 14.54-15.334 11.004-18.87z"/><path fill="#FFCC4D" d="M23.016 20.851c.085-.084.362-.375.774-.817l-7.823-7.822c-.442.411-.732.689-.817.774-.707.707-1.304 1.348-.928 1.724l3.535 3.536 3.536 3.535c.375.374 1.016-.223 1.723-.93z"/><path fill="#FFAC33" d="M31.212 2.001C30.285 1.074 29.048.102 27.28.986c-1.768.884-7.313 4.441-9.878 7.005-1.322 1.322-1.247 2.102-.751 2.165.821.106 1.37.088 2.122-.663.75-.751.883-2.033.883-2.033s3.05-2.342 5.458-3.778c2.409-1.437 3.268-1.682 4.11-.84.842.842 1.988-.841 1.988-.841z"/><path fill="#FFAC33" d="M28.87 2.487l4.643 4.643c.353-.709.631-1.384.818-2.011l-3.45-3.45c-.627.188-1.301.466-2.011.818z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f58c.svg b/games/sound-soup/assets/img/1f58c.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#3B88C3" d="M14.57 27.673c2.814-1.692 6.635-3.807 9.899-7.071 7.03-7.029 12.729-16.97 11.314-18.385C34.369.803 24.428 6.502 17.398 13.531c-3.265 3.265-5.379 7.085-7.071 9.899l4.243 4.243z"/><path fill="#C1694F" d="M.428 34.744s7.071 1.414 12.021-3.536c2.121-2.121 2.121-4.949 2.121-4.949l-2.829-2.829s-3.535.708-4.95 2.122c-1.414 1.414-2.518 4.232-2.888 5.598-.676 2.502-3.475 3.594-3.475 3.594z"/><path fill="#CCD6DD" d="M17.882 25.328l-5.168-5.168c-.391-.391-.958-.326-1.27.145l-1.123 1.705c-.311.471-.271 1.142.087 1.501l4.122 4.123c.358.358 1.03.397 1.501.087l1.705-1.124c.472-.311.536-.878.146-1.269z"/><path fill="#A0041E" d="M11.229 32.26c-1.191.769-1.826.128-1.609-.609.221-.751-.12-1.648-1.237-1.414-1.117.233-1.856-.354-1.503-1.767.348-1.393-1.085-1.863-1.754-.435-.582 1.16-1.017 2.359-1.222 3.115-.677 2.503-3.476 3.595-3.476 3.595s5.988 1.184 10.801-2.485z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f5d1.svg b/games/sound-soup/assets/img/1f5d1.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#9AAAB4" d="M31.5 4l-1.784 4.542L27.932 4h-4.147l-1.926 4.903L19.932 4h-3.863l-1.926 4.903L12.216 4H8.068L6.284 8.542 4.5 4H2.133l3.735 28.013h24.263L33.867 4H31.5zm-5.642 1.678l2.459 6.425-2.601 6.621-2.457-6.254 2.599-6.792zM18 6.049l2.457 6.42L18 18.723l-2.457-6.254L18 6.049zm-7.858-.371l2.599 6.792-2.457 6.254-2.601-6.621 2.459-6.425zM5.958 16.611l.326-.853 2.549 6.66-1.573 4.004-1.302-9.811zM8.068 32l2.216-5.79L12.5 32H8.068zm3.668-9.582l2.407-6.288 2.406 6.288-2.406 6.126-2.407-6.126zM15.784 32L18 26.21 20.216 32h-4.432zm3.667-9.582l2.406-6.288 2.406 6.288-2.406 6.126-2.406-6.126zM23.5 32l2.216-5.79L27.932 32H23.5zm5.392-5.192l-1.725-4.39 2.549-6.659.411 1.074-1.235 9.975z"/><path fill="#67757F" d="M32 34c0-1.104-.896-2-2-2H6c-1.104 0-2 .896-2 2s.896 2 2 2h24c1.104 0 2-.896 2-2zm4-32c0-1.105-.896-2-2-2H2C.896 0 0 .896 0 2c0 1.105.896 2 2 2h32c1.104 0 2-.895 2-2z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f5fa.svg b/games/sound-soup/assets/img/1f5fa.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#CCD6DD" d="M12 7.607L3 5.196c-1.657-.444-3 .539-3 2.197v20c0 1.656 1.343 3.359 3 3.804l9 2.411V7.607zm12 22.786l9 2.411c1.656.443 3-.54 3-2.196v-20c0-1.657-1.344-3.36-3-3.804l-9-2.411v26z"/><path fill="#E1E8ED" d="M24 30.392l-12 3.215v-26l12-3.215z"/><path fill="#55ACEE" d="M12 9.607L4 7.463c-1.104-.296-2 .36-2 1.464v18c0 1.104.896 2.24 2 2.535l8 2.145v-22zm20-1.072l-8-2.144v22l8 2.144c1.104.296 2-.359 2-1.465v-18c0-1.103-.896-2.239-2-2.535z"/><path fill="#88C9F9" d="M24 28.392l-12 3.215v-22l12-3.215z"/><path fill="#5C913B" d="M12 21.506c-.268.006-.595.104-.845.145-.436.073-.329.517-.463.443-.135-.074-.475-.265-.812-.658-.337-.394.25-.477.418-.622.168-.145-.316-.172-.485-.332-.169-.159-.471.104-.739-.276-.27-.376-.101-.79-.133-1.18-.034-.39.436-.341.739-.334.303.003.538.144.538.602 0 .456.37.708.269.146-.101-.561.101-.657.235-.89.134-.23.37-.55.672-1.04.221-.358.389-.283.606-.164v-3.345c-.055-.044-.11-.09-.165-.113-.303-.132.096.753-.139 1.109-.237.356-.598.193-.97.094-.369-.099-.713-.545-.443-1.007.268-.462.782-.268 1.051-.653.269-.385-.957-.672-1.394-.941-.438-.271-.963-.409-1.4-.146-.437.264-1.373.133-1.743.186-.37.054-.436-.369-.503-.693-.067-.322-.653-.329-1.257-.338-.606-.01-1.741.281-1.547 1.759.068.514.605.124.707.036.101-.088.605.2.671.637.068.439.035.887-.099 1.232-.137.345.336 1.006.604 1.612.269.605.573.686.707.723.135.036.908.587.875.996-.033.411.706.991 1.009 1.34.303.35.494.662.887.846.393.183.852 1.035 1.054 1.165.155.1.418.451.576.58-.208.242-.612.748-.612 1.029 0 .383.44 1.103.608 1.3.167.196.65.442.751.85.101.407-.07 1.646-.372 1.907-.304.262-.414 1.043-.414 1.043s.107.293.465.124c.309-.144.776-.566 1.094-.801v-6.371z"/><path fill="#77B255" d="M12.268 17.413c-.1.016-.187.012-.268.011v-3.345c.254.065.512.317.707.736.234.509.604.066.739.374.134.308.875.758.505.742-.371-.015-.741.008-.841.454-.102.448.168.794-.102.828-.27.033-.37.142-.74.2zm2.59 5.558c-.436-.074-.359-.023-.628-.599-.268-.577-.431-.743-1.036-.656-.605.087-.583.136-.751-.163-.072-.128-.241-.082-.443.031v6.37c.09-.114.17-.211.228-.267.505-.477.996-1.026 1.466-1.646.47-.621.3-.924.535-1.481.236-.558.541-.617.944-1.145.405-.53.122-.368-.315-.444zM24 15.731V9.417c-.04.004-.068.012-.11.015-.209.011-.482.135-.779.32-.024-.005-.046-.016-.07-.021-.896-.175-1.312 1.205-2.039 1.152-.729-.053-1.7-.344-2.332.514-.631.857-.777 1.53 0 1.294s1.312-1.425 1.7-1.089c.388.336.29.721-.632 1.105-.922.385-1.918.817-2.452.96-.534.143-.655.534-.292.822.364.288-.219.471-.826 1.02-.607.547.146.512.656.705.51.193.898-.323 1.117-.932.217-.608 1.158-1.521 1.55-1.544.392-.023.392.197.318.602-.013.071.001.103.011.138-.17.54-.31 1.03-.365 1.306-.026.135-.031.351-.027.595-.086.004-.183.021-.248.001-.438-.129-1.434-.22-1.701.043-.267.265-.412.502-.728.723-.317.219-1.603 1.224-1.627 2.084-.025.859-.049 1.83.461 1.721.51-.109 1.749-.826 2.137-1.288.387-.463.921-.522 1.092-.156.17.369.048.649-.12 1.354-.171.708.11.963.381 1.468.273.504.491.775.491 1.189 0 .412.389.723.97-.123.188-.27.367-.606.536-.957.409-.469.768-.923.758-1.096-.016-.281-.021-.434-.024-.624l.091-.22c.127-.293.241-.746.362-1.155.007-.01.013-.017.02-.024.237-.337.185-.353.58-.756.264-.27.254-.512.214-.678.105-.175.134-.28-.033-.235l-.016.004c-.002 0-.005.019-.006.014-.023-.149-.206.003-.501.148-.284.008-.566-.066-.668-.368-.133-.396-.602-.996-.639-1.336.003-.041.005-.077.01-.124.014-.036.033-.066.069-.088.038-.022.075-.03.112-.035.041.111.075.279.086.464.026.477.394.196.394.498 0 .303.53.492.661.548.037.016.094-.027.159-.098.031.011.06.033.093.037.292.031.922-.984 1.117-1.164.102-.095.104-.269.033-.394l.056-.025z"/><path fill="#5C913B" d="M27.634 20.221c-.053.44.184.201.606.705.423.508-.104.394-.289.316-.185-.08-.37-.131-.579-.518-.213-.391 0-.695 0-1.027 0 0 .316.084.262.524zm.362-.901c.389.166.114-.482.244-.841.132-.356.316-.368.555-.363.236.002.422-.191.581-.389.157-.199.448-.454.422-.733-.026-.279-.104-.482-.212-.843-.105-.36.316-.368.502-.711.184-.343 0 0 .421-.008.422-.008.238-.058.316-.218.08-.159.133-.327.054-.68-.078-.353-.37-.1-.66-.177-.289-.077.106-.425.132-.78.026-.356.397-.165.661-.125.263.039.342.484.421.597.081.112.895-.365 1.108-.641.211-.275-.186-.291-.079-.403.106-.111-.632-.337-.925-.469-.289-.133-1.028-.367-1.318-.649-.289-.283-1.396-.426-1.688-.337-.291.086-.476-.143-.606-.406-.131-.262-.686-.297-.844-.467-.158-.17-.316.127-.529.16-.21.035-.289.043-.554-.209-.263-.252.08-.371-.288-.621-.355-.238-.813-.182-1.71-.591v6.314c.157.014.309.036.364.051.131.035.448.573.448.784 0 .211.158.828.291 1.195.131.366.42.506.686.818.264.312.394.137.394.137s-.105-.544-.105-.965c0-.424.316-.701.316-.701s.5.558.528.685c.026.128.421.656.449.876.026.219.237.546.625.71zm2.092-2.068c.184.08.976.171.554-.334-.423-.506-.106-.299-.053-.738.053-.44-.264-.524-.264-.524 0 .332-.211.638 0 1.026.21.39-.423.49-.237.57zm2.004 5.371c-.131-.217-.421-.385-.738-.461-.315-.076-.29-.48-.606-.533-.316-.055-.544-.389-.686-.512-.312-.266-.106.209 0 .699.106.488.501.246.869.586.369.34.397.016.74.229.342.211.475.52.804.578.329.059-.25-.369-.383-.586zm-3.088-1.432c.262.402.159.043.504-.016.343-.059-.291-.561-.502-.799-.211-.238-.42-.301-.605.049-.21.399.342.364.603.766zm2.403 2.579c-.133-.338-.518-.713-.675-.91-.159-.193-.407-.312-.618-.279-.212.031-.343-.092-.448-.361-.106-.266-.29-.233-.449-.244-.157-.012-.791.119-1.161.23-.368.113-.537.252-.778.457-.382.32-.17.105 0 .424.168.318-.187 1.066-.213 1.543-.027.477.676.031.939.039.264.012.186-.189.396-.314.212-.125.349-.129.579.172.233.305.537.611.537.883 0 .27.834.947 1.151.852.316-.098.501-.408.791-.635.291-.225.186-.824.264-1.168.081-.344-.185-.354-.315-.689z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f60b.svg b/games/sound-soup/assets/img/1f60b.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><circle fill="#FFCC4D" cx="18" cy="18" r="18"/><path fill="#664500" d="M6.999 17c-.15 0-.303-.034-.446-.105-.494-.247-.694-.848-.447-1.342C6.158 15.448 7.424 13 11 13c3.577 0 4.842 2.449 4.894 2.553.247.494.046 1.095-.447 1.342-.491.247-1.085.049-1.336-.436C14.067 16.378 13.28 15 11 15c-2.317 0-3.099 1.433-3.106 1.447-.175.351-.528.553-.895.553zM29 17c.15 0 .303-.034.446-.105.494-.247.694-.848.447-1.342-.052-.105-1.318-2.553-4.894-2.553-3.577 0-4.842 2.449-4.894 2.553-.247.494-.046 1.095.447 1.342.491.247 1.085.049 1.336-.436.044-.081.831-1.459 3.111-1.459 2.317 0 3.099 1.433 3.106 1.447.175.351.528.553.895.553zm.503 6.52c.323-.749.461-1.257.466-1.277.134-.534-.19-1.075-.725-1.21-.536-.136-1.077.188-1.215.723C27.955 22.051 26.081 29 18 29s-9.955-6.949-10.03-7.245c-.138-.534-.679-.858-1.215-.723-.534.135-.858.676-.725 1.21C6.052 22.33 8.331 31 18 31c1.65 0 3.07-.266 4.315-.685 4.33-1.458 6.374-4.91 7.188-6.795z"/><path fill="#E75B70" d="M32.407 26.681l-2.956-3.051s-.68-.438-1.273.568c-.78 1.322-2.075 2.935-3.934 4.171-.781.519-2.139 1.189-2.697 1.318-.735.169-.388.964-.388.964.585-.127 1.051-.301 1.051-.301l.296.296 2.726 2.726c1.728 1.728 5 2 7 0s1.904-4.962.175-6.691z"/><path fill="#DE3146" d="M29.915 31.074c.213-.213.213-.558 0-.771l-3.629-3.629-.796.748 3.653 3.653c.213.212.559.212.772-.001z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f620.svg b/games/sound-soup/assets/img/1f620.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#FFCC4D" d="M36 18c0 9.941-8.059 18-18 18-9.94 0-18-8.059-18-18C0 8.06 8.06 0 18 0c9.941 0 18 8.06 18 18"/><path fill="#664500" d="M25.485 29.879C25.44 29.7 24.317 25.5 18 25.5c-6.318 0-7.44 4.2-7.485 4.379-.055.217.043.442.237.554.195.109.439.079.6-.077.019-.019 1.954-1.856 6.648-1.856s6.63 1.837 6.648 1.855c.096.095.224.145.352.145.084 0 .169-.021.246-.064.196-.112.294-.339.239-.557zm-9.778-12.586C12.452 14.038 7.221 14 7 14c-.552 0-.999.447-.999.998-.001.552.446 1 .998 1.002.029 0 1.925.022 3.983.737-.593.64-.982 1.634-.982 2.763 0 1.934 1.119 3.5 2.5 3.5s2.5-1.566 2.5-3.5c0-.174-.019-.34-.037-.507.013 0 .025.007.037.007.256 0 .512-.098.707-.293.391-.391.391-1.023 0-1.414zM29 14c-.221 0-5.451.038-8.707 3.293-.391.391-.391 1.023 0 1.414.195.195.451.293.707.293.013 0 .024-.007.036-.007-.016.167-.036.333-.036.507 0 1.934 1.119 3.5 2.5 3.5s2.5-1.566 2.5-3.5c0-1.129-.389-2.123-.982-2.763 2.058-.715 3.954-.737 3.984-.737.551-.002.998-.45.997-1.002-.001-.551-.447-.998-.999-.998z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f622.svg b/games/sound-soup/assets/img/1f622.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#FFCC4D" d="M36 18c0 9.941-8.059 18-18 18-9.94 0-18-8.059-18-18C0 8.06 8.06 0 18 0c9.941 0 18 8.06 18 18"/><ellipse fill="#664500" cx="11.5" cy="17" rx="2.5" ry="3.5"/><ellipse fill="#664500" cx="24.5" cy="17" rx="2.5" ry="3.5"/><path fill="#664500" d="M5.999 13.5c-.208 0-.419-.065-.599-.2-.442-.331-.531-.958-.2-1.4 3.262-4.35 7.616-4.4 7.8-4.4.552 0 1 .448 1 1 0 .551-.445.998-.996 1-.155.002-3.568.086-6.204 3.6-.196.262-.497.4-.801.4zm24.002 0c-.305 0-.604-.138-.801-.4-2.641-3.521-6.061-3.599-6.206-3.6-.55-.006-.994-.456-.991-1.005.003-.551.447-.995.997-.995.184 0 4.537.05 7.8 4.4.332.442.242 1.069-.2 1.4-.18.135-.39.2-.599.2zm-6.516 14.879C23.474 28.335 22.34 24 18 24s-5.474 4.335-5.485 4.379c-.053.213.044.431.232.544.188.112.433.086.596-.06C13.352 28.855 14.356 28 18 28c3.59 0 4.617.83 4.656.863.095.09.219.137.344.137.084 0 .169-.021.246-.064.196-.112.294-.339.239-.557z"/><path fill="#5DADEC" d="M16 31c0 2.762-2.238 5-5 5s-5-2.238-5-5 4-10 5-10 5 7.238 5 10z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f634.svg b/games/sound-soup/assets/img/1f634.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><circle fill="#FFCC4D" cx="18" cy="18" r="18"/><circle fill="#664500" cx="18" cy="26" r="3"/><path fill="#664500" d="M17.312 16.612c-.176-.143-.427-.147-.61-.014-.012.009-1.26.902-3.702.902-2.441 0-3.69-.893-3.7-.9-.183-.137-.435-.133-.611.009-.178.142-.238.386-.146.594.06.135 1.5 3.297 4.457 3.297 2.958 0 4.397-3.162 4.457-3.297.092-.207.032-.449-.145-.591zm10 0c-.176-.143-.426-.148-.61-.014-.012.009-1.261.902-3.702.902-2.44 0-3.69-.893-3.7-.9-.183-.137-.434-.133-.611.009-.178.142-.238.386-.146.594.06.135 1.5 3.297 4.457 3.297 2.958 0 4.397-3.162 4.457-3.297.092-.207.032-.449-.145-.591z"/><path fill="#FFAC33" d="M34.43 12.534c.004-.044.023-.077.023-.123 0-.754-.548-1.188-1.225-1.188h-3.582l4.279-5.993c.206-.283.282-.453.282-.811 0-.735-.64-.849-.885-.849h-5.349l-.032-.688s-1.409.831-1.503.888-.505.372-.505.987c0 .754.546 1.187 1.225 1.187h3.149l-4.261 5.993c-.094.151-.244.433-.244.735 0 .622.508.924 1.111.924h6.315c.258 0 .515-.076.681-.176l1.503-.888h-.982z"/><path fill="#2A6797" d="M31.771 5.084h-3.149c-.679 0-1.225-.433-1.225-1.187s.546-1.188 1.225-1.188h6.164c.245 0 .885.113.885.848 0 .358-.076.528-.282.811l-4.279 5.993h3.582c.677 0 1.225.433 1.225 1.187s-.548 1.187-1.225 1.187h-6.315c-.603 0-1.111-.302-1.111-.924 0-.302.15-.584.244-.735l4.261-5.992z"/><path fill="#FFAC33" d="M24.886 7.48c.003-.033.018-.058.018-.092 0-.564-.41-.889-.917-.889h-2.682l3.203-4.487c.156-.212.212-.339.212-.606 0-.55-.479-.635-.663-.635h-4.004l-.024-.515s-1.055.622-1.125.665c-.07.043-.378.279-.378.739 0 .564.409.889.917.889H21.8l-3.19 4.487c-.07.113-.183.324-.183.55 0 .466.38.691.832.691h4.728c.193 0 .385-.057.51-.132l1.125-.665h-.736z"/><path fill="#2A6797" d="M22.896 1.903h-2.357c-.508 0-.917-.324-.917-.889 0-.564.409-.889.917-.889h4.615c.184 0 .663.085.663.635 0 .268-.057.395-.211.607l-3.203 4.487h2.682c.505 0 .915.324.915.889s-.41.889-.917.889h-4.728c-.452 0-.832-.226-.832-.691 0-.226.113-.437.183-.55l3.19-4.488z"/><path fill="#FFAC33" d="M17.741 10.425c.003-.028.015-.049.015-.079 0-.483-.351-.761-.785-.761h-2.295l2.742-3.84c.132-.181.181-.29.181-.519 0-.471-.41-.544-.567-.544h-3.427l-.021-.441-.963.569c-.06.037-.324.238-.324.633 0 .483.35.761.785.761h2.017l-2.73 3.84c-.06.097-.157.278-.157.471 0 .399.326.592.712.592h4.047c.165 0 .33-.049.436-.113l.963-.569h-.629z"/><path fill="#2A6797" d="M16.037 5.652H14.02c-.435 0-.785-.278-.785-.761s.35-.761.785-.761h3.95c.157 0 .567.073.567.544 0 .229-.048.338-.181.519l-2.742 3.84h2.295c.434 0 .785.278.785.761s-.351.761-.785.761h-4.047c-.386 0-.712-.193-.712-.592 0-.193.096-.374.157-.471l2.73-3.84z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f680.svg b/games/sound-soup/assets/img/1f680.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#A0041E" d="M1 17l8-7 16 1 1 16-7 8s.001-5.999-6-12-12-6-12-6z"/><path fill="#FFAC33" d="M.973 35s-.036-7.979 2.985-11S15 21.187 15 21.187 14.999 29 11.999 32c-3 3-11.026 3-11.026 3z"/><circle fill="#FFCC4D" cx="8.999" cy="27" r="4"/><path fill="#55ACEE" d="M35.999 0s-10 0-22 10c-6 5-6 14-4 16s11 2 16-4c10-12 10-22 10-22z"/><path d="M26.999 5c-1.623 0-3.013.971-3.641 2.36.502-.227 1.055-.36 1.641-.36 2.209 0 4 1.791 4 4 0 .586-.133 1.139-.359 1.64 1.389-.627 2.359-2.017 2.359-3.64 0-2.209-1.791-4-4-4z"/><path fill="#A0041E" d="M8 28s0-4 1-5 13.001-10.999 14-10-9.001 13-10.001 14S8 28 8 28z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f68c.svg b/games/sound-soup/assets/img/1f68c.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#808285" d="M0 21v7c0 1.657 1.343 3 3 3h30c1.657 0 3-1.343 3-3v-7H0z"/><path fill="#CCD6DD" d="M36 22v-9c0-1.657-3.343-3-5-3H11c-8 0-11 2.343-11 4v8h36z"/><path fill="#939598" d="M0 22h36v3H0z"/><path fill="#BCBEC0" d="M7 25c-3.063 0-5.586 2.298-5.95 5.263.526.453 1.202.737 1.95.737h10c0-3.313-2.686-6-6-6zm27.95 5.263C34.586 27.298 32.063 25 29 25c-3.313 0-6 2.687-6 6h10c.749 0 1.425-.284 1.95-.737z"/><circle cx="7" cy="31" r="4"/><circle fill="#99AAB5" cx="7" cy="31" r="2"/><circle cx="29" cy="31" r="4"/><circle fill="#99AAB5" cx="29" cy="31" r="2"/><path fill="#F4900C" d="M0 25h1v2H0zm35-2h1v2h-1z"/><path fill="#58595B" d="M1 13h35v10H1z"/><path fill="#292F33" d="M2 13H.342C.11 13.344 0 13.685 0 14v11h2c1.104 0 2-.896 2-2v-8c0-1.104-.896-2-2-2z"/><path fill="#55ACEE" d="M31 20c0 .553-.447 1-1 1H7c-.552 0-1-.447-1-1v-4c0-.552.448-1 1-1h23c.553 0 1 .448 1 1v4z"/><path fill="#FFAC33" d="M35 19h1v2h-1z"/><path fill="#55ACEE" d="M1 15H0v8h1c.552 0 1-.447 1-1v-6c0-.552-.448-1-1-1z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f690.svg b/games/sound-soup/assets/img/1f690.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#CCD6DD" d="M35 10c0-1-1-4-4-4H9.401C6 6 0 18 0 21v6c0 2.209 1.791 4 4 4h28c2.209 0 4-1.791 4-4V17c0-1.027-1-7-1-7z"/><path fill="#66757F" d="M0 23h36v3H0z"/><path fill="#E1E8ED" d="M9 25c-3.267 0-5.918 2.612-5.993 5.861.32.081.648.139.993.139h11c0-3.313-2.686-6-6-6zm23.993 5.86C32.918 27.612 30.268 25 27 25c-3.312 0-6 2.687-6 6h11c.345 0 .674-.058.993-.14z"/><path fill="#99AAB5" d="M5.686 26h6.629c-.95-.631-2.088-1-3.314-1-1.227 0-2.366.368-3.315 1zm18.001 0h6.629c-.949-.632-2.089-1-3.315-1s-2.364.369-3.314 1z"/><circle fill="#292F33" cx="9" cy="31" r="4"/><circle fill="#99AAB5" cx="9" cy="31" r="2"/><circle fill="#292F33" cx="27" cy="31" r="4"/><circle fill="#99AAB5" cx="27" cy="31" r="2"/><path fill="#55ACEE" d="M5 13h6v7H2zm8 0h8v7h-8zm10 0v7h13v-3c0-.526-.262-2.347-.518-4H23z"/><path fill="#99AAB5" d="M33 11H7c-.552 0-1-.448-1-1s.448-1 1-1h26c.553 0 1 .448 1 1s-.447 1-1 1z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f695.svg b/games/sound-soup/assets/img/1f695.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#CCD6DD" d="M21.377 15.449c.089.816-.83 1.495-2.053 1.515-1.223.02-2.095-.635-1.947-1.463l.356-2c.147-.829.938-1.5 1.767-1.5.828 0 1.572.662 1.661 1.478l.216 1.97z"/><path fill="#FFCC4D" d="M32.246 21h-.135l-.444-3c-.327-2.209-1.864-4-3.433-4H16.162c-1.569 0-3.574 1.791-4.478 4l-1.228 3H6.911c-2.073 0-4.104 1.791-4.538 4l-.588 3c-.001.008 0 .015-.002.021C.782 28.132 0 28.97 0 30c0 1.104.896 2 2 2h30c2.209 0 4-1.791 4-4v-3c0-2.209-1.681-4-3.754-4z"/><circle fill="#292F33" cx="10" cy="31" r="4"/><circle fill="#CCD6DD" cx="10" cy="31" r="2"/><circle fill="#292F33" cx="27" cy="31" r="4"/><circle fill="#CCD6DD" cx="27" cy="31" r="2"/><path fill="#F4900C" d="M2.373 25l-.196 1H36v-1c0-.348-.055-.679-.133-1H2.702c-.141.321-.261.652-.329 1z"/><path d="M24 21h5.92c-.014-.416-.064-.908-.156-1.5-.395-2.5-.552-3.5-3.437-3.5H24v5zm-2-5h-5c-1.594 0-3.236 1.567-3.847 3.5-.194.599-.353 1.088-.472 1.5H22v-5z" fill="#55ACEE"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f69a.svg b/games/sound-soup/assets/img/1f69a.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#DD2E44" d="M36 27c0 2.209-1.791 4-4 4H4c-2.209 0-4-1.791-4-4v-3c0-2.209 1.791-4 4-4h28c2.209 0 4 1.791 4 4v3z"/><path fill="#FFCC4D" d="M19 13l-.979-1H7.146C4 12 3 14 3 14l-3 5.959V25h19V13z"/><path fill="#55ACEE" d="M9 20H2l2-4s1-2 3-2h2v6z"/><circle fill="#292F33" cx="9" cy="31" r="4"/><circle fill="#CCD6DD" cx="9" cy="31" r="2"/><circle fill="#292F33" cx="27" cy="31" r="4"/><circle fill="#CCD6DD" cx="27" cy="31" r="2"/><path fill="#CCD6DD" d="M32 8H17c-2.209 0-4 1.791-4 4v13h23V12c0-2.209-1.791-4-4-4z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f6a2.svg b/games/sound-soup/assets/img/1f6a2.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#292F33" d="M16 0h4v5h-4z"/><path fill="#D1D3D4" d="M16 2h4v4h-4z"/><path fill="#A7A9AC" d="M18 2h2v4h-2z"/><path fill="#66757F" d="M18 19H5s1.355 8 4.875 14C13.394 39 18 33 18 33s4.606 6 8.125 0C29.645 27 31 19 31 19H18z"/><path fill="#99AAB5" d="M18 19H5s1.355 8 4.875 14C13.394 39 18 33 18 33V19z"/><path fill="#55ACEE" d="M0 30h36v6H0z"/><path fill="#E6E7E8" d="M30 9h-2.45l-.242-4H8.692l-.241 4H6v6h2.088l-.242 4h20.308l-.242-4H30z"/><path fill="#D1D3D4" d="M10 15h16v4H10z"/><path fill="#6D6E71" d="M8 11h20v2H8z"/><path fill="#BCBEC0" d="M10 7h16v2H10z"/><path fill="#BE1931" d="M5.913 23h24.174c.201-.747.368-1.427.5-2H5.413c.132.573.299 1.253.5 2z"/><path fill="#A0041E" d="M18 23h12.087c.201-.747.368-1.427.5-2H18v2z"/><path fill="#BCBEC0" d="M12 17h12v2H12z"/><path fill="#58595B" d="M16 0h2v2h-2z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f6a4.svg b/games/sound-soup/assets/img/1f6a4.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#5C913B" d="M33 27h-2l1-8h2z"/><path fill="#DD2E44" d="M1 14c9.055.053 11 1 17 2 2.959.493 7 1 11 1 2 0 4 1 4 4 0 2 .534 3.187-5.433 3.815C23.59 25.233 13 23 9 21c-6-3-9-7.005-8-7z"/><path fill="#55ACEE" d="M0 24h36v12H0z"/><path fill="#FFCC4D" d="M5 13c4 0 11 0 18 1s10 1 10 3v3c0 1.414-6.869.447-10 0-7-1-14-3-18-4-4.047-1.011-7-3 0-3z"/><path fill="#A0041E" d="M30.592 24.322c2.122-.539 2.403-1.307 2.418-2.361-1.263.619-3.699.32-5.886.047-7.96-.995-16.481-2.935-24.722-5.608 1.043 1.04 2.584 2.29 4.568 3.483 6.706 1.901 13.484 3.307 19.905 4.109 1.259.157 2.53.312 3.717.33z"/><path fill="#55ACEE" d="M12 24c3-2 7-5 6-3s-1.001 3 1 2c2-1 5-2 4 0s2 1 3 0 4 0 3 1-9 2-12 2-5-2-5-2z"/><path fill="#5C913B" d="M34 16h-1c-1.104 0-2 .896-2 2h1c.553 0 1 .447 1 1v1h1c1.104 0 2 .104 2-1s-.896-3-2-3z"/><path fill="#C1694F" d="M30 16c.97.243 0 2-1 3s-10-1-10-1l2-3s5 0 9 1z"/><path fill="#A0041E" d="M29 19c.328-.328.645-.737.899-1.147-1.475-.764-3.63-1.385-6.079-1.664-1.249-.142-2.441-.178-3.527-.128L19 18s9 2 10 1z"/><path fill="#55ACEE" d="M21 15v-1l-4 1-2 2 4 1z"/><path fill="#292F33" d="M23 14c-.553 0-1 1.343-1 3 0 .612.062 1.181.167 1.656.502.127 1.035.221 1.592.278.148-.524.241-1.193.241-1.934 0-1.656-.447-3-1-3z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f6a9.svg b/games/sound-soup/assets/img/1f6a9.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#66757F" d="M13 34s0 2-2 2-2-2-2-2V2s0-2 2-2 2 2 2 2v32z"/><path fill="#DD2E44" d="M11 4c0-2.2 1.636-3.25 3.636-2.333l16.727 7.667c2 .917 2 2.417 0 3.333l-16.727 7.667C12.636 21.25 11 20.2 11 18V4z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f6b0.svg b/games/sound-soup/assets/img/1f6b0.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#269" d="M0 4c0-4 4-4 4-4h28s4 0 4 4v28s0 4-4 4H4s-4 0-4-4V4z"/><g fill="#FFF"><path d="M33 9H22V3s0-1-1-1-1 1-1 1v6c-8 0-8 7-8 7v2c0 1 1 1 1 1h3s1 0 1-1v-2c0-2 2-2 2-2h14c1 0 1-1 1-1v-3s0-1-1-1z"/><path d="M18.5 3h-3c-.829 0-1.5.671-1.5 1.5S14.671 6 15.5 6h3c.828 0 1.5-.671 1.5-1.5S19.328 3 18.5 3zm5 3h3c.828 0 1.5-.671 1.5-1.5S27.328 3 26.5 3h-3c-.828 0-1.5.671-1.5 1.5S22.672 6 23.5 6z"/></g><path fill="#FFF" d="M20 20c-1 0-1 1-1 1v5h-9v-5s0-1-1-1-1 1-1 1v11s0 2 2 2h9c2 0 2-2 2-2V21s0-1-1-1z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f6c1.svg b/games/sound-soup/assets/img/1f6c1.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><ellipse fill="#99AAB5" cx="32" cy="16" rx="2" ry="1"/><path fill="#CCD6DD" d="M33 21c-.553 0-1-.447-1-1V4c0-1.582-1.962-2-3-2s-3 .418-3 2c0 .552-.447 1-1 1s-1-.448-1-1c0-2.626 2.516-4 5-4s5 1.374 5 4v16c0 .553-.447 1-1 1z"/><path fill="#99AAB5" d="M28 6c0 1.104-1.343 1-3 1s-3 .104-3-1 1.343-2 3-2 3 .896 3 2z"/><path fill="#CCD6DD" d="M29 7c0 .552-.447 1-1 1h-6c-.553 0-1-.448-1-1s.447-1 1-1h6c.553 0 1 .448 1 1z"/><circle fill="#99AAB5" cx="12" cy="34" r="2"/><circle fill="#99AAB5" cx="28" cy="34" r="2"/><path fill="#CCD6DD" d="M30 34H12C7 34 2 28 1 20c-.248-1.984.812-1 3.021-1C4.021 19 17 21 30 21c2 0 5-1.209 5 1 0 0-1 12-5 12z"/><path fill="#99AAB5" d="M36 21c0 .553-.447 1-1 1L1 20c-.552 0-1-.447-1-1 0-.553.448-1 1-1l34 2c.553 0 1 .447 1 1z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f6cf.svg b/games/sound-soup/assets/img/1f6cf.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#C1694F" d="M1 28h34v5H1z"/><path fill="#C1694F" d="M36 35c0 .553-.447 1-1 1-.553 0-1-.447-1-1V21c0-.553.447-1 1-1 .553 0 1 .447 1 1v14zM0 35c0 .553.447 1 1 1 .553 0 1-.447 1-1V13c0-.552-.447-1-1-1-.553 0-1 .448-1 1v22z"/><path fill="#269" d="M32 23H4c-1.104 0-2 .896-2 2v4h32v-4c0-1.104-.896-2-2-2z"/><path fill="#55ACEE" d="M32 23H4c-1.104 0-2 .896-2 2v4h32v-4c0-1.104-.896-2-2-2z"/><path fill="#5C913B" d="M2.126 19.938c-.261 1.073 1.485 2.419 3.9 3.006 2.415.587 4.584.192 4.846-.881.261-1.073-1.485-2.419-3.9-3.007-2.415-.587-4.585-.192-4.846.882z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f6d6.svg b/games/sound-soup/assets/img/1f6d6.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#D99E82" d="M31 18H5c-1.1 0-2 .9-2 2v15c0 .55.45 1 1 1h28c.55 0 1-.45 1-1V20c0-1.1-.9-2-2-2z"/><path fill="#662113" d="M14 26h8v10h-8z"/><path fill="#C1694F" d="M33 24.671v-2.347H3v2.34c.32-.133.625-.37.625-.37s.491.458.982.542c.491.083.848-.583.848-.583s.357.5 1.027.5c.67 0 .804-.5.804-.5s.179.5.804.542c.625.042.848-.5.848-.5s.134.458.893.5c.759.042.803-.542.803-.542s.223.417.759.458c.536.042.848-.417.848-.417s.357.5.938.5c.58 0 .848-.417.848-.417s.089.542.67.542c.58 0 .893-.583.893-.583s0 .542.67.542.893-.5.893-.5 0 .542.625.542.982-.542.982-.542.045.583.804.542c.759-.042.938-.542.938-.542s.223.5.759.542c.536.042.803-.542.803-.542s.045.583.714.5c.67-.083.803-.542.803-.542s.089.667.759.625c.67-.042.848-.583.848-.583s.179.583.803.583c.625 0 .759-.542.759-.542s0 .583.714.542c.714-.042.982-.583.982-.583s.223.542.938.542.759-.5.759-.5.223.5.848.458c.625-.042.759-.458.759-.458s.357.458.804.458c.184-.001.329-.099.446-.207z"/><path opacity=".3" fill="#662113" d="M5 36H4c-.552 0-1-.448-1-1V21h2v15zm27 0h-1V21h2v14c0 .552-.448 1-1 1z"/><path opacity=".15" fill="#662113" d="M5 21h5v15H5zm21 0h5v15h-5z"/><path fill="#FFD983" d="M18.242 4.234c-.177-.129-.4-.132-.58-.007-1.179.823-5.745 4.565-10.379 9.037-3.675 3.546-6.071 7.361-7.162 8.794-.284.374-.033.928.416.928h34.928c.461 0 .707-.584.398-.951-1.562-1.856-5.152-6.882-8.193-9.703-3.573-3.313-8.235-7.228-9.428-8.098z"/><path fill="#FFD983" d="M16.997 5.203s-1.003-1.274-.821-1.813c.148-.437.775-.343.867 0 0 0 .182-.784.593-.882.41-.098.593.735.593.735s.365-.735.775-.588c.41.147.137.931.137.931s.319-.539.775-.196-1.231 2.058-1.231 2.058-1.368.294-1.688-.245zm-15.415 17.1c-.228.735.319 1.225.73 1.274s1.049-.539 1.049-.539.502.539 1.003.637.867-.686.867-.686.365.588 1.049.588.821-.588.821-.588.182.588.821.637.867-.588.867-.588.137.539.912.588c.775.049.821-.637.821-.637s.228.49.775.539c.547.049.866-.49.866-.49s.365.588.958.588.867-.49.867-.49.091.637.684.637c.593 0 .912-.686.912-.686s0 .637.684.637.912-.588.912-.588 0 .637.639.637 1.003-.637 1.003-.637.046.686.821.637c.775-.049.958-.637.958-.637s.228.588.775.637c.547.049.821-.637.821-.637s.046.686.73.588c.684-.098.821-.637.821-.637s.091.784.775.735c.684-.049.866-.686.866-.686s.182.686.821.686.775-.637.775-.637 0 .686.73.637c.73-.049 1.003-.686 1.003-.686s.228.637.958.637.775-.588.775-.588.228.588.867.539.775-.539.775-.539.365.539.821.539.73-.637.73-.637.274.735.912.588c.639-.147.456-.931.456-.931l-1.14-1.029-29.052-.098-3.238.686z"/><path fill="#FFAC33" d="M16.085 7.064s-6.522 4.997-8.711 7.349c-2.189 2.352-4.424 6.909-4.424 6.909s4.15-5.586 5.245-6.762c1.095-1.175 7.89-7.496 7.89-7.496zm5.519 1.568s2.417 2.94 3.512 4.115c1.095 1.176 4.834 8.33 4.834 8.33s-3.512-5.293-4.743-7.154c-.897-1.355-3.603-5.291-3.603-5.291zm-6.112.637s-2.691 3.038-4.014 5.291C10.174 16.785 9.427 20 9.427 20s2.957-5.034 3.74-6.468l2.325-4.263zm3.649.882s.224 3.052.684 5.634c.41 2.303 1.095 5.929 1.095 5.929s-.319-4.362-.684-6.86c-.34-2.323-1.095-4.703-1.095-4.703z"/><g fill="#C1694F"><path opacity=".9" d="M4.958 27.292s-.917 5.667 0 7.667c0 0 .083-4.792.083-5.792s-.083-1.875-.083-1.875zM31 26.417s.958 7 .042 9c0 0-.241-5.5-.241-6.5s.199-2.5.199-2.5z"/><path opacity=".7" d="M9.958 25.667s-.875 8 .042 10c0 0 .083-6.125.083-7.125s-.125-2.875-.125-2.875zm15.968-.292s.991 7.375.074 9.375c0 0-.157-4.542-.157-5.542s.083-3.833.083-3.833z"/></g></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f6e3.svg b/games/sound-soup/assets/img/1f6e3.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#88C9F9" d="M36 32s0 4-4 4H4s-4 0-4-4V4c0-4 4-4 4-4h28c4 0 4 4 4 4v28z"/><path fill="#77B255" d="M0 32c0 4 4 4 4 4h28c4 0 4-4 4-4V19H0v13z"/><path fill="#F5F8FA" d="M31.313 2.678c-.274 0-.539.042-.787.119-.296-.843-1.096-1.449-2.04-1.449-1.013 0-1.86.697-2.096 1.637-.25-.189-.559-.306-.898-.306-.827 0-1.497.67-1.497 1.497 0 .201.041.392.114.567-.144-.04-.292-.068-.446-.068C22.744 4.674 22 5.418 22 6.337 22 7.256 22.744 8 23.663 8h7.65c1.469 0 2.661-1.191 2.661-2.661.001-1.469-1.192-2.661-2.661-2.661z"/><path fill="#D99E82" d="M22.375 19h-8.75L2.263 35.616C3.167 35.995 4 36 4 36h28c.71 0 1.289-.13 1.77-.338L22.375 19z"/><path fill="#58595B" d="M4 36h28L21.375 19h-6.75z"/><path fill="#808285" d="M6.625 36h22.75l-8.75-17h-5.25z"/><path fill="#CCD6DD" d="M24 36h-1l-4-17h.469zm-7-17l-4 17h-1l4.484-17z"/><path fill="#808285" d="M12 33h12v1H12zm0-3h12v1H12zm-.062-2.688h12v1h-12zM13 25h10v1H13zm2-2h6v1h-6zm0-1.688h6V22h-6zm0-1.437h6v.688h-6z"/><path fill="#66757F" d="M8 21.5c0 .276-.224.5-.5.5s-.5-.224-.5-.5v-11c0-.276.224-.5.5-.5s.5.224.5.5v11zm21 .096c0 .223-.181.404-.404.404h-.191c-.224 0-.405-.181-.405-.404V10.404c0-.223.181-.404.404-.404h.191c.224 0 .405.181.405.404v11.192z"/><path fill="#66757F" d="M29 10.5c0 .276-.224.5-.5.5h-21c-.276 0-.5-.224-.5-.5s.224-.5.5-.5h21c.276 0 .5.224.5.5zm0 5.116c0 .212-.172.384-.384.384H7.384C7.172 16 7 15.828 7 15.616v-.232c0-.212.172-.384.384-.384h21.232c.212 0 .384.172.384.384v.232z"/><path fill="#5C913B" d="M27 15c0 .552-.447 1-1 1H10c-.552 0-1-.448-1-1v-4c0-.552.448-1 1-1h16c.553 0 1 .448 1 1v4z"/><path fill="#A6D488" d="M13 11h2v2h-2z"/><path fill="#77B255" d="M12 14h4v1h-4z"/><path fill="#A6D488" d="M21 11h2v2h-2z"/><path fill="#77B255" d="M20 14h4v1h-4z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f6e9.svg b/games/sound-soup/assets/img/1f6e9.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#66757F" d="M25.819 10.731c-.22.132-.505.061-.637-.158l-3.135-5.201c-.133-.219-.062-.504.157-.636l.062-.038c.22-.132.505-.062.637.158l3.136 5.2c.132.219.061.504-.158.637l-.062.038z"/><path fill="#66757F" d="M25.27 10.181c-.132.219-.062.504.158.636l5.2 3.135c.22.133.505.062.637-.157l.037-.062c.133-.219.062-.504-.158-.637l-5.2-3.135c-.219-.132-.504-.062-.637.158l-.037.062z"/><path fill="#BE1931" d="M13.049 32.144l-3.536-4.95-.707-.707-4.95-3.535c-.391-.391-1.023-.391-1.414 0L.32 25.073c-.391.391-.39 1.023 0 1.414l9.192 9.192c.391.391 1.023.391 1.415 0l2.121-2.121c.391-.391.391-1.024.001-1.414z"/><path fill="#CCD6DD" d="M23.204 17.747C16.209 24.74 3.535 33.304 3.116 32.884c-.42-.419 8.144-13.093 15.138-20.087 6.993-6.993 8.821-6.605 10.188-5.238 1.367 1.366 1.755 3.194-5.238 10.188z"/><path fill="#BE1931" d="M32.076 4.632c-.977.976-1.927 1.609-2.122 1.414-.195-.195.438-1.145 1.414-2.122.976-.976 1.927-1.609 2.122-1.414.196.196-.438 1.145-1.414 2.122z"/><path fill="#CCD6DD" d="M23.944 17.005c-.781.781-2.048.781-2.828 0l-2.122-2.121c-.781-.781-.781-2.047 0-2.829l7.778-7.778c.781-.781 3.222-1.955 4.003-1.174l2.121 2.121c.781.781-.393 3.221-1.174 4.002l-7.778 7.779z"/><path fill="#269" d="M28.526 3.203l4.272 4.271c.364-.883.529-1.818.099-2.249l-2.121-2.121c-.431-.431-1.367-.266-2.25.099z"/><path fill="#BE1931" d="M32.592 3.733c-.196.196-1.145-.438-2.121-1.414-.977-.977-1.61-1.926-1.414-2.122.195-.195 1.145.438 2.121 1.415.976.976 1.609 1.926 1.414 2.121z"/><path fill="#BE1931" d="M35.823 6.965c-.195.195-1.145-.438-2.121-1.414-.976-.977-1.61-1.926-1.414-2.122.195-.195 1.144.438 2.121 1.415.977.976 1.61 1.926 1.414 2.121z"/><path fill="#66757F" d="M24.517 10.108l1.375 1.375c.578.578 1.114 1.088 1.285 1.746l.061-.061c.944-.945.944-2.478 0-3.422l-.982-.982c-.944-.945-2.477-.945-3.422 0l-.061.06c.657.171 1.167.707 1.744 1.284z"/><path fill="#292F33" d="M21.831 6.39c-.34.34-.893.34-1.233 0l-.18-.18c-.341-.341-.341-.893-.001-1.233l1.596-1.595c.34-.34.893-.34 1.233 0l.18.18c.341.341.341.893.001 1.233L21.831 6.39zm9.159 9.226c-.322.322-.844.322-1.166 0l-.248-.248c-.322-.322-.322-.844 0-1.167l1.662-1.662c.322-.322.844-.322 1.166 0l.248.248c.322.322.322.844 0 1.166l-1.662 1.663z"/><path fill="#EA596E" d="M3.865 27.202l-.716-.716-1.423-1.423c-.386-.386-1.011-.386-1.397 0l-.017.017c-.386.387-.386 1.012 0 1.397L1.735 27.9l.715.716.699.698.699-.698.017-.016.698-.7-.698-.698zm7.063 7.063l-1.44-1.44-.681-.681-.707-.707-1.414 1.414.707.707.681.681 1.44 1.44c.391.391 1.023.391 1.415 0 .389-.391.389-1.024-.001-1.414z"/><path fill="#BE1931" d="M7.119 29.475c-2.047 2.047-3.84 3.573-4.003 3.409-.164-.163 1.362-1.956 3.41-4.003 2.047-2.048 3.84-3.574 4.004-3.41.163.164-1.363 1.956-3.411 4.004zm28.013-6.302L12.803.845c-.781-.781-2.047-.781-2.829 0l-2.536 2.95 8.227 10.641 5.9 5.899 10.617 8.204 2.95-2.537c.781-.78.781-2.048 0-2.829z"/><path d="M16.525 10.49L8.544 2.508 7.438 3.794l6.882 8.901zm16.943 16.943l-8.457-8.458-2.269 2.269 9.44 7.294z" fill="#EA596E"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f6f6.svg b/games/sound-soup/assets/img/1f6f6.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#DD2E44" d="M33.793 17S32.476 20 18 20C3.523 20 1.973 17 1.973 17S-1 22.117 4.802 25c4.238 2.105 10.916-.131 12.723-.814 1.991.683 9.274 2.824 13.557.814 5.862-2.751 2.711-8 2.711-8z"/><path fill="#55ACEE" d="M0 24h36v12H0z"/><path fill="#FFAC33" d="M27.005 25.389c.206 0 .412-.079.569-.236.315-.315.315-.824 0-1.139l-8.861-8.86c-.315-.315-.824-.315-1.139 0-.315.315-.315.824 0 1.139l8.861 8.86c.158.157.364.236.57.236z"/><path fill="#FFCC4D" d="M29.316 28.505c.412 0 .825-.157 1.139-.472.629-.629.629-1.649 0-2.278l-2.416-2.416c-.629-.629-1.65-.629-2.278 0-.629.629-.629 1.649 0 2.278l2.416 2.416c.314.315.727.472 1.139.472z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f6f7.svg b/games/sound-soup/assets/img/1f6f7.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#E1E8ED" d="M36 13.062V32s0 4-4 4H4c-4 0-4-4-4-4v-4.938s17-10 36-14z"/><path fill="#9D0522" d="M26.004 15.523c-1.341.693-17.35 9.168-19.132 10.044-2.822 1.388-5.22.05-6.416-1.769-.617-.938-.406-1.219 0-1.312s1.424-.277 1.688.25c.438.875 2.531 2.031 3.625 1.531s17.938-9.531 18.562-9.812.906-.281 1.375 0 1.315.542.298 1.068z"/><path fill="#E75A70" d="M11.143 29.264c-.081 0-.163-.02-.238-.061l-9.301-5.06c-.243-.132-.333-.436-.201-.679.132-.242.436-.332.678-.2l9.301 5.06c.243.132.333.436.201.679-.091.166-.263.261-.44.261z"/><path fill="#9D0522" d="M35.452 20.607c-1.341.693-17.35 9.168-19.132 10.044-2.822 1.387-5.22.05-6.416-1.769-.617-.938-.406-1.219 0-1.312s1.424-.277 1.688.25c.438.875 2.531 2.031 3.625 1.531s17.938-9.531 18.563-9.812.906-.281 1.375 0 1.314.542.297 1.068z"/><path fill="#9D0522" d="M19.167 28.271c-.058 0-.117-.01-.174-.031-.259-.097-.391-.384-.295-.643l2.479-6.666c.062-.166.206-.286.379-.318.18-.029.352.031.467.164l3.334 3.833c.182.208.159.524-.049.705-.209.183-.523.158-.705-.049l-2.782-3.198-2.185 5.877c-.075.201-.266.326-.469.326zm-9.289-4.909c-.058 0-.117-.01-.174-.031-.259-.097-.391-.384-.294-.643l2.479-6.667c.062-.166.206-.286.379-.317.173-.036.35.03.467.164l3.333 3.833c.181.208.159.524-.049.705-.208.182-.523.159-.706-.049l-2.781-3.198-2.186 5.876c-.075.203-.265.327-.468.327zm18.605 0c-.058 0-.117-.01-.174-.031-.26-.097-.391-.384-.295-.643l2.479-6.667c.062-.166.206-.286.379-.317.179-.034.352.031.467.164l3.333 3.833c.182.208.159.524-.049.705-.208.183-.523.158-.705-.049l-2.781-3.198-2.186 5.876c-.074.203-.264.327-.468.327z"/><path fill="#C1694F" d="M23.921 10.779c1.076-.558 1.113-.628 2.249-.024 1.136.604 6.569 3.484 7.21 3.825.814.434 1.112.591-.449 1.421-1.695.903-12.305 6.512-12.931 6.824-.765.381-1.311.327-2.206-.112-.718-.352-6.549-3.463-7.276-3.875-.82-.465-.482-.706.769-1.37 1.251-.666 12.634-6.689 12.634-6.689z"/><path fill="#C1694F" d="M10.085 17.075h1.416v1.329h-1.416zm22.838-3.308h1.125v1.422h-1.125z"/><path fill="#D99E82" d="M23.921 9.435c1.076-.558 1.113-.628 2.249-.024 1.136.604 6.569 3.484 7.21 3.825.814.434 1.112.591-.449 1.421-1.695.902-12.305 6.511-12.931 6.823-.765.381-1.311.327-2.206-.112-.718-.352-6.549-3.463-7.276-3.875-.82-.465-.482-.706.769-1.37 1.251-.665 12.634-6.688 12.634-6.688z"/><path fill="#BF6952" d="M12.532 17.375c-.179 0-.352-.096-.442-.265-.13-.244-.037-.547.207-.676L24.609 9.89c.242-.13.547-.038.676.207.13.244.037.547-.207.676l-12.312 6.544c-.075.039-.155.058-.234.058zm6.714 3.45c-.179 0-.352-.097-.441-.266-.13-.243-.037-.546.207-.676l12.312-6.544c.241-.131.546-.037.676.207s.037.547-.207.676L19.48 20.767c-.074.04-.154.058-.234.058zm-2.171-1.133c-.179 0-.352-.097-.442-.266-.13-.243-.037-.546.207-.676l12.313-6.544c.241-.131.546-.037.676.207s.037.547-.207.676L17.31 19.634c-.076.04-.156.058-.235.058zM14.84 18.56c-.179 0-.352-.096-.442-.265-.13-.244-.037-.547.207-.676l12.313-6.544c.242-.13.546-.038.676.207s.037.547-.207.676l-12.312 6.544c-.075.039-.156.058-.235.058z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f7e5.svg b/games/sound-soup/assets/img/1f7e5.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#DD2E44" d="M36 32c0 2.209-1.791 4-4 4H4c-2.209 0-4-1.791-4-4V4c0-2.209 1.791-4 4-4h28c2.209 0 4 1.791 4 4v28z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f7e9.svg b/games/sound-soup/assets/img/1f7e9.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#78B159" d="M36 32c0 2.209-1.791 4-4 4H4c-2.209 0-4-1.791-4-4V4c0-2.209 1.791-4 4-4h28c2.209 0 4 1.791 4 4v28z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f917.svg b/games/sound-soup/assets/img/1f917.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#FFCC4D" d="M34 16c0 8.834-7.166 16-16 16-8.836 0-16-7.166-16-16C2 7.164 9.164 0 18 0c8.834 0 16 7.164 16 16"/><path fill="#664500" d="M25.861 16.129c-.15-.161-.374-.171-.535-.029-.033.029-3.303 2.9-7.326 2.9-4.013 0-7.293-2.871-7.326-2.9-.162-.142-.385-.13-.535.029-.149.16-.182.424-.079.628C10.168 16.972 12.769 22 18 22s7.833-5.028 7.94-5.243c.103-.205.07-.468-.079-.628z"/><path d="M13.393 23.154l-1.539-2.219s-.543-.867-1.411-.325c-.867.541-.324 1.409-.324 1.409l1.758 3.38s.144.768-.918-.091c-.463-.374-.197-.163-.197-.163l-.44-.361-5.491-4.508s-.709-.748-1.359.042c-.648.791.223 1.341.223 1.341l5.193 4.266c-.12.12-.482.534-.6.663l-5.557-4.56s-.71-.749-1.358.041c-.65.791.222 1.341.222 1.341l5.555 4.562c-.103.14-.363.476-.459.614l-4.792-3.934s-.71-.748-1.358.042c-.651.791.222 1.342.222 1.342l5.094 4.184c-.064.178-.363.646-.41.82l-3.672-3.012s-.709-.752-1.357.041c-.65.791.222 1.341.222 1.341l5.93 4.868.395.326c2.62 2.151 6.489 1.771 8.64-.849 1.971-2.403 1.817-5.853-.24-8.069-.729-.782-1.36-1.653-1.972-2.532zm22.19 4.916c-.648-.793-1.357-.041-1.357-.041l-3.672 3.012c-.047-.174-.346-.643-.41-.82l5.094-4.184s.873-.551.223-1.342c-.648-.79-1.358-.042-1.358-.042l-4.792 3.934c-.096-.139-.357-.475-.459-.614l5.555-4.562s.873-.55.223-1.341c-.648-.79-1.357-.041-1.357-.041l-5.558 4.56c-.117-.129-.479-.543-.6-.663l5.193-4.266s.87-.55.223-1.341c-.65-.79-1.359-.042-1.359-.042l-5.491 4.508-.439.361s.99-.841-.197.163c-1.188 1.004-.918.091-.918.091l1.758-3.38s.543-.868-.324-1.409c-.869-.542-1.411.325-1.411.325l-1.538 2.219c-.613.879-1.244 1.75-1.974 2.533-2.058 2.217-2.212 5.666-.239 8.069 2.15 2.62 6.02 3 8.64.849l.396-.326 5.93-4.868c-.005-.001.867-.551.218-1.342z" fill="#F4900C"/><path fill="#664500" d="M27.677 10.983C27.626 10.861 26.392 8 23.856 8c-2.534 0-3.768 2.861-3.819 2.983-.08.188-.028.406.123.536.149.128.365.132.523.012.01-.009 1.081-.816 3.173-.816 2.079 0 3.149.797 3.174.816.075.059.165.089.255.089.095 0 .189-.034.267-.099.153-.129.205-.35.125-.538zm-11 0C16.625 10.861 15.392 8 12.856 8c-2.534 0-3.768 2.861-3.82 2.983-.079.188-.028.406.124.536.15.128.366.132.523.012.011-.009 1.081-.816 3.173-.816 2.08 0 3.149.797 3.173.816.076.059.165.089.255.089.095 0 .189-.034.267-.099.154-.129.205-.35.126-.538z"/><path fill="#B55005" d="M11.182 25.478s-2.593 3.314.484 7.46c.212.285.75.146.521-.188-.25-.364-2.75-3.896-.4-7.057 0 0-.381-.047-.605-.215zm13.583.098s2.594 3.314-.484 7.46c-.212.285-.75.146-.521-.188.25-.364 2.75-3.896.4-7.057-.001.001.381-.046.605-.215z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f934.svg b/games/sound-soup/assets/img/1f934.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#FFDC5D" d="M32 20c0-2.209-1.119-4-2.5-4-.012 0-.021.005-.033.005C27.955 9.704 23.394 5.125 18 5.125s-9.956 4.58-11.467 10.88C6.521 16.004 6.511 16 6.5 16 5.119 16 4 17.791 4 20c0 2.107 1.021 3.815 2.314 3.97C7.537 30.619 12.299 35 18 35c5.7 0 10.463-4.381 11.685-11.03C30.979 23.815 32 22.107 32 20z"/><path d="M13 21c-.552 0-1-.447-1-1v-2c0-.552.448-1 1-1s1 .448 1 1v2c0 .553-.448 1-1 1zm10 0c-.553 0-1-.447-1-1v-2c0-.552.447-1 1-1s1 .448 1 1v2c0 .553-.447 1-1 1z" fill="#662113"/><path fill="#C1694F" d="M18 31c-4.201 0-5.491-1.077-5.707-1.293-.391-.391-.391-1.023 0-1.414.378-.379.984-.39 1.376-.036.08.058 1.1.743 4.331.743 3.355 0 4.326-.739 4.336-.747.39-.389 1.001-.37 1.393.021.391.391.369 1.043-.021 1.434C23.491 29.923 22.201 31 18 31zm1-5h-2c-.552 0-1-.447-1-1s.448-1 1-1h2c.553 0 1 .447 1 1s-.447 1-1 1z"/><path fill="#FFAC33" d="M18 1c8 0 13 6 13 11s-1 7-2 5l-2-4s-6 0-8-2c0 0 3 6-3 0 0 0 1 4-5-1 0 0-3 2-4 7-.277 1.387-2 0-2-5S9 1 18 1z"/><path fill="#99AAB5" d="M30.68 5.784c-.192-.075-.411-.022-.55.13L28.672 7.52l-2.349-4.743c-.082-.165-.248-.271-.432-.277-.195-.013-.356.089-.449.249l-2.066 3.565L18.355.183C18.261.067 18.119 0 17.969 0c-.149 0-.292.067-.386.183l-5.041 6.138-2.12-3.576c-.091-.153-.24-.227-.436-.245-.179.002-.342.099-.43.254L6.909 7.449l-.994-1.478c-.123-.182-.351-.266-.561-.199-.21.064-.354.258-.354.478V9.5c0 .276.224.5.5.5h25c.276 0 .5-.224.5-.5V6.25c0-.207-.127-.392-.32-.466z"/><path fill="#E1E8ED" d="M18 3l-2 2v2l2 2 2-2V5zm-7 3l-1 1.415L9 6l1-1.414zm16 0l-1 1.415L25 6l1-1.414z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f941.svg b/games/sound-soup/assets/img/1f941.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#F18F26" d="M0 18h36v9H0z"/><ellipse fill="#F18F26" cx="18" cy="26" rx="18" ry="9"/><ellipse fill="#F18F26" cx="18" cy="27" rx="18" ry="9"/><path fill="#9D0522" d="M0 10v16h.117c.996 4.499 8.619 8 17.883 8s16.887-3.501 17.883-8H36V10H0z"/><ellipse fill="#F18F26" cx="18" cy="11" rx="18" ry="9"/><ellipse fill="#F18F26" cx="18" cy="12" rx="18" ry="9"/><path fill="#F18F26" d="M0 10h1v2H0zm35 0h1v2h-1z"/><ellipse fill="#FCAB40" cx="18" cy="10" rx="18" ry="9"/><ellipse fill="#F5F8FA" cx="18" cy="10" rx="17" ry="8"/><path fill="#FDD888" d="M18 3c9.03 0 16.395 3.316 16.946 7.5.022-.166.054-.331.054-.5 0-4.418-7.611-8-17-8S1 5.582 1 10c0 .169.032.334.054.5C1.605 6.316 8.97 3 18 3z"/><path d="M28.601 2.599c.44-.33.53-.96.2-1.4l-.6-.8c-.33-.44-.96-.53-1.4-.2L14.157 10.243c-.774-.167-1.785.083-2.673.749-1.326.994-1.863 2.516-1.2 3.4s2.275.794 3.6-.2c.835-.626 1.355-1.461 1.462-2.215l13.255-9.378zm5.868 2.919l-.509-.861c-.28-.474-.896-.632-1.37-.352l-13.913 8.751c-.719-.141-1.626.023-2.472.524-1.426.843-2.127 2.297-1.565 3.248.562.951 2.174 1.039 3.6.196 1.005-.594 1.638-1.49 1.735-2.301l14.142-7.835c.474-.281.632-.897.352-1.37z" fill="#AA695B"/><path fill="#DA2F47" d="M2 28c-.55 0-1-.45-1-1v-9c0-.55.45-1 1-1s1 .45 1 1v9c0 .55-.45 1-1 1zm9 4c-.55 0-1-.45-1-1v-9c0-.55.45-1 1-1s1 .45 1 1v9c0 .55-.45 1-1 1zm12 0c-.55 0-1-.45-1-1v-9c0-.55.45-1 1-1s1 .45 1 1v9c0 .55-.45 1-1 1zm11-4c-.55 0-1-.45-1-1v-9c0-.55.45-1 1-1s1 .45 1 1v9c0 .55-.45 1-1 1z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f944.svg b/games/sound-soup/assets/img/1f944.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#99AAB5" d="M24 10c0-4.971-2.91-10-6.5-10S11 5.029 11 10c0 3.744 1.651 6.385 4 7.461V33.5c0 1.381 1.119 2.5 2.5 2.5s2.5-1.119 2.5-2.5V17.461c2.349-1.076 4-3.717 4-7.461z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f945.svg b/games/sound-soup/assets/img/1f945.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#CCD6DD" d="M33.553 17.894c.143.072.296.106.446.106.367 0 .72-.202.896-.553.247-.494.047-1.095-.447-1.342L32 14.881v-1.764l1.553.776c.143.073.296.107.446.107.367 0 .72-.202.896-.553.247-.494.047-1.095-.447-1.342L32 10.881V7c0-.552-.447-1-1-1s-1 .448-1 1v2.881l-1.553-.776C28.133 8.948 27.187 9 27 9h-2V6c0-.552-.447-1-1-1s-1 .448-1 1v3h-2V6c0-.552-.447-1-1-1s-1 .448-1 1v3h-2V6c0-.552-.448-1-1-1s-1 .448-1 1v3h-2V6.028c0-.552-.448-1-1-1s-1 .448-1 1V9H9c-.187 0-1.132-.051-1.447.106L6 9.882V7c0-.552-.448-1-1-1s-1 .448-1 1v3.882l-2.447 1.224c-.494.247-.694.848-.447 1.342.175.35.528.552.895.552.15 0 .303-.034.446-.105L4 13.118v1.764l-2.447 1.224c-.494.247-.694.848-.447 1.342.175.35.528.552.895.552.15 0 .303-.034.446-.105L4 17.118v1.764l-2.447 1.224c-.494.247-.694.848-.447 1.342.175.35.528.552.895.552.15 0 .303-.034.446-.105L4 21.118v1.764l-2.447 1.224c-.494.247-.694.848-.447 1.342.175.35.528.552.895.552.15 0 .303-.034.446-.105L4 25.118v1.764l-2.447 1.224c-.494.247-.694.848-.447 1.342.175.35.528.552.895.552.15 0 .303-.034.446-.105L4 29.118V30c0 .553.448 1 1 1s1-.447 1-1v-1.882L8.236 27H11v1.865c0 .553.448 1 1 1s1-.447 1-1V27h2v2c0 .553.448 1 1 1s1-.447 1-1v-2h2v2.146c0 .553.447 1 1 1s1-.447 1-1V27h2v2.146c0 .553.447 1 1 1s1-.447 1-1V27h2.765L30 28.117V30c0 .553.447 1 1 1s1-.447 1-1v-.883l1.553.776c.144.071.296.105.446.105.367 0 .72-.202.896-.553.247-.494.047-1.095-.447-1.342L32 26.881v-1.764l1.553.776c.144.071.296.105.446.105.367 0 .72-.202.896-.553.247-.494.047-1.095-.447-1.342L32 22.881v-1.764l1.553.776c.144.071.296.105.446.105.367 0 .721-.202.896-.553.247-.494.047-1.095-.447-1.342L32 18.881v-1.763l1.553.776zM11 25H9c-.187 0-1.132-.051-1.447.106L6 25.882v-1.764L8.236 23H11v2zm0-4H9c-.187 0-1.132-.051-1.447.106L6 21.882v-1.764L8.236 19H11v2zm0-4H9c-.187 0-1.132-.052-1.447.106L6 17.882v-1.764L8.236 15H11v2zm0-4H9c-.187 0-1.132-.052-1.447.106L6 13.882v-1.764L8.236 11H11v2zm4 11.999h-2v-2h2v2zM15 21h-2v-2h2v2zm0-4.001h-2v-2h2v2zm0-4h-2v-2h2v2zm4 12h-2v-2h2v2zM19 21h-2v-2h2v2zm0-4.001h-2v-2h2v2zm0-4h-2v-2h2v2zm4 12h-2v-2h2v2zM23 21h-2v-2h2v2zm0-4.001h-2v-2h2v2zm0-4h-2v-2h2v2zm7 12.882l-1.553-.776C28.133 24.949 27.187 25 27 25h-2v-2h2.765L30 24.117v1.764zm0-4l-1.553-.776C28.133 20.948 27.187 21 27 21h-2v-2h2.764L30 20.117v1.764zm0-4l-1.553-.776C28.133 16.948 27.187 17 27 17h-2v-2h2.764L30 16.118v1.763zm0-4l-1.553-.776C28.133 12.948 27.187 13 27 13h-2v-2h2.764L30 12.118v1.763z"/><path fill="#8C989F" d="M1.999 34c-.191 0-.383-.055-.554-.168-.459-.307-.583-.928-.277-1.387 1.465-2.194 3.785-3.446 6.379-3.446l.114.001h20.691c2.604-.06 4.994 1.218 6.479 3.444.307.459.183 1.08-.277 1.387-.459.305-1.08.182-1.387-.277-1.117-1.675-2.784-2.612-4.802-2.554H7.647c-1.99-.014-3.697.879-4.815 2.555-.193.289-.51.445-.833.445z"/><path fill="#8C989F" d="M28 30c-.553 0-1-.447-1-1V6.324c0-.552.447-1 1-1s1 .448 1 1V29c0 .553-.447 1-1 1zm-20-.135c-.552 0-1-.447-1-1V6.028c0-.552.448-1 1-1s1 .448 1 1v22.837c0 .553-.448 1-1 1z"/><path fill="#BE2032" d="M34 34.25c-.69 0-1.25-.56-1.25-1.25V12c0-2.619-2.131-4.75-4.75-4.75H8c-2.619 0-4.75 2.131-4.75 4.75v21c0 .69-.56 1.25-1.25 1.25S.75 33.69.75 33V12C.75 8.002 4.002 4.75 8 4.75h20c3.998 0 7.25 3.252 7.25 7.25v21c0 .69-.56 1.25-1.25 1.25z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f95a.svg b/games/sound-soup/assets/img/1f95a.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#F7DECE" d="M23.142 3.403C21.492 1.899 19.712 1 18 1 12.375 1 6 10.611 6 20c0 .975.079 1.899.202 2.791C7.261 30.484 12.623 35 18 35c6 0 12-5.611 12-15 0-6.531-3.086-13.161-6.858-16.597z"/><path fill="#E0AA94" d="M23.142 3.403c2.908 3.519 5.108 9.018 5.108 14.453 0 9.009-5.672 14.393-11.344 14.393-4.541 0-9.075-3.459-10.705-9.459C7.261 30.484 12.623 35 18 35c6 0 12-5.611 12-15 0-6.531-3.086-13.161-6.858-16.597z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f95b.svg b/games/sound-soup/assets/img/1f95b.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#CCD6DD" d="M8.777 4.458L10.792 32h14.333l2.189-27.562z"/><path fill="#FFF" d="M25 31.281l-14.094.157-1.018-15.312h16.246z"/><path fill="#8899A6" d="M23.906 35.175H12.094c-1.712 0-2.914-1.153-3.135-3.011L6.984 4.448c-.046-.647.441-1.209 1.089-1.255.659-.045 1.209.442 1.255 1.088l1.969 27.66c.107.884.573.884.797.884h11.812c.516 0 .708 0 .8-.917l1.966-27.627c.046-.646.6-1.131 1.255-1.088.647.046 1.135.608 1.089 1.255l-1.969 27.659c-.252 2.541-1.847 3.068-3.141 3.068z"/><path fill="#8899A6" d="M18 33h-2.627c-5.532 0-6.167-2.684-6.46-6.474C8.585 22.295 7.16 4.432 7.16 4.432l1.993-.159s1.427 17.868 1.754 22.099c.265 3.439.556 4.628 4.466 4.628H18c.552 0 1 .447 1 1s-.448 1-1 1z"/><path fill="#8899A6" d="M20.627 33H18c-.552 0-1-.447-1-1s.448-1 1-1h2.627c3.91 0 4.2-1.189 4.466-4.628.328-4.232 1.754-22.099 1.754-22.099l1.994.159s-1.426 17.864-1.754 22.094c-.293 3.79-.927 6.474-6.46 6.474z"/><path fill="#CCD6DD" d="M29 4.083c0 1.703-2.73 3.083-11 3.083-8.269 0-11-1.38-11-3.083C7 2.38 9.73 1 18 1s11 1.38 11 3.083z"/><path fill="#FFF" d="M28.532 4.128c-.307-.459-.927-.583-1.387-.276-2.086 1.393-7.274 1.571-9.097 1.523-.01-.001-.019.005-.029.004-.009 0-.016-.004-.025-.004-1.819.049-7.008-.127-9.097-1.523-.46-.306-1.08-.183-1.387.276-.307.459-.183 1.08.276 1.387 2.57 1.717 7.917 1.866 9.688 1.866.289 0 .477-.004.547-.006.071.002.258.006.547.006 1.771 0 7.118-.148 9.688-1.866.46-.307.584-.928.276-1.387z"/><ellipse fill="#E1E8ED" cx="17.991" cy="16.06" rx="7.827" ry="1.464"/><path fill="#8899A6" d="M11.042 31.542h14.167v1.833H11.042z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f95c.svg b/games/sound-soup/assets/img/1f95c.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#D99E82" d="M17.256 31.519c-4.754 4.754-11.207 5.573-14.683 2.097C-.902 30.141.056 23.55 4.672 18.934c3.355-3.356 6.711-3.356 9.228-5.873 2.517-2.516 1.678-5.033 5.454-8.809C23.968-.361 29.84-1.201 33.616 2.574c3.774 3.776 2.832 9.754-1.678 14.263-3.775 3.775-6.292 2.936-8.809 5.452-2.518 2.518-2.518 5.873-5.873 9.23z"/><path fill="#C1694F" d="M8.211 21.397c-.324 0-.643-.157-.835-.448-.305-.46-.179-1.081.282-1.386.682-.451 1.438-.853 2.245-1.193 3.938-1.516 6.902-4.208 7.765-7.037.161-.529.721-.829 1.248-.665.529.161.826.72.666 1.248-1.039 3.406-4.46 6.589-8.931 8.309-.665.28-1.31.623-1.889 1.006-.17.113-.361.166-.551.166zm-5.044 8.937h-.022c-.552-.012-.99-.47-.979-1.021.047-2.164.385-4.916 2.601-7.002.403-.377 1.036-.358 1.414.043.379.402.359 1.035-.042 1.414-1.666 1.567-1.932 3.704-1.972 5.588-.012.544-.458.978-1 .978zm4.125-.709c-.082 0-.166-.01-.249-.031-.535-.137-.858-.682-.721-1.217.562-2.191 1.782-5.277 5.875-6.774.517-.188 1.093.077 1.282.596.19.519-.077 1.093-.596 1.283-3.171 1.159-4.137 3.49-4.625 5.392-.113.451-.52.751-.966.751zm17.667-11.041c-.412 0-.798-.257-.943-.667-.184-.521.09-1.092.61-1.276 4.279-1.51 6.849-4.449 7.856-8.982.12-.54.657-.874 1.193-.759.539.12.879.654.76 1.193-1.154 5.19-4.23 8.701-9.144 10.435-.109.037-.222.056-.332.056z"/><path fill="#C1694F" d="M24.459 14.875c-.397 0-.774-.24-.93-.632-.203-.514.048-1.095.562-1.298 1.722-.682 3.378-2.263 4.12-3.935.225-.502.813-.732 1.32-.508.505.225.731.815.508 1.32-.956 2.151-3.002 4.107-5.212 4.982-.121.049-.245.071-.368.071zm-8.709 7.209c-.395 0-.768-.234-.926-.622-.209-.512.037-1.095.548-1.304.955-.39 1.979-1.345 2.266-1.832.28-.476.895-.635 1.369-.354.477.28.635.893.354 1.369-.513.873-1.864 2.11-3.233 2.669-.124.051-.252.074-.378.074zm4-12.667c-.108 0-.22-.018-.328-.056-.522-.181-.798-.751-.617-1.272.4-1.152 1.234-2.255 2.124-2.812.469-.293 1.086-.15 1.378.318.293.468.15 1.085-.317 1.378-.421.263-1.017.973-1.295 1.772-.144.413-.531.672-.945.672zM6.457 33.915c-.49 0-.917-.358-.988-.857-.064-.451 0-1.253.098-1.691.12-.539.659-.87 1.193-.759.539.12.879.655.759 1.194-.064.286-.09.824-.069.975.076.546-.305 1.053-.852 1.129-.048.006-.095.009-.141.009z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f963.svg b/games/sound-soup/assets/img/1f963.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#A0041E" d="M16 31c8.837 0 16-7.163 16-16H0c0 8.837 7.164 16 16 16z"/><ellipse fill="#EA596E" cx="16" cy="15" rx="16.051" ry="8.917"/><ellipse fill="#662113" cx="16" cy="15" rx="14.641" ry="7.719"/><path fill="#99AAB5" d="M34 8c-1 0-4 1-8 4-4.525 3.394-7.596 2.187-7.596 2.187l.001.003C16.981 13.453 15.087 13 13 13c-4.418 0-8 2.015-8 4.5S8.582 22 13 22c2.773 0 5.216-.794 6.651-2 0 0 4.995-4.431 7.349-6 3-2 5-3 7-3s2-3 0-3z"/><ellipse fill="#CCD6DD" cx="13" cy="19" rx="5" ry="2.5"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f964.svg b/games/sound-soup/assets/img/1f964.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#8899A6" d="M26.864 8H18v28c6 0 10-1 11.393-7.015.774-3.342 1.663-12.335 1.663-17.841C31.056 9.279 29.623 8 26.864 8zM8.892 8H18v28c-6 0-10-1-11.515-7.015-.838-3.327-1.724-12.335-1.724-17.841C4.761 9.279 6.132 8 8.892 8z"/><path fill="#CCD6DD" d="M31.052 10.5c0 2.071-3.13 3.75-13.057 3.75-9.926 0-13.057-1.679-13.057-3.75 0-2.072 3.131-3.75 13.057-3.75 9.927 0 13.057 1.678 13.057 3.75z"/><path fill="#CCD6DD" d="M18.025 12.997L6.516 12c0 7 1.104 14.382 1.681 16.572C8.859 31.081 11 30.981 18 30.998V31v-.002c7-.018 9.166.083 9.827-2.426C28.405 26.382 29.52 19 29.52 12l-11.495.997z"/><path fill="#55ACEE" d="M18 21v13c7 0 9-1 9.857-5.428.241-1.243.807-4.075 1.174-7.572H18zM7.019 21c.366 3.497.915 6.327 1.149 7.572C9 33 11 34 18 34V21H7.019z"/><path fill="#3B88C3" d="M18 30.16c-4.556 0-8.249.895-8.249 2 0 .009.005.018.006.028C11.154 33.598 13.578 34 18 34c4.413 0 6.836-.4 8.241-1.803.001-.012.008-.024.008-.037 0-1.105-3.693-2-8.249-2z"/><ellipse fill="#88C9F9" cx="17.995" cy="21" rx="11.016" ry="2"/><path fill="#E1E8ED" d="M33.958.936l-6.824 1.697c-.751.187-1.565.577-2.44 1.239-1.643 1.243-1.978 2.93-2.246 3.861L18.34 22h3.122L25.33 8.564c.264-.916.526-1.702 1.129-2.249.534-.484 1.417-.769 2.244-.983l5.977-1.483c.801-.199 1.293-1.016 1.095-1.817-.199-.802-1.016-1.295-1.817-1.096z"/><path fill="#88C9F9" d="M18.34 22l-2.671 9.277c-.228.793.234 1.628 1.026 1.857.793.228 1.628-.234 1.856-1.026L21.462 22H18.34z"/><path fill="#744EAA" d="M18.641 22.677l-.588.322-.273.947 2.066 3.669.776-2.696-1.261-2.241z"/><path fill="#DD2E44" d="M27.906 2.441l-1.128 3.647c.531-.347 1.242-.58 1.927-.757l.434-.108 1.035-3.346-2.268.564z"/><path fill="#744EAA" d="M17.045 26.499l-.777 2.697 1.962 3.485c.144-.162.258-.353.322-.574l.558-1.939-2.065-3.669z"/><path fill="#DD2E44" d="M20.048 16.068l-.777 2.698 1.381 2.452.472.105.626-.323.364-1.262zM34.495.907c-.175-.021-.356-.016-.537.029l-1.696.422-1.036 3.347 2.267-.563L34.495.907zM20.791 13.488l2.066 3.668.776-2.696-2.065-3.669zm3.862-9.579c-.798.617-1.285 1.338-1.601 2.022l1.32 5.968.96-3.335c.066-.23.133-.452.204-.664l-.883-3.991z"/><path fill="#55ACEE" d="M18.663 20.881L18.053 23c1.094-.001 2.15-.031 3.147-.086l.585-2.032c-.938.483-2.894.181-3.122-.001z"/><path fill="#FFF" d="M29.615 11.309c-.242-.649-.954-.975-1.584-.724-3.606 1.416-7.044 1.909-10.036 1.937v2.492c3.333-.034 7.089-.577 10.913-2.08.633-.248.95-.976.707-1.625zm-23.24 0c.242-.649.954-.975 1.584-.724 3.606 1.416 7.044 1.909 10.036 1.937v2.492c-3.333-.034-7.089-.577-10.913-2.08-.632-.248-.95-.976-.707-1.625z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f965.svg b/games/sound-soup/assets/img/1f965.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#8B5E3C" d="M5.506 30.167c-7.03-7.029-6.397-19.059 1.414-26.87l25.456 25.456c-7.811 7.811-19.841 8.443-26.87 1.414z"/><path fill="#603913" d="M6.92 3.297c-3.905 3.905-1.373 12.77 5.656 19.8 7.031 7.029 15.895 9.561 19.799 5.656 3.905-3.904 1.374-12.769-5.657-19.799C19.69 1.924 10.825-.607 6.92 3.297z"/><path fill="#F5F8FA" d="M31.591 24.217c.229.229.56.336.887.276.387-.071.696-.362.79-.745.067-.269 1.575-6.67-3.013-11.258-1.12-1.12-2.783-3.774-3.256-4.544-.4-.415-.733-.27-1.498-1.035-.35-.35-1.405-2.248-4.439.629-.35.332-.39.878-.116 1.275l10.528 15.263c.036.05.075.097.117.139z"/><path fill="#603913" d="M24.271 7.562c.707.707 1.458 1.417 1.458 1.417s2.404 2.804 3.819 4.218c4.242 4.243 2.75 10.313 2.75 10.313L20.646 7.833s2.792-1.042 3.625-.271zm1.271-1.354c-1.833-1.438-4.777-.315-5.5.396-.683.683-.467 1.984.081 2.778l10.528 15.263c.07.101.147.193.232.278.46.46 1.121.673 1.775.553.774-.142 1.393-.725 1.581-1.489.072-.293 1.705-7.223-3.277-12.205-.892-.892-1.938-1.738-2.909-2.913-.329-.423-1.186-1.441-2.074-2.328l-.437-.333z"/><path fill="#F5F8FA" d="M8.334 4.711c-3.124 3.124-.592 10.722 5.657 16.97 6.247 6.248 13.846 8.781 16.97 5.657 3.124-3.124.591-10.723-5.657-16.971-6.248-6.247-13.846-8.78-16.97-5.656z"/><path fill="#99AAB5" d="M10.411 6.877c-2.318 2.318-.083 8.314 4.994 13.391 5.078 5.078 11.072 7.312 13.391 4.994 2.318-2.318.083-8.313-4.994-13.391-5.076-5.078-11.072-7.313-13.391-4.994z"/><path fill="#99AAB5" d="M20.199 9.939l1.176-.335c.562-.312 1.354-.333 1.896.167l1.211 1.273c.573.69 1.919 2.137 2.995 3.213 3.807 3.807 2.319 8.722 2.253 8.93-.057.182-.213.315-.401.344-.159.024-.318-.029-.429-.141-.021-.021-.04-.043-.058-.068l-8.797-12.669c-.079-.114-.108-.254-.078-.39.029-.135.113-.252.232-.324z"/><path fill="#F5F8FA" d="M23.76 26.056s-3.209-2.085-3.771-3.771c-.628-1.885-4.662-2.252-5.29-4.138-.627-1.885-1.676-4.765-2.253-5.918-.576-1.153-1.258-.001-1.886-1.886-.628-1.887-1.885-3.144-1.885-3.144s0 8.801 1.885 10.686c1.886 1.886 9.429 9.429 10.058 8.799.628-.628 3.142-.628 3.142-.628z"/><path fill="#603913" d="M22.838 27.73s-3.73-2.537-4.316-4.294c-.655-1.964-5.29-2.619-5.944-4.583-.655-1.964-1.596-5.486-1.913-5.892-.916-1.18-1.309-.001-1.964-1.964-.655-1.965-1.964-3.274-1.964-3.274s0 9.166 1.964 11.13c1.964 1.965 9.82 9.821 10.476 9.166.655-.654 3.661-.289 3.661-.289z"/><path fill="#8B5E3C" d="M21.769 29.46s-3.61-2.345-4.242-4.242c-.707-2.121-6.365-3.536-7.072-5.657-.707-2.122-.707-4.95-1.414-5.657-.707-.708-1.414 0-2.121-2.122-.707-2.121-2.121-3.535-2.121-3.535s0 9.9 2.121 12.021 10.606 10.606 11.313 9.899c.708-.707 3.536-.707 3.536-.707z"/><path fill="#A97C50" d="M10.811 32.853c.091-.285.836-.061 1.113.001 6.934 1.526 11.66-1.385 11.712-1.409.272-.136.579-.034.685.224.106.258-.026.577-.296.712-.225.112-4.808 2.55-12.125.939-.091-.021-.721-.131-.781-.191-.123-.123-.37-.084-.308-.276zm-8.364-9.968c.115.114.108.212.29.165.288-.073.299-.517.255-.798-.492-3.192.36-6.504 1.841-10.352.107-.281-.027-.575-.294-.652-.272-.08-.579.084-.687.364-1.555 4.034-2.057 7.636-1.536 11.013.017.104.064.192.131.26zm10.357 9.178c.07.071.167.118.279.132 3.769.426 6.893-1.137 7.024-1.204.269-.137.4-.456.291-.714-.108-.259-.417-.356-.688-.218-.029.015-2.743 1.843-6.181 1.453-.285-.032-.826-.016-.889.275-.036.177.053.166.164.276z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f96b.svg b/games/sound-soup/assets/img/1f96b.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#99AAB5" d="M30 32.5c0 1.933-5.373 3.5-12 3.5S6 34.433 6 32.5c0-1.932 5.372-3.5 12-3.5 6.627 0 12 1.567 12 3.5z"/><path fill="#99AAB5" d="M6 31.5h24v1H6z"/><path fill="#E6E7E8" d="M30 31.5c0 1.933-5.373 3.5-12 3.5S6 33.433 6 31.5 11.372 28 18 28c6.627 0 12 1.567 12 3.5z"/><path fill="#5C913B" d="M29 31c0 1.656-4.926 3-11 3-6.075 0-11-1.343-11-3s4.924-3 11-3c6.075 0 11 1.343 11 3z"/><path fill="#5C913B" d="M7 8h22v23H7z"/><path fill="#A6D388" d="M29 23c0 1.932-4.926 3.5-11 3.5-6.075 0-11-1.567-11-3.5s4.924-3.5 11-3.5c6.074 0 11 1.567 11 3.5z"/><path fill="#A6D388" d="M7 10h22v13H7z"/><path fill="#5C913B" d="M29 14c0 1.932-4.926 3.5-11 3.5-6.075 0-10.999-1.567-10.999-3.5S11.925 10.5 18 10.5c6.074 0 11 1.567 11 3.5z"/><path fill="#5C913B" d="M7 4h22v10H7z"/><path fill="#99AAB5" d="M30 4.5C30 6.433 24.627 8 18 8S6 6.433 6 4.5 11.373 1 18 1s12 1.567 12 3.5z"/><path fill="#99AAB5" d="M6 3.5h24v1H6z"/><path fill="#E6E7E8" d="M30 3.5C30 5.433 24.627 7 18 7S6 5.433 6 3.5 11.373 0 18 0s12 1.567 12 3.5z"/><path fill="#CCD6DD" d="M28.25 3.5C28.25 4.881 23.662 6 18 6S7.75 4.881 7.75 3.5 12.338 1 18 1s10.25 1.119 10.25 2.5z"/><path fill="#E6E7E8" d="M27.25 3.5c0 1.105-4.14 2-9.25 2s-9.25-.895-9.25-2 4.141-2 9.25-2c5.11 0 9.25.895 9.25 2z"/><path fill="#CCD6DD" d="M24 3.5c0 .69-2.686 1.25-6 1.25s-6-.56-6-1.25 2.686-1.25 6-1.25 6 .56 6 1.25z"/><ellipse fill="#E6E7E8" cx="18" cy="3.5" rx="4.75" ry=".75"/><path fill="#FFCC4D" d="M25 21c0 4.142-3.134 7.5-7 7.5s-7-3.358-7-7.5 3.134-7.5 7-7.5 7 3.358 7 7.5z"/><path fill="#FFAC33" d="M23 21c0 3.156-2.239 5.715-5 5.715s-5-2.56-5-5.715 2.238-5.714 5-5.714 5 2.558 5 5.714z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f975.svg b/games/sound-soup/assets/img/1f975.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><circle fill="#EA596E" cx="18" cy="18" r="18"/><path fill="#292F33" d="M26.125 29.261c-.028 0-.057-.003-.084-.01-8.424-1.942-15.996-.022-16.072-.002-.111.028-.231.005-.324-.066-.091-.072-.145-.181-.145-.297 0-3.178 3.713-6.872 8.5-6.872s8.5 3.694 8.5 6.872c0 .114-.052.223-.142.294-.067.052-.149.081-.233.081zM29 12c-5.554 0-7.802-4.367-7.895-4.553-.247-.494-.047-1.095.447-1.342.493-.247 1.092-.048 1.34.443C22.967 6.694 24.713 10 29 10c.553 0 1 .448 1 1s-.447 1-1 1zM7 12c-.552 0-1-.448-1-1s.448-1 1-1c5.083 0 5.996-3.12 6.033-3.253.145-.528.692-.848 1.219-.709.53.139.851.673.718 1.205C14.921 7.437 13.704 12 7 12z"/><ellipse fill="#292F33" cx="12" cy="16.5" rx="2.5" ry="3.5"/><ellipse fill="#292F33" cx="24" cy="16.5" rx="2.5" ry="3.5"/><path fill="#F4ABBA" d="M18.629 23.079v.827l-1.091.001v-.837c-2.277.127-4.191 1.162-5.455 2.516l.008.461c.086 2.932 2.08 5.802 5.992 5.802 3.899 0 5.892-2.851 5.991-5.773l.009-.284c-1.229-1.415-3.145-2.537-5.454-2.713z"/><path fill="#EA596E" d="M18 23.049c-.157 0-.308.014-.462.022v4.447c0 .301.244.545.545.545s.545-.244.545-.545V23.08c-.207-.017-.414-.031-.628-.031z"/><path fill="#5DADEC" d="M35.906 12.062c0 2.814-2.28 5.094-5.094 5.094-2.814 0-5.094-2.28-5.094-5.094s4.075-10.188 5.094-10.188 5.094 7.375 5.094 10.188zM8.573 8.438c0 2.313-1.875 4.188-4.188 4.188-2.313 0-4.188-1.875-4.188-4.188S3.547.063 4.385.063s4.188 6.062 4.188 8.375z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f97e.svg b/games/sound-soup/assets/img/1f97e.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#C1694F" d="M34.469 28.156c-.59-.885-2.458-2.365-4.5-2.656-2.112-.302-3.877.18-6.571-1.394-.086-.066-.171-.131-.273-.19-2.153-1.247-5.917-5.417-6-9.542-.047-2.336-3-2.625-5.375-2.333-2.375.292-10.083 2.083-9.792 3.25.248.991.224 5.316-.018 8.205-.366 2.27-.636 5.435-.636 7.44C1.303 31.599 2.333 32 2.333 32l14.198.5 17.688-.477s.933-.084.906-.711c-.042-.958-.156-2.406-.656-3.156z"/><path fill="#662113" d="M23.208 32.458c.75-1.333 2.375-2.417 4.75-2.583s5.792-2.531 5.792-2.531.969 0 1.531 2.031c.463 1.67-.406 2.094-.406 2.094s-3.542 1.99-5.875 1.615-5.792-.626-5.792-.626zM1.906 24.156s-1.125 3.656-.86 5.661c.181 1.369.573 2.351 1.376 2.58.803.229 9.5.066 9.328.009-.172-.057-1.097-4.124-4.594-5.156-3.497-1.032-5.25-3.094-5.25-3.094z"/><path fill="#292F33" d="M1.906 35.562c-.55 0-1.031-.438-1.156-1.938l-.031-.562c.156-2 .638.625 1.188.625.55 0 1 .45 1 1-.001.55-.451.875-1.001.875zm3.032.25c-.55 0-1-.45-1-1s.45-1 1-1 1 .45 1 1-.45 1-1 1zM8 36c-.55 0-1-.45-1-1s.45-1 1-1 1 .45 1 1-.45 1-1 1zm3.062-.156c-.55 0-1-.45-1-1s.45-1 1-1 1 .45 1 1-.449 1-1 1zM20 36c-.55 0-1-.45-1-1s.45-1 1-1 1 .45 1 1-.45 1-1 1zm3 0c-.55 0-1-.45-1-1s.45-1 1-1 1 .45 1 1-.45 1-1 1zm2.938-.125c-.55 0-1-.45-1-1s.45-1 1-1 1 .45 1 1-.451 1-1 1zm3.031-.187c-.55 0-1-.45-1-1s.45-1 1-1 1 .45 1 1c0 .549-.45 1-1 1zm3-.188c-.55 0-1-.45-1-1s.45-1 1-1 1 .45 1 1-.45 1-1 1zm2.962-.481h-.281c-.55 0-1-.606-1-1.156 0-.55.45-1 1-1s1.219-.675 1.219-.125l-.063 1.062c0 .55-.325 1.219-.875 1.219z"/><path fill="#662113" d="M2.167 16.042c-.713.366-.75-1.25-.083-2s6.25-1.75 7.625-2 4.042-.75 4.875-.083c.833.667 1 2.833.958 3.625-.042.791-3.084-.417-5.542-.667-2.323-.237-6.208.291-7.833 1.125z"/><circle fill="#292F33" cx="14.604" cy="18.583" r="1.5"/><circle fill="#292F33" cx="15.948" cy="21.781" r="1.5"/><circle fill="#292F33" cx="18.292" cy="24.458" r="1.5"/><circle fill="#292F33" cx="21.562" cy="25.938" r="1.5"/><circle fill="#292F33" cx="25" cy="26.823" r="1.5"/><path fill="#BE1931" d="M14.5 19.425c-.362 0-.69-.247-.777-.614-.103-.43.162-.861.592-.964l3.833-.917c.428-.105.861.162.964.592.103.43-.162.861-.592.964l-3.833.917c-.063.015-.126.022-.187.022zm1.5 3.187c-.288 0-.566-.155-.709-.429-.205-.392-.054-.875.337-1.08l3.542-1.854c.392-.203.874-.054 1.08.338.205.392.054.875-.338 1.08l-3.542 1.854c-.118.062-.245.091-.37.091zm2.25 2.688c-.225 0-.449-.095-.607-.278-.288-.335-.25-.84.085-1.128l2.958-2.542c.336-.288.84-.249 1.128.085.288.335.25.84-.085 1.128l-2.958 2.542c-.15.129-.336.193-.521.193zm3.332 1.5c-.125 0-.252-.029-.37-.091-.392-.205-.543-.688-.338-1.08l1.417-2.708c.205-.391.69-.541 1.08-.338.392.205.543.688.338 1.08l-1.417 2.708c-.143.273-.422.429-.71.429zm3.45.906c-.037 0-.074-.002-.112-.008-.438-.062-.742-.466-.681-.903l.386-2.739c.061-.438.462-.75.903-.681.438.062.742.466.681.903l-.386 2.739c-.055.401-.398.689-.791.689z"/><path fill="#292F33" d="M1.334 31.216c.787.175 2.201.662 4.591.7 2.755.044 5.187-.525 8.308-.525 2.991 0 6.9.848 10.319.7 3.487-.152 7.35-.652 8.192-.799 1.667-.292 2.438-.479 2.438-.479s.365-.021.531.688c.167.708.304 2.083-.841 2.427-2.186.656-7.554 1.113-12.724 1.049-2.626-.032-5.549-.81-6.966-.976-1.417-.167-2.667-.287-3.062 0-.396.287.089.792-.946.933-2.58.35-8.614.044-9.928-.516-.672-.286-1.051-3.455.088-3.202zm12.102-10.613c-.219 0-.421-.146-.482-.368l-.61-2.224c-.073-.266.084-.542.35-.615.264-.073.541.083.615.35l.61 2.225c.073.266-.084.541-.35.614-.045.012-.09.018-.133.018zm1.505 3.28c-.201 0-.39-.122-.467-.32l-.827-2.153c-.099-.258.03-.548.288-.646.257-.1.547.03.646.287l.827 2.153c.099.258-.03.548-.288.646-.059.022-.12.033-.179.033zm2.992 2.861c-.127 0-.254-.048-.352-.145l-1.639-1.622c-.196-.194-.198-.511-.004-.707s.511-.198.707-.004l1.639 1.622c.196.194.198.511.004.707-.098.099-.226.149-.355.149zm3.998 1.563c-.077 0-.155-.018-.228-.055l-2.053-1.051c-.246-.126-.344-.428-.218-.673.127-.246.431-.343.673-.218l2.053 1.051c.246.126.344.428.218.673-.089.173-.264.273-.445.273zm3.993.755c-.032 0-.065-.003-.098-.01l-2.263-.448c-.271-.054-.447-.316-.394-.587.054-.271.32-.45.587-.394l2.263.448c.271.054.447.316.394.587-.047.239-.256.404-.489.404z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f980.svg b/games/sound-soup/assets/img/1f980.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#A0041E" d="M6.96 20.637c.068.639-.543 1.228-1.368 1.315-.824.089-1.547-.357-1.615-.995-.068-.639.544-1.227 1.368-1.314.824-.089 1.547.356 1.615.994zm2.087 2.717c.125.818-1.756 2.544-2.576 2.669-.819.125-1.584-.438-1.708-1.257-.125-.818.58-1.14 1.398-1.265.819-.124 2.761-.965 2.886-.147zm1.783 2.104c.173.81-1.628 3.927-2.438 4.1-.811.173-1.645.146-1.817-.665-.173-.81.306-1.688 1.116-1.861.81-.174 2.966-2.384 3.139-1.574zm3.853.858c.165.811-1.338 4.354-2.15 4.519-.812.165-1.439.451-1.604-.36-.165-.812.261-1.975 1.006-2.58.644-.523 2.584-2.39 2.748-1.579z"/><path fill="#A0041E" d="M3.925 18.869c.607.715 1.18 1.23.464 1.835-.716.606-1.747.162-2.354-.554-.605-.715-2.239-3.42-1.524-4.025.717-.606 2.809 2.029 3.414 2.744zm.33 4.88c.892.295 1.857.801 1.563 1.691-.294.891-1.328.991-2.219.698-.889-.295-3.772-1.691-3.478-2.581.295-.89 3.244-.102 4.134.192zm1.214 4.532c.905-.253 1.907-.283 2.159.619.252.903-.282 1.535-1.186 1.787-.902.251-4.342.727-4.594-.176-.251-.905 2.718-1.98 3.621-2.23zm4.348 3.188c.084-.937.644-1.669 1.577-1.585.934.085 1.258 1.025 1.173 1.96-.085.934.147 3.562-1.715 4.016-.912.221-1.121-3.46-1.035-4.391zM29.04 20.637c-.068.639.543 1.228 1.367 1.315.824.089 1.547-.357 1.615-.995.068-.639-.544-1.227-1.367-1.314-.824-.089-1.547.356-1.615.994zm-2.087 2.717c-.125.818 1.757 2.544 2.575 2.669.819.125 1.584-.438 1.709-1.257s-.58-1.14-1.398-1.265c-.819-.124-2.761-.965-2.886-.147zm-1.783 2.104c-.173.81 1.628 3.927 2.438 4.1.81.173 1.644.146 1.816-.665.174-.81-.305-1.688-1.115-1.861-.81-.174-2.966-2.384-3.139-1.574zm-3.853.858c-.166.811 1.338 4.354 2.149 4.519.812.165 1.438.451 1.604-.36.164-.812-.262-1.975-1.007-2.58-.642-.523-2.582-2.39-2.746-1.579z"/><path fill="#A0041E" d="M32.075 18.869c-.607.715-1.18 1.23-.465 1.835.716.606 1.747.162 2.354-.554.605-.715 2.239-3.42 1.523-4.025-.715-.606-2.807 2.029-3.412 2.744zm-.33 4.88c-.892.295-1.857.801-1.562 1.691.293.891 1.328.991 2.219.698.889-.295 3.771-1.691 3.477-2.581-.294-.89-3.244-.102-4.134.192zm-1.215 4.532c-.904-.253-1.906-.283-2.158.619-.252.903.282 1.535 1.185 1.787.902.251 4.343.727 4.594-.177.252-.904-2.717-1.979-3.621-2.229zm-4.347 3.188c-.084-.937-.645-1.669-1.577-1.585-.935.085-1.258 1.025-1.173 1.96.084.934-.147 3.562 1.715 4.016.912.221 1.121-3.46 1.035-4.391zM3.148 13.878c-.383-.856.001-1.86.857-2.242.856-.383 1.86.002 2.243.858.381.855 2.651 5.612 1.795 5.994-.855.382-4.513-3.755-4.895-4.61z"/><path fill="#A0041E" d="M3.994 12.034c1.221 2.956 8.341-3.341 8.803-6.281.462-2.939-.308-4.201-.694-4.5-.386-.299.144 1.435-1.187 3.306-1.053 1.482-7.766 5.434-6.922 7.475zm28.858 1.844c.384-.856-.001-1.86-.857-2.242-.857-.383-1.861.002-2.244.858-.381.855-2.65 5.612-1.794 5.994.855.382 4.513-3.755 4.895-4.61z"/><path fill="#A0041E" d="M32.007 12.034c-1.222 2.956-8.341-3.341-8.804-6.281-.461-2.939.309-4.201.694-4.5.386-.299-.144 1.435 1.187 3.306 1.054 1.482 7.766 5.434 6.923 7.475z"/><path fill="#BE1931" d="M6 22c0-2 2-10 12-10s12 8 12 10c-5 3-5.373 7-12 7s-6-4-12-7zm-1.677-9.777c-3.153.543-.358-8.141 1.883-10.099C8.446.166 10.863.207 11.321.374s-1.174 2.595-1.75 4.178c-.293.803-3.072 7.296-5.248 7.671zm27.354 0c3.152.543.358-8.141-1.882-10.099C27.555.166 25.139.207 24.68.374c-.459.167 1.174 2.595 1.75 4.178.293.803 3.071 7.296 5.247 7.671z"/><path fill="#A0041E" d="M17.032 12.136c.335 1.339-.045 1.588-.849 1.789-.804.201-1.727.278-2.061-1.061-.335-1.339.045-2.588.849-2.789.803-.201 1.726.721 2.061 2.061zm4.846.728c-.335 1.34-1.258 1.262-2.061 1.061-.804-.201-1.184-.45-.849-1.789.335-1.34 1.258-2.262 2.062-2.061.803.2 1.183 1.449.848 2.789z"/><circle fill="#292F33" cx="14.5" cy="9.5" r="1.5"/><circle fill="#292F33" cx="21.5" cy="9.5" r="1.5"/><path fill="#DD2E44" d="M9.053 21.529c-.14.236-3.053.732-2.303-.731s2.443.497 2.303.731z"/><path fill="#DD2E44" d="M9.891 20.124c-.218.225-3.188.391-1.922-1.404 1.265-1.793 2.234 1.082 1.922 1.404z"/><path fill="#DD2E44" d="M11.657 18.66c-.378.231-3.471-.501-1.407-1.932 1.872-1.296 1.906 1.626 1.407 1.932z"/><path fill="#DD2E44" d="M14.102 17.427c-1.008.299-3.378-1.302-.881-2.141 2.498-.839 1.889 1.842.881 2.141zm12.754 4.102c.141.235 3.053.731 2.303-.731-.75-1.463-2.443.497-2.303.731z"/><path fill="#DD2E44" d="M26.019 20.124c.218.225 3.188.391 1.922-1.404-1.266-1.793-2.235 1.082-1.922 1.404z"/><path fill="#DD2E44" d="M24.253 18.66c.378.231 3.471-.501 1.406-1.932-1.872-1.296-1.906 1.626-1.406 1.932z"/><path fill="#DD2E44" d="M21.808 17.427c1.008.299 3.378-1.302.881-2.141-2.499-.839-1.89 1.842-.881 2.141z"/><path fill="#A0041E" d="M26.849 16.25c0 .414-2.189-2.25-8.849-2.25-6.661 0-8.848 2.664-8.848 2.25 0-.414 2.754-3.75 8.848-3.75 6.094 0 8.849 3.336 8.849 3.75z"/><path fill="#DD2E44" d="M10.793 24.433c0-.414 1.782 2.25 7.207 2.25s7.208-2.664 7.208-2.25c0 .414-2.244 3.75-7.208 3.75s-7.207-3.336-7.207-3.75z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f981.svg b/games/sound-soup/assets/img/1f981.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#662113" d="M32.325 10.958s2.315.024 3.511 1.177c-.336-4.971-2.104-8.249-5.944-10.13-3.141-1.119-6.066 1.453-6.066 1.453s.862-1.99 2.19-2.746C23.789.236 21.146 0 18 0c-3.136 0-5.785.227-8.006.701 1.341.745 2.215 2.758 2.215 2.758S9.194.803 6 2.053C2.221 3.949.481 7.223.158 12.174c1.183-1.19 3.55-1.215 3.55-1.215S-.105 13.267.282 16.614c.387 2.947 1.394 5.967 2.879 8.722C3.039 22.15 5.917 20 5.917 20s-2.492 5.96-.581 8.738c1.935 2.542 4.313 4.641 6.976 5.916-.955-1.645-.136-3.044-.103-2.945.042.125.459 3.112 2.137 3.743 1.178.356 2.4.548 3.654.548 1.292 0 2.55-.207 3.761-.583 1.614-.691 2.024-3.585 2.064-3.708.032-.098.843 1.287-.09 2.921 2.706-1.309 5.118-3.463 7.064-6.073 1.699-2.846-.683-8.557-.683-8.557s2.85 2.13 2.757 5.288c1.556-2.906 2.585-6.104 2.911-9.2-.035-3.061-3.459-5.13-3.459-5.13z"/><path fill="#FFCC4D" d="M13.859 9.495c.596 2.392.16 4.422-2.231 5.017-2.392.596-6.363.087-6.958-2.304-.596-2.392.469-5.39 1.81-5.724 1.341-.334 6.784.62 7.379 3.011zm9.104 18.432c0 2.74-2.222 4.963-4.963 4.963s-4.963-2.223-4.963-4.963c0-2.741 2.223-4.964 4.963-4.964 2.741 0 4.963 2.222 4.963 4.964z"/><path fill="#DD2E44" d="M21.309 27.927c0 1.827-1.481 3.309-3.309 3.309s-3.309-1.481-3.309-3.309c0-1.827 1.481-3.31 3.309-3.31s3.309 1.483 3.309 3.31z"/><path fill="#E6AAAA" d="M11.052 8.997c.871 1.393.447 3.229-.946 4.1-1.394.871-2.608.797-3.479-.596-.871-1.394-.186-4.131.324-4.45.51-.319 3.23-.448 4.101.946z"/><path fill="#FFCC4D" d="M22.141 9.495c-.596 2.392-.159 4.422 2.232 5.017 2.392.596 6.363.087 6.959-2.304.596-2.392-.47-5.39-1.811-5.724-1.342-.334-6.786.62-7.38 3.011z"/><path fill="#E6AAAA" d="M24.948 8.997c-.871 1.393-.447 3.229.945 4.1 1.394.871 2.608.797 3.479-.596.871-1.394.185-4.131-.324-4.45-.51-.319-3.229-.448-4.1.946z"/><path fill="#FFCC4D" d="M18 7.125h-.002C5.167 7.126 7.125 12.083 8.5 18.667 9.875 25.25 10.384 27 10.384 27h15.228s.51-1.75 1.885-8.333C28.872 12.083 30.829 7.126 18 7.125z"/><path fill="#272B2B" d="M12 16s0-1.5 1.5-1.5S15 16 15 16v1.5s0 1.5-1.5 1.5-1.5-1.5-1.5-1.5V16zm9 0s0-1.5 1.5-1.5S24 16 24 16v1.5s0 1.5-1.5 1.5-1.5-1.5-1.5-1.5V16z"/><path fill="#FFE8B6" d="M20.168 21.521c-1.598 0-1.385.848-2.168 2.113-.783-1.266-.571-2.113-2.168-2.113-6.865 0-6.837.375-6.865 2.828-.058 4.986 2.802 6.132 5.257 6.06 1.597-.048 2.994-.88 3.777-2.131.783 1.251 2.179 2.083 3.776 2.131 2.455.072 5.315-1.073 5.257-6.06-.029-2.453-.001-2.828-6.866-2.828z"/><path fill="#272B2B" d="M14.582 21.411c-1.14.233 2.279 4.431 3.418 4.431s4.559-4.198 3.419-4.431c-1.14-.232-5.698-.232-6.837 0z"/><circle fill="#D99E82" cx="11.5" cy="24.5" r=".5"/><circle fill="#D99E82" cx="10.5" cy="26.5" r=".5"/><circle fill="#D99E82" cx="12.5" cy="27.5" r=".5"/><circle fill="#D99E82" cx="24.5" cy="24.5" r=".5"/><circle fill="#D99E82" cx="25.5" cy="26.5" r=".5"/><circle fill="#D99E82" cx="23.5" cy="27.5" r=".5"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f986.svg b/games/sound-soup/assets/img/1f986.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#D99E82" d="M12.75 17.75c1.226 2.195 1.856-1.361 9.312-2.625s13.729 4.454 13.859 5.413c.132.958-4.447 9.462-9.462 9.462H10.75c-4.143 0-7.022-7.224-4-11.438 0 0 4.5-3.5 6-.812z"/><path fill="#C1694F" d="M13.008 18.136C8.02 25.073 6.969 30 10.75 30c-4.143 0-6.578-6.188-4.468-11.031.463-1.064 1.758-2.492 1.758-2.492l4.179-.008c.162.32.599 1.365.789 1.667z"/><path fill="#E1E8ED" d="M20.062 22.75c6.672-2.682 15.729-3.171 15.859-2.212.132.958-4.447 9.462-9.462 9.462H11.813c-4.143 0 1.232-4.429 8.249-7.25z"/><path fill="#292F33" d="M25.359 21.125c6-1.312 10.468-1.282 10.563-.587.079.578-1.559 3.907-3.973 6.454 2.204-2.804 1.42-6.113-6.59-5.867z"/><path fill="#3E721D" d="M7.125 6.125c1.562-4.938 10.44-4.143 10.062.688-.378 4.829-6.453 7.859-4.179 11.323-1.586 1.38-5.016.913-6.727.833.702-1.351 1.381-2.631 1.812-3.406 2.916-5.235-2.131-5.764-.968-9.438z"/><path fill="#FFCC4D" d="M1.893 9.045c.568-1.1.549.106 3.352.142 1.554.021 1.463-.56 1.664-1.621.041-.223.776 1.616 2.061 1.807 3.554.531 1.879 1.94 0 2.261-.901.154-2.01.318-3.938.155-2.519-.212-3.768-1.528-3.139-2.744z"/><path fill="#F5F8FA" d="M12.502 16.625c.044.625.506 1.511.506 1.511-1.016 1.474-5.643 3.017-7.354 2.93.096-.628.283-1.362.627-2.097 1.844-.471 4.661-1.071 6.221-2.344z"/><path fill="#292F33" d="M11.708 6.151c0 .716-.58 1.296-1.296 1.296s-1.295-.58-1.295-1.296c0-.716.579-1.295 1.295-1.295s1.296.58 1.296 1.295z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f987.svg b/games/sound-soup/assets/img/1f987.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#66757F" d="M23 21c0 6.352-3 10-5 10s-5-3.648-5-10 2.239-7 5-7c2.762 0 5 .648 5 7z"/><circle fill="#66757F" cx="18" cy="11" r="4"/><path fill="#66757F" d="M14 11c-2-5 1-7 1-7s2 1 2 4-3 3-3 3z"/><path fill="#546066" d="M14.668 9.904c-.776-2.457-.119-3.896.403-4.58C15.486 5.773 16 6.608 16 8c0 1.268-.739 1.734-1.332 1.904z"/><path fill="#66757F" d="M22 11c2-5-1-7-1-7s-2 1-2 4 3 3 3 3zm-5.984 3c-1.62 1.157-10 2-9-5 .142-.99-1-1-2 0-3 3-6 7.834-4 20 3-5 6-4 7 1 3-4 6-2 8 0 3-3 0-16 0-16zm3.937 0c1.62 1.157 10 2 9-5-.142-.99 1-1 2 0 3 3 6 7.834 4 20-3-5-6-4-7 1-3-4-6-2-8 0-3-3 0-16 0-16z"/><circle fill="#292F33" cx="16" cy="11" r="1"/><circle fill="#292F33" cx="20" cy="11" r="1"/><path fill="#546066" d="M21.332 9.904c.775-2.457.118-3.896-.403-4.58C20.514 5.773 20 6.608 20 8c0 1.268.739 1.734 1.332 1.904z"/><path fill="#99AAB5" d="M7.996 26.91c.892-2.691.573-5.988-.996-9.91-1.487-3.719-1.315-6.329-1.129-7.423-.049.041-.096.078-.148.13C3.017 12.414.477 16.531 1.66 26.436c1.276-1.379 2.412-1.723 3.228-1.723 1.265 0 2.333.783 3.108 2.197z"/><path fill="#99AAB5" d="M6.832 13.25c-.019-.03-.041-.058-.06-.087C7 16 8.4 17.001 9 20c.588 2.94.476 5.519.088 7.564.839-.571 1.726-.874 2.656-.874 1.264 0 2.548.538 3.895 1.627C14 19 9 17 6.832 13.25zm21.172 13.66c-.893-2.691-.572-5.988.996-9.91 1.487-3.719 1.315-6.329 1.129-7.423.049.041.097.078.148.13 2.706 2.707 5.246 6.824 4.063 16.729-1.275-1.379-2.412-1.723-3.227-1.723-1.266 0-2.334.783-3.109 2.197z"/><path fill="#99AAB5" d="M29.168 13.25l.061-.087C29 16 27.6 17.001 27 20c-.588 2.94-.477 5.519-.088 7.564-.84-.571-1.726-.874-2.656-.874-1.264 0-2.548.538-3.895 1.627C22 19 27 17 29.168 13.25zm-10.48-.144c-.378.378-.998.378-1.375 0l-.57-.571c-.378-.378-.25-.688.285-.688h1.945c.535 0 .664.309.285.688l-.57.571z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f989.svg b/games/sound-soup/assets/img/1f989.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#662113" d="M7.317 11c-5.723 9.083.958 18 .958 18s2.874-.442 6.875-5.2c4-4.758-7.833-12.8-7.833-12.8zm21.342 0c5.723 9.083-.958 18-.958 18s-2.874-.442-6.875-5.2C16.825 19.042 28.659 11 28.659 11z"/><path fill="#FFAC33" d="M15.203 31.557c-.123-.229-.317-.384-.531-.496-2.032-2.172-.589-4.717-.589-4.717 0-.703 1.271-2.544 0-2.544l-1.272 1.272c-1.272 1.271-1.272 5.089-1.272 5.089H8.995c-.703 0-1.272.568-1.272 1.272 0 .701.569 1.271 1.272 1.271h1.408l-.282.153c-.618.334-.848 1.105-.514 1.726.334.616 1.106.848 1.724.514l3.357-1.816c.619-.334.846-1.105.515-1.724zm5.596 0c.123-.229.317-.384.53-.496 2.033-2.172.589-4.717.589-4.717 0-.703-1.271-2.544 0-2.544l1.272 1.272c1.273 1.271 1.273 5.089 1.273 5.089h2.544c.704 0 1.271.568 1.271 1.272 0 .701-.566 1.271-1.271 1.271h-1.408l.281.153c.62.334.848 1.105.515 1.726-.336.616-1.105.848-1.726.514l-3.356-1.816c-.619-.334-.847-1.105-.514-1.724z"/><path fill="#662113" d="M28.278 11.292c2.891-6.092 0-10.542 0-10.542s-5.781.959-6.744 2.875c-1.219 2.424 6.744 7.667 6.744 7.667z"/><path fill="#662113" d="M29.562 12.738c0 10.297-3.152 20.595-11.562 20.595-8.409 0-11.563-10.298-11.563-20.595C6.437 2.44 11.614 2.083 18 2.083c6.387 0 11.562.357 11.562 10.655z"/><path fill="#C1694F" d="M27.666 17.738c0 10.297-7.774 14.595-9.666 14.595s-9.666-4.298-9.666-14.595c0-10.298 19.332-10.298 19.332 0z"/><path fill="#662113" d="M7.722 11.292C4.831 5.2 7.722.75 7.722.75s5.782.959 6.746 2.875c1.218 2.424-6.746 7.667-6.746 7.667z"/><path fill="#C1694F" d="M14.929 4.373C10.702 2.789 7.722.75 7.722.75s-2.076 3.221-.928 7.926c.446 2.137 1.94 4.195 3.904 4.662 2.637.627 7.302-.049 7.302-3.963 0-2.695-1.074-4.252-3.071-5.002zm6.142 0C25.298 2.789 28.277.75 28.277.75s2.076 3.221.928 7.926c-.445 2.137-1.939 4.195-3.902 4.662-2.638.627-7.303-.049-7.303-3.963 0-2.695 1.074-4.252 3.071-5.002z"/><path fill="#FFD983" d="M16.083 8.417c0 2.117-1.716 3.833-3.833 3.833s-3.833-1.716-3.833-3.833 1.717-3.833 3.833-3.833c2.117-.001 3.833 1.716 3.833 3.833zm11.5 0c0 2.117-1.716 3.833-3.833 3.833s-3.833-1.716-3.833-3.833 1.716-3.833 3.833-3.833 3.833 1.716 3.833 3.833z"/><path fill="#292F33" d="M14.167 8.417c0 1.059-.858 1.917-1.917 1.917s-1.917-.858-1.917-1.917c0-1.058.858-1.917 1.917-1.917s1.917.858 1.917 1.917zm11.5 0c0 1.059-.858 1.917-1.917 1.917s-1.917-.858-1.917-1.917c0-1.058.858-1.917 1.917-1.917s1.917.858 1.917 1.917z"/><path fill="#FFCC4D" d="M20.875 12.729c0 2.382-2.875 3.354-2.875 3.354s-2.875-.973-2.875-3.354S18 9.375 18 9.375s2.875.972 2.875 3.354z"/><path fill="#F4900C" d="M20.875 12.729c0 2.382-2.875 3.354-2.875 3.354s-2.875-.973-2.875-3.354C16.323 13.927 18 14.167 18 14.167s1.677-.24 2.875-1.438z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f98a.svg b/games/sound-soup/assets/img/1f98a.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#F4900C" d="M13.431 9.802c.658 2.638-8.673 10.489-11.244 4.098C.696 10.197-.606 2.434.874 2.065c1.48-.368 11.9 5.098 12.557 7.737z"/><path fill="#A0041E" d="M11.437 10.355c.96 1.538-1.831 4.561-3.368 5.522-1.538.961-2.899-.552-4.414-4.414-.662-1.689-1.666-6.27-1.103-6.622.562-.351 7.924 3.976 8.885 5.514z"/><path fill="#F4900C" d="M22.557 9.802C21.9 12.441 31.23 20.291 33.802 13.9c1.49-3.703 2.792-11.466 1.312-11.835-1.48-.368-11.899 5.098-12.557 7.737z"/><path fill="#A0041E" d="M24.552 10.355c-.96 1.538 1.831 4.561 3.368 5.522 1.537.961 2.898-.552 4.413-4.414.662-1.688 1.666-6.269 1.104-6.621-.563-.352-7.924 3.975-8.885 5.513z"/><path fill="#F18F26" d="M32.347 26.912c0-.454-.188-1.091-.407-1.687.585.028 1.519.191 2.77.817-.008-.536-.118-.984-.273-1.393.041.02.075.034.116.055-1.103-3.31-3.309-5.517-3.309-5.517h2.206c-2.331-4.663-4.965-8.015-8.075-9.559-1.39-.873-3.688-1.338-7.373-1.339h-.003c-3.695 0-5.996.468-7.385 1.346-3.104 1.547-5.734 4.896-8.061 9.552H4.76s-2.207 2.206-3.311 5.517c.03-.015.055-.025.084-.04-.201.392-.307.847-.282 1.377 1.263-.632 2.217-.792 2.813-.818-.189.513-.343 1.044-.386 1.475-.123.371-.191.812-.135 1.343C6.75 26.584 8.25 26.792 10 27.667 11.213 31.29 14.206 34 18.001 34c3.793 0 6.746-2.794 7.958-6.416 1.458-1.25 3.708-.875 6.416.416.066-.414.036-.773-.036-1.093l.008.005z"/><path fill="#FFD983" d="M31.243 23.601c.006 0 1.108.003 3.309 1.103-1.249-2.839-7.525-4.07-9.931-3.291-1.171 1.954-1.281 5.003-3.383 6.622-1.741 1.431-4.713 1.458-6.479 0-2.345-1.924-2.559-5.813-3.382-6.622-2.407-.781-8.681.454-9.931 3.291 2.201-1.101 3.304-1.103 3.309-1.103 0 .001-1.103 2.208-1.103 3.311l.001-.001v.001c2.398-1.573 5.116-2.271 7.429-.452 1.666 7.921 12.293 7.545 13.833 0 2.314-1.818 5.03-1.122 7.429.452v-.001l.001.001c.002-1.103-1.101-3.311-1.102-3.311z"/><path fill="#272B2B" d="M11 17s0-1.5 1.5-1.5S14 17 14 17v1.5s0 1.5-1.5 1.5-1.5-1.5-1.5-1.5V17zm11 0s0-1.5 1.5-1.5S25 17 25 17v1.5s0 1.5-1.5 1.5-1.5-1.5-1.5-1.5V17zm-7.061 10.808c-1.021.208 2.041 3.968 3.062 3.968 1.02 0 4.082-3.76 3.062-3.968-1.021-.208-5.103-.208-6.124 0z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f98e.svg b/games/sound-soup/assets/img/1f98e.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#3E721D" d="M15.66 6.844c.485-2.03.384-3.139-.553-3.677-.936-.537.337-2.666 1.977-.793 1.641 1.873 1.428 3.618.662 4.853-.767 1.234-2.336.66-2.086-.383zm-.48 2.951c-.916 1.877-1.699 2.668-2.764 2.487-1.064-.18-1.435 2.272 1.023 1.869 2.457-.403 3.401-1.886 3.595-3.326.193-1.44-1.383-1.994-1.854-1.03zm11.336 6.261c-2.072-1.377-5.382 1.43-5.699 2.28-.317.85 1.721 3.461 1.146 4.94-.577 1.477 1.142 1.768 1.92.491.778-1.278.304-3.612.156-4.169-.148-.557 2.189-1.219 3.671-.037 1.481 1.18-.323-2.925-1.194-3.505zm1.068-.818c-1.546-1.949.975-5.482 1.794-5.87.821-.387 2.572.402 3.997-.295 1.424-.697 1.745.854.65 1.874-1.227 1.142-2.241 1.335-3.119 1.526-.563.122-.032 2.033-.009 3.97.023 1.799-2.663-.385-3.313-1.205z"/><path fill="#3E721D" d="M15.319 3.167c0 .4-.324.724-.724.724-.399 0-.724-.324-.724-.724 0-.399.324-.724.724-.724.4 0 .724.325.724.724zm-2.999 8.897c0 .399-.324.724-.724.724-.399 0-.724-.324-.724-.724 0-.399.324-.724.724-.724.4 0 .724.324.724.724zm-.298 1.677c0 .4-.324.724-.724.724-.399 0-.724-.324-.724-.724 0-.399.324-.724.724-.724.4 0 .724.324.724.724zm1.447.724c0 .399-.324.724-.724.724-.399 0-.724-.324-.724-.724 0-.4.324-.724.724-.724.4 0 .724.324.724.724zm8.575 8.848c.128.378-.073.791-.452.92-.377.129-.789-.075-.918-.453s.072-.79.45-.919c.379-.129.791.074.92.452zm.772 1.268c.128.378-.073.79-.452.919-.377.128-.789-.074-.918-.452-.129-.377.073-.79.451-.918.379-.13.791.073.919.451zm1.627-.24c.13.377-.073.79-.452.919-.376.129-.788-.074-.917-.451-.129-.378.073-.791.45-.919.379-.13.791.073.919.451zm9.803-15.752c0 .399-.325.724-.725.724s-.724-.325-.724-.724c0-.399.324-.725.724-.725s.725.326.725.725zm1.504.725c0 .399-.324.724-.725.724-.398 0-.723-.325-.723-.724s.324-.724.723-.724c.401-.001.725.325.725.724zm-.346 1.716c0 .399-.324.724-.725.724-.399 0-.724-.325-.724-.724 0-.399.324-.724.724-.724.401 0 .725.325.725.724zM15.512 1.618c0 .399-.324.724-.724.724-.399 0-.724-.324-.724-.724 0-.4.324-.724.724-.724.4 0 .724.324.724.724zm1.571-.469c0 .4-.324.724-.724.724-.399 0-.724-.324-.724-.724 0-.399.324-.724.724-.724.4.001.724.325.724.724z"/><path fill="#3E721D" d="M.208 5.652C.472 4.599 4.865.929 8.85 1.618c2.024.351 2.942 3.354 5.102 3.428 11.289.389 22.272 11.138 18.847 21.391-1.506 4.507-5.618 8.798-15.461 5.001-1.956-.754-3.97-1.985-7.824-1.227-2.852.563-3.149 3.159-2.803 4.497.272 1.048-.161 1.582-.701.759-1.226-1.867-1.317-4.347.643-5.955 3.912-3.211 9.248-1.284 13.137-.059 6.481 2.044 9.917-3.651 6.346-10.315-3.503-6.54-9.812-8.324-14.005-8.816-1.168-.137-3.639 2.048-5.367 1.742C3.143 11.421.018 6.416.208 5.652z"/><path fill="#77B255" d="M.208 5.652C.472 4.599 4.865.929 8.85 1.618c2.024.351 2.942 3.354 5.102 3.428 11.289.389 22.272 11.138 18.847 21.391-1.506 4.507-5.618 8.798-15.461 5.001-1.956-.754-3.97-1.985-7.824-1.227-2.852.563-3.149 3.159-2.803 4.497.134.516.097.906-.039 1.073-.922-1.281-1.362-4.339.661-5.864 3.539-2.667 8.568-.131 13.301 1.042 10.251 2.541 12.183-7.995 7.907-14.875C23.355 7.736 7 5.75.208 5.652z"/><circle fill="#292F33" cx="7" cy="3" r="1"/><circle fill="#292F33" cx="6" cy="10" r="1"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f99c.svg b/games/sound-soup/assets/img/1f99c.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#66757F" d="M17.5 35c-.128 0-.256-.049-.354-.146-.195-.195-.195-.512 0-.707L19 32.293V29.5c0-.276.224-.5.5-.5s.5.224.5.5v3.207l-2.146 2.146c-.098.098-.226.147-.354.147z"/><path fill="#66757F" d="M19.5 33h-3c-.276 0-.5-.224-.5-.5s.224-.5.5-.5h3c.276 0 .5.224.5.5s-.224.5-.5.5zm3 2c-.128 0-.256-.049-.354-.146-.195-.195-.195-.512 0-.707L24 32.293V29.5c0-.276.224-.5.5-.5s.5.224.5.5v3.207l-2.146 2.146c-.098.098-.226.147-.354.147z"/><path fill="#66757F" d="M24.5 33h-3c-.276 0-.5-.224-.5-.5s.224-.5.5-.5h3c.276 0 .5.224.5.5s-.224.5-.5.5z"/><path fill="#F4900C" d="M3.53 13.798c.845.452 3.813-1.411 4.36-3.308S6.869 8.374 5.008 9.25 2.8 13.407 3.53 13.798z"/><path fill="#FFAC33" d="M6.325 4C3.77 5 2.047 7.378 2.031 10.088c-.016 2.699 1.219 4.423 2.594 4.047.376-.103-.303-1.922.85-4.371.57-1.207 1.303-1.35 1.675-1.313.373.036.443-4.947-.825-4.451z"/><path fill="#77B255" d="M34.525 31.384c-.014.499-1.916.822-5.592-3.599-2.927 3.09-7.374 3.182-11.821 1.422-4.999-1.979-8.383-6.924-7.701-13.976.382-3.954-5.228-3.587-5.081-8.899C4.441 2.337 7.843.797 10.704.876c4.74.131 6.82 5.293 8.052 6.561 6.494 6.688 15.81 22.47 15.769 23.947z"/><path fill="#EA596E" d="M28.932 27.785c-2.927 3.09-7.374 3.182-11.821 1.422-4.999-1.979-8.383-6.924-7.701-13.976.036 4.343 1.559 7.582 2.94 8.416-.58-2.79.117-7.062.784-7.779 1.591 9.402 10.022 14.78 15.798 11.917z"/><path fill="#3B88C3" d="M13.135 15.867s1.737 5.788 7.33 9.387 8.468 2.53 8.468 2.53-2.939 3.363-7.647 2.084-9.321-5.997-8.151-14.001z"/><path fill="#5C913B" d="M16.938 9.417c-2.504-.588-6.666 7.123-1.009 13.392 7.436 8.241 13.004 4.976 13.004 4.976S24.785 11.259 16.938 9.417z"/><circle fill="#292F33" cx="8" cy="6" r="1"/><path fill="#5C913B" d="M10.724 13.259c-.79 0-1.509-.189-1.976-.45-.248-.139-.336-.453-.198-.701.14-.248.454-.336.701-.198.665.373 2.406.63 3.614-.423.213-.187.539-.165.726.05.187.214.164.54-.05.726-.836.729-1.877.996-2.817.996z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f9a9.svg b/games/sound-soup/assets/img/1f9a9.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#EA596E" d="M22.903 27.55c1.218-.812 2.232-.783 3.073-.058-.435-1.131-2.348-3.624-2.348-3.624l-.725 3.682z"/><path fill="#F4ABBA" d="M22.949 27.679c-.03 0-.06-.002-.091-.006-.401-.05-.686-.415-.637-.816l.426-3.448c.023-.188.117-.358.264-.478l4.448-3.624-7.108-3.833c-.356-.192-.488-.636-.296-.992.193-.356.636-.489.992-.297l8.072 4.353c.214.116.357.332.381.575.023.244-.077.482-.267.636l-5.07 4.13-.39 3.156c-.044.373-.36.644-.724.644z"/><path fill="#E1E8ED" d="M5.38 3.523L1.476 7.427C.5 8.403.5 11.331.5 13.283s1.952 1.952 1.952 0 3.904-4.88 6.832-5.856S5.38 3.523 5.38 3.523z"/><path fill="#292F33" d="M1.476 7.427C.5 8.403.5 11.331.5 13.283s1.952 1.952 1.952 0c0-.671.461-1.456 1.184-2.238 1.383-1.495-2.16-.69-2.16-3.618z"/><path fill="#EA596E" d="M16.598 31.913c.522.87.478 2.283.283 3.087.957-.457 2.435-2.827 2.435-2.827l-2.718-.26z"/><path fill="#EA596E" d="M17.366 35.203c.928-.616 2.595-.573 3.58-.138.986.435-.681-2.529-.681-2.529l-2.435 1.507-.464 1.16z"/><path fill="#F4ABBA" d="M25.877 27.679c-.236 0-.469-.114-.61-.326l-1.952-2.928c-.224-.336-.133-.791.203-1.015.337-.224.791-.133 1.015.203l1.952 2.928c.224.336.133.791-.203 1.015-.125.083-.266.123-.405.123zm-5.857 4.88h-2.928c-.404 0-.732-.328-.732-.732s.328-.732.732-.732h2.196V16.211c0-.404.328-.732.732-.732s.732.328.732.732v15.616c0 .404-.328.732-.732.732z"/><path fill="#F4ABBA" d="M20.996 35.487c-.307 0-.592-.193-.695-.5l-.601-1.804-2.09 2.09c-.286.286-.749.286-1.035 0-.286-.286-.286-.749 0-1.035l2.928-2.928c.18-.18.443-.252.685-.194.247.058.446.24.527.48l.976 2.928c.128.384-.08.798-.463.926-.077.025-.156.037-.232.037z"/><circle fill="#EA596E" cx="8.308" cy="4.499" r="3.904"/><path fill="#EA596E" d="M12.579 20.716c-.573 0-1.14-.098-1.688-.3-2.025-.748-3.217-2.756-3.036-5.115.223-2.908 1.853-4.853 3.164-6.415 1.175-1.402 1.833-2.241 1.696-3.203-.163-1.143-.848-1.673-1.393-1.917-1.068-.477-2.506-.286-3.666.488L6.032 1.817C8.039.479 10.524.201 12.517 1.092c1.697.759 2.826 2.281 3.096 4.176.329 2.303-1.034 3.928-2.351 5.499-1.201 1.433-2.337 2.786-2.488 4.758-.081 1.051.342 1.852 1.131 2.144.858.315 2.341.04 3.68-1.792l2.364 1.727c-1.461 1.997-3.442 3.112-5.37 3.112z"/><path fill="#EA596E" d="M15.14 17.187c-2.166-3.248 0-7.808 2.928-9.76s10.736-2.928 15.616 4.88c5.401 8.641-2.763 4.433-4.88 3.904-7.808-1.952-11.712 3.904-13.664.976z"/><circle fill="#292F33" cx="6.356" cy="4.499" r=".976"/><path fill="#EA596E" d="M9.284 6.451c1.952-.976 4.255-.997 1.327 2.907-1.171 1.562 5.282-4.671 1.957-8.175-.536-.565-4.407-1.087-5.943-.174-.982.584 2.659 5.442 2.659 5.442z"/><circle fill="#EA596E" cx="28.804" cy="19.139" r=".976"/><circle fill="#EA596E" cx="20.02" cy="24.995" r=".976"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f9ab.svg b/games/sound-soup/assets/img/1f9ab.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#C1694F" d="M2.262 12.452c-.238 1.726 0 3.214 1.131 3.036.532-.084 1.071-1.488.893-2.381-.179-.893-2.024-.655-2.024-.655z"/><path fill="#FFE9B7" d="M2.56 12.929c-.238 1.726.06 2.5.595 2.44.536-.06.833-1.31.655-2.202-.179-.893-1.25-.238-1.25-.238z"/><path fill="#C1694F" d="M1.37 9.544c.932-.673 2.495-1.916 4.322-2.406s4.144-.579 4.901-.757c.757-.178 2.584-.446 2.851.312s3.743 2.094 5.525 1.827 7.359-1.037 10.871 1.025c4.099 2.406 5.614 7.262 4.767 10.738-.847 3.475-2.896 6.059-5.525 6.371-2.629.312-5.748.178-8.332-2.495s-4.188-5.436-6.683-5.703-3.03-2.139-3.787-3.03c-.757-.891-3.208-.356-5.213-.802-1.532-.34-2.762-1.827-3.342-2.941s-1.157-1.56-.355-2.139z"/><path fill="#93493B" d="M12.144 14.035c-2.477 0-4.789-1.862-4.789-1.862s-4.033.944-5.629-.49c.579 1.114 1.809 2.6 3.342 2.941 2.005.446 4.455-.089 5.213.802.254.298.487.708.773 1.129.634-.829 1.35-2.082 1.09-2.52zm-.014-5.758s.725.275 1.239-.276c.35-.376.38-.729.319-.999-.126-.109-.211-.214-.244-.309l-.005-.007c-.028.352-.216 1.15-1.309 1.591zm5.131 7.874c-.449.766-1.39 2.016-1.904 2.686 1.321.647 2.452 2.002 3.776 3.531 1.4-.7 4.878-2.38 6.847-2.804-3.102-3.086-6.694-1.564-8.719-3.413zm10.571 4.495c-.438.189-.602 3.636-.664 6.123.656.003 1.299-.044 1.915-.117 2.629-.312 4.678-2.896 5.525-6.371.446-1.829.23-4.039-.665-6.075.481 4.745-4.255 6.69-6.111 6.44z"/><path fill="#292F33" d="M.493 10.45C.33 9.455 1.592 9.128 1.86 9.663c.267.535.876 2.525.356 2.51-.637-.019-1.505-.401-1.723-1.723z"/><path fill="#662113" d="M24.805 19.167c-1.565-.351-5.168-.535-6.015 1.47-.144.341-.178.745-.144 1.168.648.746 1.332 1.553 2.105 2.352 2.4 2.483 4.36 2.582 6.861 2.361.526-.943 1.412-2.252 1.203-3.386-.312-1.692-1.426-3.385-4.01-3.965z"/><path fill="#C1694F" d="M24.939 19.346c-2.673-.312-4.901.356-5.881 1.782s.401 3.921 3.03 4.812c0 0-3.475.178-3.921.757-.446.579-.446 1.649.178 1.693.624.045 7.218-.579 8.955-.802 1.738-.223 2.139-2.807 1.827-4.5s-1.615-3.442-4.188-3.742z"/><path fill="#662113" d="M14.409 14.023c-1.337-.312-2.688-.163-2.911 1.218-.088.542-.254 1.215-.327 1.634.575.876 1.142 1.392 2.896 1.58.602.065 1.152.277 1.677.596.437-.634.804-1.349.996-2.066.43-1.599-.994-2.65-2.331-2.962z"/><path fill="#C1694F" d="M11.172 14.979c-.215 1.336-.212 2.264-.847 2.941-.668.713-1.871 1.158-2.851 1.203-.98.045-1.782.936-1.782 1.916s.757 1.47 1.248 1.559c.766.139 1.559.178.847-.535-.713-.713-.09-1.435.713-1.47 1.025-.045 2.985-.045 4.054-.49 1.069-.446 3.23-1.77 4.188-3.119 2.406-3.386-.847-3.119-2.183-3.431-1.338-.311-3.165.045-3.387 1.426z"/><path fill="#662113" d="M10.905 7.152c.456-.684 2.629-.624 1.738.178s-2.54 1.025-1.738-.178z"/><circle fill="#292F33" cx="7" cy="9.001" r="1"/><path fill="#662113" d="M31.31 22.197c1.233-.231 3.965 2.228 3.52 5.391-.446 3.163-3.653 5.881-7.129 7.04s-12.609 1.604-16.173.312c-3.564-1.292-2.851-4.455 0-5.703s6.861-1.693 11.406-1.604c3.566.07 7.04-1.693 6.861-2.762-.178-1.07.089-2.407 1.515-2.674z"/><path fill="#C1694F" d="M31.845 24.202c1.025 1.871.422 4.916-3.832 6.371-3.386 1.158-6.793.825-9.401.891-1.96.05-3.698.223-4.634.401 0 0 1.66-.677 4.366-.847 2.274-.142 6.798-.136 8.465-.668 6.283-2.004 5.036-6.148 5.036-6.148z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f9b5.svg b/games/sound-soup/assets/img/1f9b5.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#FFDC5D" d="M24.854.731c3.633 1.239 4.935 8.505.991 10.297-1.586.72-4.049 1.511-6.185 1.032-2.737-.613-4.721-.238-4.721-.238s4.797 2.19 6.339 6.323c1.542 4.133 1.072 7.171 3.235 9.163 1.708 1.573 2.79 3.7 1.639 4.994-1.545 1.738-3.929-.028-6.1 2.48-1.522 1.758-4.81 1.182-7.088.142-2.879-1.314.807-2.191 1.842-2.297 1.15-.117 5.29-2.348 3.33-5.544-4.196-6.841-8.501-10.544-11.313-12.61-2.514-1.846-2.772-5.313-.147-8.465 1.662-1.996 5.229-3.554 9.253-4.733 2.8-.821 5.973-1.551 8.925-.544z"/><path fill="#EF9645" d="M8.267 13.047l-.042-.02c-.381-.202-.526-.674-.324-1.054 1.154-2.179.472-3.999.443-4.076-.155-.4.041-.852.44-1.01.398-.157.849.033 1.009.432.041.102.993 2.541-.513 5.383-.194.367-.64.515-1.013.345z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f9b6.svg b/games/sound-soup/assets/img/1f9b6.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#FFDC5D" d="M34.624 11.009c.322-2.495-10.793-4.379-10.996-2.002-.203 2.377-.247 4.06-1.02 6.958C21.358 20.65 15.954 23 10.583 23H8.895c-.653 0-1.207.426-1.411 1.012-1.024 0-1.646.58-1.646 1.262-.531 0-1.713.508-1.713 1.725 0 0-2.5-.106-2.5 2.5l.002.015c-2.145.606-1.53 4.091.93 4.419 2.334.311 2.428-.369 4.428-.369 1.968 0 2.255.435 4.64.435 4.913 0 4.79-.968 9.447-1.681 5.138-.787 6.406 1.124 9.406 1.124s5.207-2.339 5.207-5.412c0-5-2.059-5.03-2.059-9.03s.676-5.497.998-7.991z"/><path fill="#EF9645" d="M27.894 23.077c-.262 0-.481-.202-.499-.467-.019-.275.19-.514.466-.532.991-.066 1.563-.676 1.869-1.177.514-.842.655-2.045.329-2.799-.109-.253.007-.548.261-.658.252-.108.547.007.657.26.458 1.059.296 2.587-.394 3.718-.605.992-1.549 1.579-2.656 1.653-.011.002-.022.002-.033.002zM6.766 28H4.069c-.825 0-1.927.476-1.927 1.301 0 .005.627-.249 1.886-.249l2.738.001c.291 0 .526-.236.526-.526 0-.291-.235-.527-.526-.527zm.897-1.839H5.858c-.544 0-1.006.352-1.18.839h2.985c.231 0 .419-.189.419-.419.001-.231-.188-.42-.419-.42zm.884-1.445H7.133c-.362 0-.669.234-.785.558h2.199c.153 0 .279-.126.279-.279 0-.153-.126-.279-.279-.279zm.732-1.034h-.797c-.214 0-.396.139-.465.33h1.262c.091 0 .165-.074.165-.165 0-.091-.074-.165-.165-.165z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f9b7.svg b/games/sound-soup/assets/img/1f9b7.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#E1E8ED" d="M18 2C13 2 11.11-1.276 7.002.7 3 2.625 7 17.002 9 20c-.444 2.537-.59 8.787-.171 10.91.576 2.921 1.561 5.09 2.748 5.09 2 0 2.423-13.002 6.423-13V2zm0 0c5 0 6.89-3.276 10.998-1.3C33 2.625 29 17.002 27 20c.444 2.537.59 8.787.171 10.91-.575 2.921-1.561 5.09-2.748 5.09-2 0-2.423-13.002-6.423-13V2z"/><path fill="#9AAAB4" d="M18 7.259c-2.278 0-4.419-.81-5.444-1.563-.334-.245-.406-.715-.161-1.048.246-.335.714-.405 1.048-.161.84.617 2.722 1.273 4.556 1.273 1.97 0 3.942-.709 4.489-1.217.305-.282.777-.264 1.06.039.281.304.265.778-.039 1.06-.958.891-3.431 1.617-5.509 1.617z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f9c2.svg b/games/sound-soup/assets/img/1f9c2.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#66757F" d="M26 4c0 3-3.582 6-8 6s-8-3-8-6c0-4.418 6-4 8-4s8-.418 8 4z"/><path fill="#CCD6DD" d="M24.306 12H11.694c-.978 0-1.812.707-1.973 1.671l-3.333 20C6.185 34.89 7.125 36 8.361 36h19.278c1.236 0 2.176-1.11 1.973-2.329l-3.333-20c-.161-.964-.996-1.671-1.973-1.671z"/><path fill="#F5F8FA" d="M19.344 14h-6.802c-.489 0-.906.353-.986.836l-3 18c-.103.609.367 1.164.985 1.164h10.427l-.624-20zm5.101.836c-.08-.483-.498-.836-.986-.836h-2.413l.909 20h4.505c.618 0 1.088-.555.986-1.164l-3.001-18z"/><ellipse fill="#CCD6DD" cx="15" cy="1.5" rx="1.2" ry=".6"/><ellipse fill="#CCD6DD" cx="21" cy="1.5" rx="1.2" ry=".6"/><ellipse fill="#CCD6DD" cx="18" cy="3.5" rx="1.2" ry=".6"/><path fill="#66757F" d="M25 12H11V9.528c0-.844.684-1.262 1.528-1.262h10.944c.844 0 1.528.418 1.528 1.262V12z"/><path fill="#CCD6DD" d="M23.472 8H12.528c-.657 0-1.213.417-1.429 1H24.9c-.215-.583-.77-1-1.428-1zM11 10h14v1H11zm0 2h14v.5H11z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f9d1-200d-1f373.svg b/games/sound-soup/assets/img/1f9d1-200d-1f373.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#E1E8ED" d="M36 36v-2c0-3.314-2.686-6-6-6H14c-3.313 0-6 2.686-6 6v2h28z"/><path fill="#269" d="M22 36l5-8H17z"/><path fill="#FFDC5D" d="M17.64 28.038c0 1.962 8.72 1.962 8.72 0v-3.749h-8.72v3.749z"/><path fill="#F9CA55" d="M17.632 25.973c1.216 1.374 2.724 1.746 4.364 1.746 1.639 0 3.146-.373 4.363-1.746v-3.491h-8.728v3.491z"/><path fill="#FFAC33" d="M23.157 6.984c-1.925-.623 1.444-3.7.312-2.228-2.944.057-7.652 3.158-9.802 5.97-2.317 3.03-1.824 4.933-1.469 7.546.403 2.96 2.067 3.907 3.397 4.303 1.914 2.529 3.949 2.421 7.366 2.421 6.672 0 9.24-4.913 8.884-11.106-.267-2.981-3.848-5.34-8.688-6.906z"/><path fill="#FFDC5D" d="M29.547 13.273c-.646-.894-1.472-1.614-3.284-1.868.68.311 1.725 1.727 1.81 2.321.085.595.082 1.75-.386 1.099-2.049-2.851-4.878-2.401-7.204-3.856-1.624-1.016-2.119-2.141-2.119-2.141s-.198 1.5-2.661 3.029c-.714.443-1.566 1.43-2.038 2.888-.34 1.048-.234 1.982-.234 3.578 0 4.66 3.841 8.578 8.578 8.578s8.578-3.953 8.578-8.578c-.001-2.898-.304-4.031-1.04-5.05z"/><path fill="#C1694F" d="M22.961 20.706h-1.906c-.263 0-.477-.213-.477-.477s.213-.477.477-.477h1.906c.263 0 .477.213.477.477s-.213.477-.477.477z"/><path fill="#662113" d="M18.195 17.37c-.526 0-.953-.427-.953-.953v-.953c0-.526.427-.953.953-.953s.953.427.953.953v.953c0 .527-.426.953-.953.953zm7.626 0c-.526 0-.953-.427-.953-.953v-.953c0-.526.427-.953.953-.953s.953.427.953.953v.953c0 .527-.427.953-.953.953z"/><path fill="#C1694F" d="M22.134 24.686c-2.754 0-3.6-.705-3.741-.848-.256-.256-.256-.671 0-.927.248-.248.646-.255.902-.023.052.037.721.487 2.839.487 2.2 0 2.836-.485 2.842-.49.256-.255.657-.243.913.015.256.256.242.683-.014.938-.142.143-.987.848-3.741.848"/><path fill="#CCD6DD" d="M31.178 12S28.556 13 22 13s-9.178-1-9.178-1V6h18.355v6z"/><path fill="#99AAB5" d="M16 11.25c0 .276-.224.5-.5.5s-.5-.224-.5-.5v-3c0-.276.224-.5.5-.5s.5.224.5.5v3zm13 0c0 .276-.224.5-.5.5s-.5-.224-.5-.5v-3c0-.276.224-.5.5-.5s.5.224.5.5v3zm-10 .4c0 .276-.224.5-.5.5s-.5-.224-.5-.5v-3c0-.276.224-.5.5-.5s.5.224.5.5v3zm3.5.1c0 .276-.224.5-.5.5s-.5-.224-.5-.5v-3c0-.276.224-.5.5-.5s.5.224.5.5v3zm3.5-.1c0 .276-.224.5-.5.5s-.5-.224-.5-.5v-3c0-.276.224-.5.5-.5s.5.224.5.5v3z"/><path fill="#E1E8ED" d="M28.25 0c-1.157 0-2.237.32-3.167.869C24.153.32 23.073 0 21.916 0c-1.122 0-2.173.3-3.083.818C17.923.3 16.872 0 15.75 0 12.298 0 9.5 2.798 9.5 6.25S20.794 9.5 21.916 9.5c1.157 0 12.584.202 12.584-3.25S31.701 0 28.25 0z"/><path fill="#99AAB5" d="M10 24.75C10 28.202 7.985 30 5.5 30S1 28.202 1 24.75s2.015-7.25 4.5-7.25 4.5 3.798 4.5 7.25z"/><path fill="#99AAB5" d="M4.5 28h2v8h-2z"/><path fill="#C1694F" d="M10 24.75C10 28.202 7.985 30 5.5 30S1 28.202 1 24.75s2.015-7.25 4.5-7.25 4.5 3.798 4.5 7.25z"/><path fill="#C1694F" d="M4.5 28h2v8h-2z"/><path fill="#CCD6DD" d="M14.894 4.489c.036.008.071.011.106.011.231 0 .438-.16.488-.394.031-.145.334-1.441 1.319-2.212.76-.595 1.749-.987 2.007-1.084-.396-.225-.824-.4-1.268-.535-.423.21-.914.486-1.355.832-1.236.967-1.617 2.492-1.681 2.788-.057.27.115.536.384.594zm12.157-2.586c.986.772 1.289 2.066 1.319 2.211.051.235.258.395.489.395.034 0 .069-.003.105-.011.27-.058.441-.324.384-.594-.064-.296-.443-1.82-1.681-2.789-.426-.333-.896-.602-1.309-.809-.434.139-.851.317-1.237.542.337.132 1.227.504 1.93 1.055z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f9d2.svg b/games/sound-soup/assets/img/1f9d2.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#FFAC33" d="M17.119 5.604c-7.604 0-11.421 4.937-12.313 8.588-.653 2.673.337 5.794.639 9.208.267 3.03 2.033 5.574 3.843 6.089 2.603 3.284 5.37 3.143 10.017 3.143 9.073 0 12.472-6.782 12.472-14.579-.001-5.96-4.203-12.449-14.658-12.449z"/><path fill="#FFDC5D" d="M28.259 18.69c-.878-1.216-2.002-2.194-4.466-2.541.924.423 1.809 1.886 1.925 2.695.116.809.231 1.463-.5.655-2.931-3.239-6.122-1.964-9.284-3.942-2.209-1.382-2.882-2.911-2.882-2.911s-.269 2.04-3.619 4.12c-.971.603-2.129 1.945-2.772 3.927-.463 1.424-.411 2.307-.297 3.31C7.546 30.393 11.887 34 18 34c6.107 0 10.442-2.887 11.628-9.573.022-.288.045-.576.045-.869 0-2.501-.413-3.482-1.414-4.868z"/><path fill="#C1694F" d="M22.707 26.293c-.387-.387-.93-.192-1.404-.01-.474.183-1.553.717-3.303.717s-2.829-.534-3.303-.717c-.474-.183-1.017-.377-1.404.01-.391.391-.391 1.023 0 1.414C13.425 27.84 15.229 29 18 29s4.575-1.16 4.707-1.293c.391-.391.391-1.023 0-1.414zM19 25h-2c-.552 0-1-.447-1-1s.448-1 1-1h2c.553 0 1 .447 1 1s-.447 1-1 1z"/><path fill="#662113" d="M14 23c-.552 0-1-.447-1-1v-1c0-.552.448-1 1-1s1 .448 1 1v1c0 .553-.448 1-1 1zm8 0c-.553 0-1-.447-1-1v-1c0-.552.447-1 1-1s1 .448 1 1v1c0 .553-.447 1-1 1z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f9dc-200d-2640-fe0f.svg b/games/sound-soup/assets/img/1f9dc-200d-2640-fe0f.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#2CB4CD" d="M20.716 11.182c-.994-1.383-1.654-5.509-1.654-5.509-.155-2.715-2.494-5.572-5.571-5.572-1.173 0-2.26.365-3.159.985-.182-.027-.365-.056-.555-.056-2.051 0-3.714 1.663-3.714 3.714 0 0-.73 2.881-1.521 3.797.377-.223.706-.465.997-.724-.259 1.306-.788 3.586-1.478 4.547.765-.55 1.41-1.136 1.859-1.795-.021.647-.399 1.267-.837 3.348-.458 2.177.364 2.197 1.385 2.646 1.795.789 12.847 1.296 13.281-.969.513-2.675.068-4.112-.425-5.624.391.429.86.83 1.392 1.212z"/><path fill="#FFDC5D" d="M16.125 13.384s-2.26-.137-3.764-.137-2.959.1-3.392.137l-1.963 3.619.526.283s1.209 4.354 1.209 5.202c0 .848-1.209 5.202-1.209 5.202h9.674s-1.209-4.075-1.209-5.202 1.209-5.202 1.209-5.202l.495-.266-1.576-3.636z"/><path fill="#DD415D" d="M12.369 17.286s-1.209-1.301-2.418-1.301-2.418 1.301-2.418 1.301l.983 3.902h7.681l1.009-3.902s-1.209-1.301-2.418-1.301-2.419 1.301-2.419 1.301z"/><path fill="#316116" d="M28.744 25.545c1.738-2.783-.967-7.196-.967-7.196-.067.534-.348 1.714-.348 1.714-.163.998-.678 2.566-2.219 4.469-1.049 1.294 4.467 5.504 5.708 4.687 1.937-1.276 1.587-3.432 3.655-5.726.367-.407.8-.817 1.346-1.228-3.575-.319-7.175 3.28-7.175 3.28zm-3.123 3.078c-4.478 3.618-9.355 2.292-10.385 1.38l-7.644-5.978c-5.516 0-4.48 7.846 1.132 10.349 9.576 4.271 17.103-1.423 18.399-4.18l-1.502-1.571z"/><path fill="#66A644" d="M27.814 26.878c1.408-2.155.357-5.346-.385-6.815-.163.998-.843 2.383-2.384 4.286-1.049 1.294-.483 3.302-.021 4.199l.001.001c-3.596 2.842-7.065 2.264-8.825 1.464 1.17-.542 1.986-1.7 1.986-3.053 0-1.87-1.547-3.385-3.454-3.385h-7.14c-5.516 0-4.48 7.39 1.132 9.843 9.165 4.007 17-.67 18.793-3.448.863.096 2.363.056 3.573-.741 1.937-1.276 1.414-3.441 3.482-5.734-3.483.249-6.758 3.383-6.758 3.383z"/><path fill="#316116" d="M16.212 30.014c-2.309-.46-6.994-2.625-7.193-2.717-.194-.09-.278-.319-.188-.512.09-.195.321-.28.513-.188.048.022 5.488 2.428 7.707 2.869 0 0-.158.133-.336.267-.179.132-.503.281-.503.281z"/><path fill="#FFDC5D" d="M7.399 17.278c-1.878 1.035-3.24 2.455-3.336 2.617-.239.404-.437 1.592-.276 2.415.145.741.177 2.238.66 2.915.198.277.107.759-.032 1.208-.419 1.353.306 3.199-.145 2.882-.918-.644-1.074-1.83-1.047-2.528.015-.393-.166-.884-.257-1.138-.059-.16-1.245-3.203-1.518-4.54-.148-.722-.333-1.302.275-2.155.903-1.266 2.094-2.996 3.311-3.885 2.012-1.471 3.936-1.687 3.936-1.687s-.911 3.532-1.571 3.896zm23-6.626c-.084.009-.143.102-.203.238-.023-.164-.074-.276-.184-.264-.129.013-.201.22-.304.492-.027-.047-.062-.079-.117-.078-.261.009-.349.811-.909 1.334-.081.036-.154.078-.248.104-.059.016-.098.041-.146.061.005-.255.171-.405.271-.474.239-.166.089-.586-.314-.331-.289.183-.41.365-.501.55-.255.519-.354 1.058-.333 1.487l-.119.215c-.139.242-1.626 1.17-2.696 1.967-1.066.793-1.846 1.694-2.415 1.132-.396-.391-1.591-1.706-3.322-2.833-1.093-.712-2.733-.866-2.733-.866-.807.662.166 3.109.894 3.566.938.589 4.26 2.925 5.306 3.391s1.704-.161 2.196-.609c.403-.366 3.434-4.072 3.703-4.326.365-.344 1.063-.797 1.457-1.491.338-.596.543-1.079.667-1.855.071-.456.402-1.447.05-1.41zM10.706 9.387v3.714c0 1.026.831 1.857 1.857 1.857s1.857-.831 1.857-1.857V9.387h-3.714z"/><path fill="#F9CA55" d="M14.419 9.387l-3.714.017v3.697s1.857.928 3.714 0V9.387z"/><path fill="#FFDC5D" d="M17.205 2.934H7.92V8.48c0 2.553 2.078 4.621 4.642 4.621 2.564 0 4.643-2.068 4.643-4.621V2.934z"/><path fill="#DF1F32" d="M14.419 11.244h-3.714s0 .928 1.857.928 1.857-.928 1.857-.928z"/><circle fill="#662113" cx="10.241" cy="7.994" r=".464"/><circle fill="#662113" cx="14.884" cy="7.994" r=".464"/><path fill="#C1694F" d="M11.634 9.387h1.857s0 .928-.929.928c-.928 0-.928-.928-.928-.928z"/><path fill="#2CB4CD" d="M18.133 6.601l-.613-4.114H6.992v4.114c3.674 0 3.713-3.628 3.714-3.707.001.08.08 3.707 7.427 3.707z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f9de.svg b/games/sound-soup/assets/img/1f9de.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#88C9F9" d="M32 36v-2c0-3.313-2.687-6-6-6H10c-3.313 0-6 2.687-6 6v2h28z"/><path fill="#744EAA" d="M25.987 28l-2.016-.05s.762 6.816-5.977 6.816-5.977-6.816-5.977-6.816L10 28c-1.229 2.076-1.016 8-1.016 8h18.018c.001 0 .214-5.924-1.015-8z"/><path fill="#FFCC4D" d="M18.005 32.04c4.09 0 5.453-4.09 5.453-4.09h1.363s0 6.816-6.816 6.816-6.816-6.816-6.816-6.816h1.363s1.364 4.09 5.453 4.09z"/><path fill="#88C9F9" d="M14 28.267l4 1.88 4-1.88V23.97h-8z"/><path fill="#539AE0" d="M14 26.242c1.216 1.374 2.355 1.817 3.996 1.817 1.639 0 2.787-.602 4.004-1.975v-4h-8v4.158z"/><path fill="#292F33" d="M21.597 3.6c-1.925-.623-6.455-.453-7.588 1.019-2.944.057-6.398 2.718-6.851 6.228-.448 3.475.551 5.088.906 7.701.403 2.96 2.067 3.907 3.397 4.303 1.914 2.529 3.949 2.421 7.366 2.421 6.672 0 9.85-4.464 10.131-12.047.17-4.585-2.521-8.059-7.361-9.625z"/><path fill="#88C9F9" d="M25.413 13.55c-.646-.894-1.472-1.614-3.284-1.868.68.311 1.331 1.387 1.416 1.982.085.595.17 1.076-.368.481-2.155-2.382-4.502-1.444-6.827-2.899-1.624-1.016-2.119-2.141-2.119-2.141s-.198 1.5-2.661 3.029c-.714.443-1.566 1.43-2.038 2.888-.34 1.048-.234 1.982-.234 3.578 0 4.66 3.841 8.578 8.578 8.578s8.578-3.953 8.578-8.578c-.001-2.898-.305-4.031-1.041-5.05z"/><path fill="#55ACEE" d="M18.827 20.983h-1.906c-.263 0-.477-.213-.477-.477s.213-.477.477-.477h1.906c.263 0 .477.213.477.477s-.213.477-.477.477z"/><path fill="#662113" d="M14.062 17.647c-.526 0-.953-.427-.953-.953v-.953c0-.526.427-.953.953-.953s.953.427.953.953v.953c0 .527-.427.953-.953.953zm7.625 0c-.526 0-.953-.427-.953-.953v-.953c0-.526.427-.953.953-.953s.953.427.953.953v.953c0 .527-.427.953-.953.953z"/><path fill="#269" d="M18 24.963c-2.754 0-3.6-.705-3.741-.848-.256-.256-.256-.671 0-.927.248-.248.646-.255.902-.023.052.037.721.487 2.839.487 2.2 0 2.836-.485 2.842-.49.256-.255.657-.243.913.015.256.256.242.683-.014.939-.141.142-.987.847-3.741.847"/><path fill="#744EAA" d="M18.105 8.739s9.223 2.397 10.636 6.713c.616-1.318.974-4.201.974-5.829 0-4.952-3.87-8.539-10.159-8.539h-2.903c-5.321 0-10.159 3.588-10.159 8.539 0 1.628.358 4.726.973 6.043 1.415-4.318 10.638-6.927 10.638-6.927z"/><path fill="#CBB7EA" d="M29.696 10.342c-1.801-1.439-3.926-2.627-5.95-3.301-.471-.157-.974.098-1.13.564-.156.468.097.974.564 1.13 2.149.716 4.508 2.15 6.298 3.787.117-.805.196-1.714.218-2.18zm-23.05 1.463c2.589-1.852 11.253-7.429 21.153-7.506-.61-.703-1.269-1.209-2.037-1.687-8.651.642-16.031 4.952-19.26 7.122.012.587.059 1.307.144 2.071z"/><path fill="#CBB7EA" d="M28.968 6.102c-.037-.005-.108 0-.146 0-3.657 0-7.357.916-11.001 2.719.18-.054.284-.082.284-.082s1.052.273 2.498.801c2.743-1.09 5.499-1.652 8.219-1.652.262 0 .521.018.739.068-.096-.541-.319-1.305-.593-1.854zM6.737 7.58c3.195-2.046 9.161-4.995 15.921-6.172-1.026-.304-1.994-.324-3.102-.324H17.28c-3.515 1.366-6.647 2.395-9.059 3.724-.691.875-1.205 1.696-1.484 2.772z"/><path fill="#FFD983" d="M17.782 4.808h.935c.887 0 1.612.726 1.612 1.612v3.065c0 .887-.726 1.612-1.612 1.612h-.935c-.887 0-1.612-.726-1.612-1.612V6.42c0-.886.726-1.612 1.612-1.612z"/><path fill="#FFAC33" d="M17.995 6.102h.511c.484 0 .881.396.881.881v1.94c0 .484-.396.881-.881.881h-.511c-.484 0-.881-.396-.881-.881v-1.94c0-.484.396-.881.881-.881z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f9e2.svg b/games/sound-soup/assets/img/1f9e2.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><ellipse transform="rotate(-87.495 21.25 5.018)" fill="#2B7BB9" cx="21.249" cy="5.018" rx=".944" ry="1.565"/><path fill="#292F33" d="M29.831 27.74s3.523 1.385 5.185.088c.125-1.17-3.311-2.035-3.311-2.035l-1.874 1.947zM7.527 25.549S2.271 33.375.77 32.031c0 0-.425-1.397 1.23-4.218 1.656-2.822 5.527-2.264 5.527-2.264z"/><path fill="#1C6399" d="M19.766 4.82s-8.537.43-13.735 16.348c7.494 0 16.785.555 16.785.555s7.799 3.982 8.889 4.469c1.089.487 3.311 1.637 3.311 1.637s1.089-5.531.305-9.69S30.79 4.997 19.766 4.82z"/><path fill="#1C6399" d="M3.354 25.167C1.521 28.209.138 30.988.77 32.031c0 0-.203-.761.775-1.633.892-.795 2.805-1.522 6.461-1.522 5.534 0 13.006 4.498 16.119 5.562 2.375.812 2.875.188 4.188-1.25 1.4-1.534 3.716-6.904 3.716-6.904s-7.47-4.107-11.871-5.726-5.358-1.427-6.752-1.401c-3.056.057-5.314.671-7.375 2.011 0 0-1.494 2.036-2.677 3.999z"/><path fill="#50A5E6" d="M30.588 11.339c-.61-3.443-4.011-5.076-4.011-5.076-1.895-.883-4.158-1.448-6.864-1.491 0 0-12.192-.105-13.681 16.395 0 0 2.541-1.115 7.92-.793 7.299.438 14.414 4.117 15.986 4.812.83-2.779 1.367-9.798.65-13.847z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f9e5.svg b/games/sound-soup/assets/img/1f9e5.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#55ACEE" d="M30 9c-1-2-5-4-7-4 0 0 1.194-2.174-1-3-.987-.372-2-.5-4-.5-1.615 0-3.013.128-4 .5-2.194.826-1 3-1 3-2 0-6 2-7 4S1 30.969 1 34.5c0 1 4.632 1.43 5 .5.791-2 3-16 3-16v15c0 2 5 1 9 1s9 1 9-1V19s2.525 15.12 3 16c.499.923 5 .5 5-.5C35 30.969 31 11 30 9z"/><path fill="#269" d="M14 3c0-.552 0-1 4-1s4 .448 4 1-1.791 2-4 2-4-1.448-4-2z"/><path fill="#3B88C3" d="M18 34.5c-.276 0-.5-.224-.5-.5V6c0-.276.224-.5.5-.5s.5.224.5.5v28c0 .276-.224.5-.5.5zm-4.375-.972c-1.794 0-3.564-.177-3.675-.188-.275-.027-.475-.272-.447-.548.028-.273.273-.479.548-.446.041.003 4.044.401 5.985.007.272-.055.535.119.59.391.055.271-.119.534-.39.59-.72.145-1.669.194-2.611.194zm-.97-3.084c-1.378 0-2.627-.079-2.881-.096-.275-.019-.484-.257-.465-.532.019-.275.246-.49.533-.466 1.323.089 5.002.223 6.109-.219.257-.102.547.021.65.278.103.257-.022.548-.279.649-.763.306-2.28.386-3.667.386zm1.217-3.179c-1.65 0-3.551-.143-4.105-.188-.275-.022-.48-.264-.458-.538.023-.275.268-.479.539-.458 2.574.208 5.549.275 6.072.024.25-.119.548-.014.667.235s.014.548-.235.667c-.409.196-1.39.258-2.48.258zm-.039-3.09c-1.77 0-3.75-.208-4.08-.244-.274-.03-.473-.276-.443-.552.029-.274.272-.478.551-.442 1.344.147 5.059.438 6.091.032.255-.104.547.025.649.281.101.258-.025.548-.282.648-.527.208-1.472.277-2.486.277zm.047-3.524c-1.753 0-3.767-.112-4.102-.132-.276-.016-.486-.252-.47-.528.016-.275.249-.505.528-.47 1.382.08 5.187.237 6.189.012.27-.062.537.107.598.377.061.27-.108.537-.378.598-.475.106-1.379.143-2.365.143zm-4.073-3.276c-.27 0-.492-.371-.5-.642-.007-.276.211-.584.487-.591l6.328-.205c.246.011.506.191.513.467.007.276-.211.652-.487.659l-6.328.312h-.013zM16.552 7h-.024c-.973 0-2.588-.904-2.769-1.004-.242-.133-.331-.429-.197-.671.133-.242.438-.325.679-.194.606.333 1.775.872 2.334.898.276.013.489.233.476.508-.012.269-.234.463-.499.463zm10.02 5.784c-.104 0-.21-.033-.3-.1-.221-.166-.266-.479-.101-.7l2.293-3.068c.167-.222.48-.264.7-.1.221.166.266.479.101.7l-2.293 3.068c-.098.131-.248.2-.4.2z"/><path fill="#3B88C3" d="M18.044 34.5c-.276 0-.5-.224-.5-.5V6c0-.276.224-.5.5-.5s.5.224.5.5v28c0 .276-.224.5-.5.5zm9.473-18.76c-.209 0-.404-.132-.475-.342-.087-.262.055-.545.316-.632l3-1c.262-.087.545.054.633.316.087.262-.055.545-.316.632l-3 1c-.053.018-.106.026-.158.026zm.612 3.481c-.209 0-.404-.133-.475-.342-.087-.262.055-.545.316-.633l3-1c.263-.084.545.054.633.316.087.262-.055.545-.316.632l-3 1c-.053.019-.105.027-.158.027zm.576 3.485c-.209 0-.404-.133-.475-.342-.087-.262.055-.545.316-.633l3-1c.262-.087.545.055.633.316.087.262-.055.545-.316.633l-3 1c-.052.019-.105.026-.158.026zm.642 3.488c-.209-.003-.402-.139-.469-.349-.083-.263.063-.544.326-.628l3.014-.955c.263-.082.544.063.628.326.083.263-.063.544-.326.628l-3.014.955c-.054.017-.107.024-.159.023zm.594 3.405c-.208-.017-.392-.165-.445-.379-.065-.268.099-.539.367-.605l3.071-.754c.269-.065.539.099.605.367.065.268-.099.539-.367.605l-3.071.754c-.054.013-.107.017-.16.012zm.577 3.439c-.208-.02-.39-.171-.44-.386-.061-.269.107-.537.376-.599l3.082-.706c.269-.061.537.107.599.376.061.269-.107.537-.376.599l-3.082.706c-.053.013-.106.015-.159.01zm-8.099.49c-.942 0-1.891-.049-2.61-.195-.271-.056-.445-.319-.391-.59.056-.271.324-.446.59-.391 1.943.399 5.946-.003 5.985-.007.271-.032.521.171.548.446.028.275-.172.521-.446.548-.113.012-1.882.189-3.676.189zm.971-3.084c-1.388 0-2.904-.08-3.667-.385-.257-.102-.381-.393-.278-.649.103-.256.395-.383.649-.278 1.108.441 4.786.309 6.109.219.248-.017.514.189.532.466.019.275-.189.514-.466.532-.253.016-1.502.095-2.879.095zm-1.218-3.179c-1.091 0-2.071-.062-2.479-.258-.249-.119-.354-.418-.235-.667.119-.249.419-.353.667-.235.524.251 3.497.185 6.072-.024.271-.021.517.183.538.458.022.274-.183.516-.458.538-.553.045-2.455.188-4.105.188zm.038-3.09c-1.014 0-1.958-.068-2.485-.276-.257-.101-.383-.391-.281-.648.102-.257.392-.385.648-.281 1.031.404 4.746.115 6.09-.032.271-.03.521.167.552.442.03.275-.168.521-.442.552-.332.035-2.312.243-4.082.243zm-.064-3.524c1.753 0 3.767-.112 4.102-.132.276-.016.486-.252.47-.528-.016-.275-.249-.505-.528-.47-1.382.08-5.187.237-6.189.012-.269-.062-.537.107-.598.377-.061.27.108.537.378.598.476.106 1.38.143 2.365.143zm4.09-3.276h-.013l-6.328-.322c-.276-.007-.494-.315-.487-.591.007-.275.251-.511.513-.526l6.328.147c.276.007.494.383.487.659-.007.272-.229.633-.5.633zm-6.193-3.625c-.253 0-.47-.191-.496-.449-.028-.274.171-.52.445-.549l4.818-.499c.267-.028.521.171.549.446.028.274-.171.52-.445.549l-4.818.499-.053.003zm.084-3.547c-.197 0-.385-.118-.463-.312-.104-.256.02-.547.275-.651 1.6-.648 6.095-.93 6.307-.942.312-.025.511.199.524.474.015.276-.197.511-.474.525-1.393.072-4.783.383-5.982.869-.061.025-.125.037-.187.037zM9.496 12.784c.104 0 .21-.033.3-.1.221-.166.266-.479.101-.7L7.604 8.916c-.167-.222-.48-.264-.7-.1-.221.166-.266.479-.1.7l2.293 3.068c.097.131.248.2.399.2zm-.944 2.956c.209 0 .404-.132.475-.342.087-.262-.055-.545-.316-.632l-3-1c-.262-.087-.545.054-.633.316-.087.262.055.545.316.632l3 1c.052.018.105.026.158.026zm-.612 3.481c.209 0 .404-.133.475-.342.087-.262-.055-.545-.316-.633l-3-1c-.263-.084-.545.054-.633.316-.087.262.055.545.316.632l3 1c.052.019.105.027.158.027zm-.577 3.485c.209 0 .404-.133.475-.342.087-.262-.055-.545-.316-.633l-3-1c-.262-.087-.545.055-.633.316-.087.262.055.545.316.633l3 1c.053.019.106.026.158.026zm-.641 3.488c.209-.003.402-.139.469-.349.083-.263-.063-.544-.326-.628l-3.014-.955c-.263-.082-.544.063-.628.326-.083.263.063.544.326.628l3.014.955c.053.017.106.024.159.023zm-.595 3.405c.208-.017.392-.165.445-.379.065-.268-.099-.539-.367-.605l-3.071-.754c-.269-.065-.539.099-.605.367-.065.268.099.539.367.605l3.071.754c.055.013.108.017.16.012zm-.577 3.439c.208-.02.39-.171.44-.386.061-.269-.107-.537-.376-.599l-3.082-.706c-.269-.061-.537.107-.599.376-.061.269.107.537.376.599l3.082.706c.053.013.107.015.159.01zM16.025 13.75c.253 0 .47-.191.496-.449.028-.274-.171-.52-.445-.549l-4.818-.499c-.267-.028-.521.171-.549.446-.028.274.171.52.445.549l4.818.499.053.003zm-.083-3.547c.197 0 .385-.118.463-.312.104-.256-.02-.547-.275-.651-1.6-.648-6.095-.93-6.307-.942-.313-.026-.511.199-.525.473-.015.276.197.512.474.526 1.394.072 4.783.383 5.982.869.062.025.125.037.188.037zM19.492 7c-.266 0-.487-.193-.5-.461-.013-.276.201-.502.476-.515.561-.026 1.729-.56 2.334-.892.243-.132.548-.044.68.199.133.242.044.532-.197.664-.181.1-1.797 1.005-2.769 1.005h-.024z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f9e6.svg b/games/sound-soup/assets/img/1f9e6.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#55ACEE" d="M27 19.276c-.983 0-1.893.294-2.667.784v4.549h-2.3c.175 2.603 2.319 4.667 4.966 4.667 2.761 0 5-2.239 5-5v-5H27z"/><path fill="#1C6399" d="M22.995 22.999l.005-16 9 .003-.005 16z"/><path fill="#1C6399" d="M24.202 21.451l6.354 6.354-6.878 6.878-6.354-6.354z"/><path fill="#55ACEE" d="M30 4h-5c-1.1 0-2 .9-2 2v2h9V6c0-1.1-.9-2-2-2zM15 15.276c-.983 0-1.893.294-2.667.784v4.549h-2.3c.175 2.603 2.319 4.667 4.966 4.667 2.761 0 5-2.239 5-5v-5H15z"/><path fill="#1C6399" d="M10.994 18.999l.006-16 9 .003-.006 16z"/><path fill="#55ACEE" d="M23.676 34.682c-1.757 1.757-4.607 1.757-6.364 0s-1.757-4.607 0-6.364c0 0 1.149.133 3.69 2.674s2.674 3.69 2.674 3.69zM18 0h-5c-1.1 0-2 .9-2 2v2h9V2c0-1.1-.9-2-2-2z"/><path fill="#1C6399" d="M12.202 17.451l6.354 6.354-6.878 6.878-6.354-6.354z"/><path fill="#55ACEE" d="M11.676 30.682c-1.757 1.757-4.607 1.757-6.364 0s-1.757-4.607 0-6.364c0 0 1.149.133 3.69 2.674s2.674 3.69 2.674 3.69z"/><path fill="#FFCC4D" d="M22.994 10.067h9v2h-9zm0 4h9v2h-9zm-12-8h9v2h-9zm0 4h9v2h-9z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f9ed.svg b/games/sound-soup/assets/img/1f9ed.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><circle fill="#F4900C" cx="18" cy="18" r="18"/><circle fill="#FFD983" cx="18" cy="18" r="14.5"/><circle fill="#F5F8FA" cx="18" cy="18" r="13"/><path fill="#CCD6DD" d="M18 8l1.531 6.304 5.54-3.375-3.375 5.54L28 18l-6.304 1.531 3.375 5.54-5.54-3.375L18 28l-1.531-6.304-5.54 3.375 3.375-5.54L8 18l6.304-1.531-3.375-5.54 5.54 3.375z"/><path fill="#292F33" d="M17.343 20.748l8.777 5.381-5.379-8.778z"/><path fill="#DD2E44" d="M18.657 15.267L9.879 9.886l5.38 8.779z"/><circle fill="#8899A6" cx="18" cy="18.008" r="3.055"/><circle fill="#F5F8FA" cx="18" cy="18.008" r="1.648"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f9f1.svg b/games/sound-soup/assets/img/1f9f1.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#C1694F" d="M35.627 10.826L.373 16.56v9.722h.004c-.012.149.129.295.443.473.727.412 9.835 5.286 10.553 5.639.895.439 1.441.493 2.206.112.626-.312 19.236-10.173 20.932-11.074.863-.459 1.146-.711 1.099-.919h.018v-9.687z"/><path fill="#A0041E" d="M.373 16.56v9.722h.004c-.012.149.129.295.443.473.727.412 9.835 5.286 10.553 5.639.432.212.781.329 1.114.356V16.56H.373z"/><path fill="#D99E82" d="M22.224 4.682c1.076-.558 1.113-.628 2.249-.024 1.136.604 9.846 5.248 10.487 5.589.814.434 1.112.591-.449 1.421-1.696.902-20.306 10.763-20.932 11.074-.765.381-1.311.327-2.206-.112-.718-.352-9.826-5.226-10.553-5.639-.82-.465-.482-.706.769-1.37L22.224 4.682z"/><ellipse fill="#C1694F" cx="11.189" cy="17.191" rx="3.679" ry="1.84"/><path fill="#A0041E" d="M11.642 16.734c-1.569-.516-3.352-.369-4.108.339-.005.04-.024.078-.024.118 0 1.016 1.647 1.84 3.679 1.84 1.063 0 2.013-.229 2.684-.589-.333-.692-1.086-1.332-2.231-1.708z"/><ellipse fill="#C1694F" cx="18" cy="13.639" rx="3.679" ry="1.84"/><path fill="#A0041E" d="M18.453 13.182c-1.569-.516-3.352-.369-4.108.339-.005.04-.024.078-.024.118 0 1.016 1.647 1.84 3.679 1.84 1.063 0 2.013-.229 2.684-.589-.333-.692-1.086-1.332-2.231-1.708z"/><ellipse fill="#C1694F" cx="24.811" cy="10.087" rx="3.679" ry="1.84"/><path fill="#A0041E" d="M25.264 9.63c-1.569-.516-3.352-.369-4.108.339-.005.04-.024.078-.024.118 0 1.016 1.647 1.84 3.679 1.84 1.063 0 2.012-.229 2.684-.589-.333-.692-1.086-1.332-2.231-1.708z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f9f8.svg b/games/sound-soup/assets/img/1f9f8.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#C1694F" d="M25 22h-.253c.16-.798.253-1.633.253-2.5 0-5.247-3.134-9.5-7-9.5s-7 4.253-7 9.5c0 .867.093 1.702.253 2.5H11c-2 3-1.502 8.056.122 9C18 35 14 29 18 29s0 6 6.878 2c1.624-.944 2.122-6 .122-9z"/><path fill="#C1694F" d="M23.667 20.458c-.177-1.544.317-3.562.255-5.25C22.535 16.292 19.947 17 18 17s-4.51-.708-5.897-1.792c-.062 1.688.407 3.706.23 5.25-.458 4 2.353 7.184 5.667 7.184s6.125-3.184 5.667-7.184z"/><path fill="#C1694F" d="M12.373 14s-5 1-6 7 3.419 6.581 5 5c2-2 0-4 0-4s1-5 1-8zm11.254 0s5 1 6 7-3.419 6.581-5 5c-2-2 0-4 0-4s-1-5-1-8z"/><path fill="#A0041E" d="M13 13c-2 0-1 1-1 2s1 3 3 2c0 0-1 2-1 5 0 0 2 0 3 2 0 0 1-7 1-9s-4-2-5-2zm10 0c2 0 1 1 1 2s-1 3-3 2c0 0 1 2 1 5 0 0-2 0-3 2 0 0-1-7-1-9s4-2 5-2z"/><circle fill="#C1694F" cx="11" cy="4" r="4"/><circle fill="#662113" cx="11" cy="4" r="2"/><circle fill="#C1694F" cx="25" cy="4" r="4"/><circle fill="#662113" cx="25" cy="4" r="2"/><ellipse fill="#C1694F" cx="18" cy="8.5" rx="8" ry="7.5"/><circle fill="#292F33" cx="15" cy="8" r="1"/><circle fill="#292F33" cx="21" cy="8" r="1"/><path fill="#D99E82" d="M18.058 9.563c-6.808 0-4.612 5.562-.147 5.562 4.464 0 6.955-5.562.147-5.562z"/><path fill="#292F33" d="M16.737 11.065l.526.911c.328.567 1.147.567 1.474 0l.526-.911c.328-.567-.082-1.277-.737-1.277h-1.052c-.655 0-1.064.709-.737 1.277z"/><path fill="#934035" d="M11.265 27.002c-.166 0-.327-.082-.422-.231-.148-.233-.08-.542.153-.69.04-.025 1.027-.688.997-2.022-.023-.991-.933-1.641-.942-1.646-.121-.085-.2-.221-.213-.368-.205-2.36.65-4.709.687-4.809.096-.258.381-.391.642-.296.259.096.392.383.296.642-.008.021-.761 2.099-.644 4.165.375.322 1.146 1.124 1.174 2.289.044 1.91-1.398 2.851-1.459 2.89-.085.051-.178.076-.269.076zm13.471 0c-.092 0-.186-.025-.269-.078-.062-.039-1.504-.979-1.46-2.89.027-1.165.799-1.967 1.174-2.289.118-2.072-.636-4.143-.644-4.164-.096-.259.036-.547.296-.643.257-.095.546.037.642.296.037.099.893 2.448.688 4.809-.013.149-.092.284-.215.369-.008.005-.918.654-.94 1.646-.031 1.353.986 2.016.997 2.022.232.148.302.457.153.69-.096.15-.257.232-.422.232zM24.665 22h.01-.01z"/><ellipse transform="rotate(-45.001 11.121 30.5)" fill="#C1694F" cx="11.122" cy="30.5" rx="4.5" ry="5"/><path fill="#D99E82" d="M13.349 33.227c-1.054 1.054-2.906.912-4.137-.318-1.23-1.23-1.373-3.082-.318-4.137 1.054-1.054 2.906-.912 4.137.318 1.23 1.231 1.372 3.083.318 4.137z"/><path fill="#D99E82" d="M12.889 32.768c-.781.781-2.206.623-3.182-.354-.976-.976-1.135-2.401-.354-3.182.781-.781 2.206-.623 3.182.354s1.135 2.401.354 3.182z"/><ellipse transform="rotate(-45.001 24.878 30.5)" fill="#C1694F" cx="24.878" cy="30.5" rx="5" ry="4.5"/><path fill="#D99E82" d="M22.651 33.227c1.054 1.054 2.906.912 4.137-.318 1.23-1.23 1.373-3.082.318-4.137-1.054-1.054-2.906-.912-4.137.318-1.23 1.231-1.372 3.083-.318 4.137z"/><ellipse transform="rotate(-45.001 24.878 31)" fill="#D99E82" cx="24.878" cy="31" rx="2.5" ry="2"/><path fill="#292F33" d="M18.087 14.138c-1.28 0-2.249-.947-2.264-.961-.098-.098-.098-.255 0-.353s.255-.098.353-.001c.075.074 1.849 1.797 3.647 0 .098-.098.256-.098.354 0s.098.256 0 .354c-.721.721-1.445.961-2.09.961z"/><path fill="#934035" d="M15.95 29.727c-.185 0-.362-.103-.449-.28-2.274-4.635-6.795-3.15-6.986-3.086-.262.091-.545-.049-.635-.311-.09-.261.048-.544.309-.635.055-.02 5.543-1.845 8.21 3.592.122.247.02.547-.229.669-.071.034-.147.051-.22.051zm4.101 0c-.065 0-.132-.013-.195-.04-.255-.108-.373-.401-.265-.655 2.255-5.301 8.141-3.641 8.198-3.623.265.077.416.354.339.619-.077.267-.362.414-.619.341-.207-.061-5.096-1.42-6.998 3.054-.081.189-.266.304-.46.304z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1f9fc.svg b/games/sound-soup/assets/img/1f9fc.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><circle fill="#CCD6DD" cx="28.5" cy="26.5" r="3.5"/><circle fill="#F5F8FA" cx="29.5" cy="25.5" r="2.5"/><circle fill="#CCD6DD" cx="26" cy="30" r="3"/><circle fill="#F5F8FA" cx="26.5" cy="29.5" r="2.5"/><path fill="#EA596E" d="M32.469 8.188s1.156 2.375.531 6.844-2.428 7.734-7.384 12.737c-8.411 8.493-14.321 9.461-19.461 4.271-1.883-1.901-2.627-3.883-2.936-5.977S2.991 21.306 3 21c.034-1.234 1.188-4.667 1.188-4.667l28.281-8.145z"/><path fill="#F4ABBA" d="M10.261 8.358C13.468 5.076 17.79 1.834 21.281 1c3.582-.856 6.319.766 8.484 2.881 1.87 1.827 3.482 4.751 3.141 7.775-.438 3.889-3.512 8.087-7.167 11.827-3.095 3.167-7.228 6.373-10.614 7.267-3.723.982-6.652-.602-8.891-2.79-1.933-1.889-3.565-4.781-3.141-7.929.515-3.813 3.608-8.031 7.168-11.673z"/><path fill="#FFCCD6" d="M11.626 8.375c3.179-3.24 6.801-6.001 10.03-6.531 2.428-.399 4.675.785 6.111 2.193 1.363 1.337 2.642 3.525 2.389 5.807-.368 3.319-3.118 7.05-6.418 10.414-2.682 2.734-6.094 5.501-8.926 6.336-3.049.9-5.481-.297-7.215-1.998-1.618-1.587-2.826-3.994-2.191-6.815.678-3.01 3.345-6.475 6.22-9.406z"/><circle fill="#CCD6DD" cx="9" cy="10" r="3"/><circle fill="#F5F8FA" cx="9.5" cy="9.5" r="2.5"/><circle fill="#CCD6DD" cx="5.5" cy="8.5" r="2.5"/><circle fill="#CCD6DD" cx="12" cy="6" r="3"/><circle fill="#CCD6DD" cx="9" cy="3" r="2"/><circle fill="#CCD6DD" cx="3.5" cy="4.5" r="1.5"/><circle fill="#CCD6DD" cx="18" cy="4" r="2"/><circle fill="#CCD6DD" cx="5" cy="15" r="2"/><circle fill="#CCD6DD" cx="32.5" cy="25.5" r="2.5"/><circle fill="#CCD6DD" cx="33" cy="19" r="2"/><circle fill="#F5F8FA" cx="6" cy="8" r="2"/><circle fill="#F5F8FA" cx="5.5" cy="14.5" r="1.5"/><circle fill="#F5F8FA" cx="12.5" cy="5.5" r="2.5"/><circle fill="#F5F8FA" cx="9.5" cy="2.5" r="1.5"/><circle fill="#F5F8FA" cx="4" cy="4" r="1"/><circle fill="#F5F8FA" cx="18.5" cy="3.5" r="1.5"/><circle fill="#F5F8FA" cx="33" cy="25" r="2"/><circle fill="#F5F8FA" cx="33.5" cy="18.5" r="1.5"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1fa93.svg b/games/sound-soup/assets/img/1fa93.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#F4900C" d="M35.22 30.741l-.024.024c-.97.97-2.542.97-3.511 0L7.835 6.915c-.582-.582-.582-1.525 0-2.107l1.429-1.429c.582-.582 1.525-.582 2.107 0l23.85 23.85c.969.97.969 2.542-.001 3.512z"/><path fill="#66757F" d="M17.765 6.946L14.229 3.41c-.586-.586-1.535-.586-2.121 0L8.573 6.946c-2.128 2.092-3.85 3.015-6.055 3.056-.171 1.573.665 5.193 1.967 6.652 1.692 1.896 4.545 2.495 7.223 2.454-.134-2.363.437-4.422 2.521-6.506l3.535-3.536c.587-.585.587-1.535.001-2.12z"/><path fill="#CCD6DD" d="M2.518 10.002C1.767 10.016.962 9.93.064 9.75c-.707 4.95 7.071 12.728 12.021 12.021-.193-.937-.328-1.819-.376-2.663-4.418-1.409-8.107-5.072-9.191-9.106z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1fa9d.svg b/games/sound-soup/assets/img/1fa9d.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#FFCC4D" d="M19.657 9.885c-2.648 1.447-6.515 9.628-6.355 13.029.159 3.364 1.779 5.653 3.855 6.365s9.767 1.339 8.897-7.516c-.2-2.031 1.398-3.536 1.965-3.404 1.438.336-.767 1.486-.153 3.173.506 1.391 1.126 5.323-1.041 9.135C25.364 33.236 21.855 35 17.262 35c-3.832 0-7.731-2.207-9.128-5.82-1.596-4.127-.697-7.833.396-9.523 1.503-2.323 6.482-8.747 4.325-10.233 3 1 6.802.461 6.802.461z"/><path fill="#FFCC4D" d="M16.5 3.5C17.878 3.5 19 4.622 19 6s-1.122 2.5-2.5 2.5S14 7.378 14 6s1.122-2.5 2.5-2.5m0-2.5c-2.761 0-5 2.239-5 5s2.239 5 5 5 5-2.239 5-5-2.239-5-5-5z"/><path fill="#FFAC33" d="M14.728 10.39s1.339.545 2.628.248c0 0-2.181 2.677-3.272 5.206s-2.417 5.392-1.785 8.726c.545 2.876 2.181 5.9 6.296 6.098s6.594-1.438 7.238-2.677c.645-1.239 1.041-1.19.595.545-.168.653-1.807 4.537-8.153 4.933-4.661.291-8.043-2.335-9.134-6.549s1.412-7.999 2.584-9.757c2.976-4.463 3.003-6.773 3.003-6.773z"/><path fill="#F4900C" d="M15.579 10.629c-.506-.099-.851-.239-.851-.239s-.007.424-.244 1.254c-.208.727-.596 1.768-1.312 3.114.679-1.07 1.318-2.116 1.804-2.978.411.1.867.174 1.364.212.579-.817 1.016-1.354 1.016-1.354-.637.146-1.283.087-1.777-.009zm3.305 20.731c4.336.129 6.525-1.434 7.698-4.131-.09-.228-.375.041-.749.761-.645 1.239-3.123 2.876-7.238 2.677-3.712-.179-5.404-2.658-6.1-5.253l-.026.005c.534 1.961 1.425 5.792 6.415 5.941z"/><path fill="#C1694F" d="M16.5 3.5c1.228 0 2.246.892 2.456 2.06.017.019.036.038.05.058-.019-1.38-.853-2.759-2.601-2.625-1.495.115-2.26 1.351-2.388 2.474.019-.03.046-.056.07-.083.275-1.08 1.248-1.884 2.413-1.884z"/><path fill="#FFD983" d="M19 6c0 1.378-1.122 2.5-2.5 2.5S14 7.378 14 6c-.013-.171-.005-.35.016-.533-.056.088-.096.182-.107.288-.145 1.493.986 3.355 2.896 3.208 1.738-.134 2.408-1.85 2.317-3.074-.007-.096-.054-.185-.116-.271.002.128.006.256-.006.382z"/><path fill="#FFD983" d="M14.086 5.384c-.024.028-.051.054-.07.083-.021.183-.029.362-.016.533 0-.214.035-.418.086-.616zM19 6c.012-.126.008-.254.006-.382-.015-.02-.033-.039-.05-.058.025.144.044.289.044.44z"/><path fill="#FFE8B6" d="M16.36 1.523c1.424-.069 3.202.881 3.698 1.782.49.891-.582.447-.891 0-.401-.579-2.094-.891-2.941-.802s-2.293.704-2.718 1.426c-.446.757-1.184.935-.802 0 .401-.98 1.827-2.317 3.654-2.406z"/><path fill="#C1694F" d="M17.34 34.508c-6.076-.188-8.634-4.288-9.437-5.983.071.217.146.435.231.655C9.532 32.793 13.431 35 17.262 35c4.592 0 8.102-1.764 9.563-4.333.172-.303.321-.606.459-.909-1.262 1.757-3.777 4.941-9.944 4.75z"/><path fill="#FFE8B6" d="M26.726 19.716c.63-.63 1.426-.95.95-.238-.475.713-.772 1.188-.713 1.96.059.772-.594.178-.594-.238.001-.415-.059-1.068.357-1.484z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1faa8.svg b/games/sound-soup/assets/img/1faa8.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#66757F" d="M12 4.157s4-1.075 5-1.075 5 1.075 5 1.075 4 4.298 5 4.298c0 0 2 0 4 2.149s2 6.447 2 6.447 2 4.298 2 6.447-1 4.298-2 5.373c-1 1.075-5 2.149-5 2.149s-1 2.149-3 3.224c-6 1.075-10 0-10 0l-2-2.149S6 31.02 5 31.02s-4-4.298-4-5.373c0-1.075 0-4.298 1-7.522 2-3.224 3-4.298 3-4.298S4 11.679 5 9.53c2-2.149 6-3.224 6-3.224l1-2.149z"/><path fill="#99AAB5" d="M12.409 4.49s3.633-1.021 4.574-1.021c.941 0 4.99 1.149 4.99 1.149s3.574 4.022 4.515 4.022c0 0 2.038-.045 3.921 1.979s1.708 5.617 1.708 5.617S34 20.282 34 22.305s-.941 4.047-1.882 5.059c-.941 1.012-4.706 2.023-4.706 2.023s-.941 2.023-2.824 3.035c-5.647 1.012-9.412 0-9.412 0l-1.882-2.023s-6.588-1.012-7.529-1.012S2 25.34 2 24.328c0-1.012 0-4.047.941-7.082C4.824 14.211 5.765 13.2 5.765 13.2s-1.188-1.643-.247-3.667C7.4 7.51 11.399 6.469 11.399 6.469l1.01-1.979z"/><path fill="#CCD6DD" d="M12.771 5.312s3.398-.892 4.278-.892c.88 0 4.667 1.003 4.667 1.003s3.342 3.511 4.222 3.511c0 0 1.906-.039 3.667 1.727 1.76 1.766 1.598 4.903 1.598 4.903s.275 4.245.988 4.373c1.81.324.167 3.713-.713 4.596s-7.01 1.277-7.01 1.277.216 2.755-1.545 3.639c-5.281.883-7.565.246-7.565.246l-1.76-1.766s-4.29-4.097-5.17-4.097-5.393-.319-5.393-1.202 0-3.533.88-6.182c1.76-2.649 2.641-3.533 2.641-3.533s-1.111-1.435-.231-3.201c1.76-1.766 5.5-2.675 5.5-2.675l.946-1.727z"/><path fill="#99AAB5" d="M6.29 12.597l3.981 1.851-2.08 1.468-4.274.532z"/><path fill="#66757F" d="M1.597 21.981s1.129 3.766 1.426 4.213 4.099.128 4.574.128c.475 0 2.792 1.213 2.792 1.213s-3.089.83-4.158.702c-1.069-.128-3.848-.627-4.099-1.341s-.535-4.915-.535-4.915z"/><path fill="#E1E8ED" d="M7.3 9.597c.49-.155 3.861-1.596 4.574-1.532.713.064 3.743 1.787 3.03 2.362s-4.634.128-5.762.128S6.29 9.916 7.3 9.597zm-2.376 7.724c.303-.571 4.337-.766 4.337-.766s5.228 2.107 5.228 2.426-4.752 2.49-5.228 2.553-4.575-3.766-4.337-4.213zm11.703 3.894c-.094-.505 2.02-3.575 2.436-3.83.416-.255 6-1.724 6.95-1.724s3.98 2.234 4.04 2.681c.059.447-2.673 3.511-3.386 3.766-.713.256-9.862.064-10.04-.893zm5.347-8.427c-.225.537 5.347 1.277 5.941 1.021.594-.255 1.663-1.979 1.485-2.426-.178-.447-1.901-1.724-2.792-1.596-.892.129-4.1 1.725-4.634 3.001zm-4.396-7.851c.498.107 3.802.894 4.218 1.532s-1.96 2.617-2.614 2.681c-.653.064-5.109-2.681-5.584-3s3.088-1.405 3.98-1.213z"/><path fill="#66757F" d="M13.063 31.364c-.416-.638.238-2.745.594-2.49s1.426 1.021 1.485 1.66c.059.638 1.129 1.851 1.901 2.234s4.99.383 5.703.128c.713-.255 2.376-1.66 2.792-2.49s.896-.891 1.874-1.02 1.922.844.588 1.634c-1.333.79-2.759 2.195-4.422 2.514-1.663.319-7.069.575-8.02.192-.951-.383-2.495-2.362-2.495-2.362z"/><path fill="#99AAB5" d="M20.2 26.754c-.501-.269-.688-2.495-.43-2.68s2.666.185 3.225.277c.559.092 3.956-.185 4.3.185s1.173 1.726 1.462 2.541c.289.816-1.591.693-2.322.508s-5.547-.461-6.235-.831zm-7.613-14.732c.653 0 3.386-.064 3.98 0 .594.064 2.495 1.851 1.604 2.043-.891.192-3.267.83-3.624.894s-1.663-1.149-2.079-1.468c-.415-.32-.593-1.469.119-1.469zm7.486-1.787c.33-.531 1.604-.702 1.663-.128.059.575-.297 1.851-1.01 1.787-.713-.063-1.129-.893-.653-1.659zm-3.446 13.469c.593-.071 1.723-.192 1.723.319s-.713.702-1.545.638c-.831-.063-.713-.893-.178-.957z"/><path fill="#E1E8ED" d="M14.31 16.619c.519-.446 2.317-.511 2.733-.319.416.192-.475 1.532-1.01 1.596-.535.063-2.317-.767-1.723-1.277z"/><path fill="#66757F" d="M26.191 26.385c.593-.159 2.02-.319 1.96.128-.059.447-.653 1.277-1.129 1.213s-1.306-1.213-.831-1.341zm-3.861 4.469c.656-.109 1.663-.638 1.604 0-.059.638-.772 1.468-1.545 1.341-.772-.128-.831-1.214-.059-1.341z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1faad.svg b/games/sound-soup/assets/img/1faad.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#e80040" d="m18 28 17.08-10.2c.42-.25.52-.84.19-1.2C30.98 11.94 24.84 9.01 18 9.01S5.02 11.93.73 16.59c-.33.36-.24.95.19 1.2L18 27.99Z"/><path fill="#a80018" d="m18 25.79.03-.05.47-1.71V9.01c-.17 0-.33-.01-.5-.01s-.33 0-.5.01v15.02l.47 1.71zm-.81 1.26-.06.1.23.15-.03-.1zm2.99-2.79 8.25-12.81c-.3-.15-.6-.3-.91-.43l-7.68 11.93-.58 2.12.92-.81Z"/><path fill="#a80018" d="m18.01 25.81.02-.07-.03.05zm.76 1.01 14.15-12.45c-.26-.22-.53-.43-.8-.63L20.17 24.25l-.92.81-.48 1.75Zm-1.54.03-.12.11.08.09.14.15zm1.21.62.06.23.04-.02.1-.38-.13.08z"/><path fill="#a80018" d="m19.25 25.07.58-2.12 3.63-13.3c-.32-.08-.65-.15-.98-.21L18.5 24.03l-.47 1.71-.02.07.55.86.2.18v-.03zm-3.08-2.12L8.49 11.02c-.31.14-.61.28-.91.43l8.25 12.81.92.81-.58-2.12Z"/><path fill="#a80018" d="m17.97 25.74.14.53.46.4-.56-.86-.01-.02zm.54 1.64.13-.08.23-.15-.06-.1z"/><path fill="#a80018" d="m18.57 26.67-.46-.4.33 1.2.07-.09.3-.33.08-.09-.12-.11zm-1.82-1.6-.92-.81L3.87 13.75c-.27.2-.54.41-.8.63l14.15 12.45-.48-1.75Z"/><path fill="#a80018" d="m18.44 27.47-.33-1.19-.14-.53-.47-1.71-3.98-14.59c-.33.06-.66.13-.98.21l3.63 13.3.58 2.12.48 1.75v.03l.1.35.03.09.1.38.54.32.5-.3-.06-.23Z"/><path fill="#de9a7e" d="m18 28 6.59-3.94a8.628 8.628 0 0 0-13.18 0z"/><path fill="#fff" d="M12.19 23.95c1.53-1.56 3.62-2.45 5.81-2.45s4.28.88 5.81 2.45L18 27.42l-5.81-3.47Z"/><path fill="#de9a7e" d="m19.52 27.09 5.07-3.03a8.628 8.628 0 0 0-13.18 0l5.07 3.03-2.08 1.49a.38.38 0 0 0-.08.55c.93 1.15 2.24 1.87 3.69 1.87s2.75-.72 3.69-1.87c.14-.17.09-.42-.08-.55l-2.08-1.49Zm3.47-3.23-3.4 2.03c-.1-.15-.22-.28-.35-.4l1.83-2.84c.69.3 1.34.71 1.92 1.21Zm-4.61-1.84c.68.03 1.34.16 1.97.36l-1.76 2.73c-.07-.02-.14-.04-.22-.06v-3.03Zm-.75 0v3.03c-.07.02-.15.03-.22.06l-1.76-2.73c.63-.21 1.3-.33 1.97-.36Zm-4.61 1.84c.58-.5 1.23-.9 1.92-1.21l1.83 2.84c-.13.12-.25.25-.35.4l-3.4-2.03Z"/><circle cx="18" cy="27" r="1" fill="#a80018"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1fab4.svg b/games/sound-soup/assets/img/1fab4.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#C1694F" d="M17.094 24.242H7.56s1.653 6.469 4.254 9.581c1.771 2.118 4.75 2.282 5.81 2.282 2.118 0 4.818-.174 6.589-2.294 3.087-3.691 4.534-9.569 4.534-9.569H17.094z"/><path fill="#A04E3C" d="M27.628 27.666c.458-1.18.771-2.184.947-2.795-.099-.217-.247-.427-.432-.629H8.164c-.185.202-.333.411-.432.628.174.608.479 1.608.892 2.753 1.535 1.364 4.723 2.458 9.529 2.458 4.754 0 7.922-1.072 9.475-2.415z"/><ellipse fill="#A04E3C" cx="18" cy="19.716" rx="11.768" ry="2.716"/><path fill="#662113" d="M7.819 21.073c2.036.811 5.828 1.358 10.181 1.358s8.145-.547 10.181-1.358c-2.036-.811-5.828-1.358-10.181-1.358s-8.145.548-10.181 1.358z"/><g fill="#3E721D"><path d="M18.733 23.524c-.259 0-.485-.189-.525-.452-1.074-7.082.905-17.425 5.465-20.944.231-.179.566-.137.747.096.18.233.137.567-.096.747-4.145 3.198-6.091 13.166-5.064 19.941.045.291-.155.562-.446.606-.028.004-.055.006-.081.006z"/><path d="M18.745 15.719c.061.401 1.265.552 2.69.335 1.424-.216 2.53-.717 2.469-1.118-.061-.401-1.265-.552-2.69-.335-1.425.215-2.53.716-2.469 1.118zm.095 1.739c.061.401 1.358.537 2.897.304 1.539-.234 2.737-.748 2.676-1.15-.061-.401-1.358-.537-2.897-.304-1.539.234-2.737.748-2.676 1.15z"/><ellipse transform="rotate(-8.632 21.821 18.732)" cx="21.824" cy="18.734" rx="3.083" ry=".735"/><path d="M19.095 14.226c.103.393 1.236.435 2.531.095 1.294-.34 2.26-.935 2.157-1.327-.103-.393-1.236-.435-2.531-.095-1.294.341-2.26.935-2.157 1.327zm.178-1.765c.121.388 1.149.411 2.298.053 1.148-.358 1.98-.963 1.859-1.35-.121-.388-1.149-.411-2.298-.053-1.148.358-1.98.963-1.859 1.35zm.105-1.584c.127.325 1.068.263 2.102-.14 1.034-.403 1.77-.993 1.643-1.318-.127-.325-1.068-.263-2.102.14-1.034.402-1.769.992-1.643 1.318zm.802-1.738c.113.291.898.258 1.752-.075.854-.332 1.455-.838 1.342-1.129-.113-.291-.898-.258-1.752.075-.855.333-1.456.838-1.342 1.129zm.652-1.918c.05.308.718.456 1.493.331.774-.126 1.361-.478 1.311-.786-.05-.308-.718-.456-1.493-.331-.774.125-1.361.477-1.311.786zm.786-1.276c.052.322.787.47 1.641.331.854-.139 1.504-.512 1.452-.834-.052-.322-.787-.47-1.641-.331-.854.139-1.504.512-1.452.834zm1.152-1.441c.047.287.714.417 1.491.291.777-.126 1.369-.461 1.322-.748-.047-.287-.714-.417-1.491-.291-.776.127-1.368.462-1.322.748zm.949-1.358c.041.251.603.369 1.256.263.653-.106 1.149-.396 1.108-.647-.041-.251-.603-.369-1.256-.263s-1.149.396-1.108.647z"/><path d="M24.431 2.351c.129.219.696.124 1.266-.212.57-.336.927-.786.798-1.005-.129-.219-.696-.124-1.266.212-.57.335-.927.786-.798 1.005z"/><path d="M24.066 2.291c.244.073.596-.381.786-1.014.19-.634.147-1.206-.096-1.28-.244-.073-.596.381-.786 1.014-.19.634-.147 1.207.096 1.28zm-10.796 13.2c0 .406 1.168.735 2.609.735s2.609-.329 2.609-.735c0-.406-1.168-.735-2.609-.735s-2.609.329-2.609.735zm-.336 1.458c-.039.404 1.092.844 2.526.981 1.434.138 2.628-.078 2.667-.482.039-.404-1.092-.844-2.526-.981-1.434-.138-2.628.078-2.667.482zm-.537 1.653c-.039.404 1.22.856 2.811 1.009 1.591.153 2.913-.051 2.952-.455.039-.404-1.22-.856-2.811-1.009-1.592-.153-2.913.051-2.952.455zm1.64-4.934c-.034.405.968.818 2.237.924 1.269.106 2.325-.137 2.359-.541.034-.405-.968-.818-2.237-.924s-2.325.136-2.359.541zm.778-1.404c-.018.406.907.776 2.065.828 1.159.051 2.113-.236 2.131-.641.018-.406-.907-.776-2.065-.828-1.159-.052-2.113.235-2.131.641z"/><path d="M15.825 10.677c-.068.352.654.788 1.612.973.958.185 1.79.049 1.858-.303.068-.352-.654-.788-1.612-.973-.958-.185-1.79-.049-1.858.303zm.464-1.802c-.135.333.489.9 1.393 1.267.904.367 1.747.395 1.882.063.135-.333-.488-.9-1.393-1.267-.905-.367-1.747-.395-1.882-.063zm.381-2.029c-.189.275.338.966 1.178 1.543.84.577 1.673.821 1.863.546.189-.275-.338-.966-1.178-1.543s-1.674-.821-1.863-.546zm1.131-.878c-.189.275.251.906.983 1.409s1.479.687 1.668.412c.189-.275-.251-.906-.983-1.409s-1.479-.687-1.668-.412zm.7-1.543c-.233.239.094.936.731 1.555.637.619 1.341.927 1.574.688.233-.239-.094-.936-.731-1.555-.636-.619-1.341-.927-1.574-.688zm.914-1.103c-.233.239.054.896.64 1.466s1.25.839 1.483.599c.233-.239-.054-.896-.64-1.466s-1.25-.838-1.483-.599zm.94-.972c-.229.235.018.848.553 1.367s1.153.75 1.382.515c.229-.235-.018-.848-.553-1.367s-1.153-.751-1.382-.515z"/><ellipse transform="rotate(-32.542 22.187 2.44)" cx="22.186" cy="2.439" rx=".595" ry="1.349"/><path d="M22.804.537c-.261.09-.305.65-.097 1.251.208.6.588 1.014.849.923s.305-.65.097-1.251c-.208-.6-.588-1.013-.849-.923z"/></g><path fill="#3E721D" d="M16.885 23.63c-.278-.041-.478-.293-.453-.576.753-8.173-2.365-13.97-8.781-16.321-.276-.1-.417-.406-.316-.682.1-.276.404-.421.683-.317 6.912 2.532 10.277 8.718 9.475 17.418-.027.292-.286.508-.578.481-.011 0-.02-.001-.03-.003z"/><path fill="#3E721D" d="M16.664 16.81c-.008.406-1.181.713-2.622.686s-2.602-.378-2.595-.784c.008-.406 1.182-.713 2.622-.687 1.441.028 2.602.379 2.595.785zm-.002 1.758c-.008.406-1.181.713-2.622.687s-2.912-.478-2.904-.884c.007-.406 1.491-.613 2.932-.586 1.44.026 2.601.377 2.594.783z"/><path fill="#5C913B" d="M17.077 20.098c-.008.406-1.181.713-2.622.687-1.44-.027-2.912-.478-2.904-.884.008-.406 1.491-.613 2.932-.586s2.601.378 2.594.783z"/><path fill="#3E721D" d="M16.499 15.517c-.054.402-1.173.584-2.5.405-1.326-.179-2.358-.65-2.304-1.052.054-.402 1.174-.584 2.5-.405 1.327.179 2.358.65 2.304 1.052zm-.744-1.769c-.054.402-1.064.599-2.256.438-1.192-.16-2.114-.617-2.06-1.019s1.064-.599 2.256-.438c1.192.161 2.114.617 2.06 1.019z"/><path fill="#3E721D" d="M15.314 12.1c.004.349-.893.642-2.002.654-1.11.012-2.012-.261-2.016-.61-.004-.349.893-.642 2.002-.654 1.109-.012 2.012.261 2.016.61zm-1.392-1.313c.003.312-.737.574-1.653.584-.917.01-1.662-.235-1.666-.547-.003-.312.737-.574 1.653-.584.917-.011 1.663.234 1.666.547zM12.601 9.25c.068.305-.496.691-1.262.863-.765.172-1.441.064-1.51-.24-.068-.305.496-.691 1.262-.863.766-.172 1.442-.065 1.51.24zm-1.204-.891c.071.318-.555.73-1.399.919-.844.19-1.587.086-1.658-.232s.555-.73 1.399-.919c.844-.19 1.586-.086 1.658.232zM9.79 7.452c.064.283-.507.653-1.275.826-.767.172-1.441.082-1.505-.201-.064-.284.507-.653 1.275-.826.768-.172 1.442-.082 1.505.201zm-1.387-.907c.056.248-.422.567-1.068.712-.645.145-1.214.062-1.269-.187-.056-.248.422-.567 1.067-.712.646-.145 1.214-.061 1.27.187z"/><path fill="#3E721D" d="M7.446 6.073c-.038.251-.6.375-1.254.275s-1.153-.384-1.114-.636c.038-.251.6-.375 1.254-.275.653.1 1.152.385 1.114.636z"/><path fill="#5C913B" d="M7.762 5.882c-.198.159-.694-.131-1.107-.648-.413-.517-.587-1.065-.388-1.223.199-.159.695.131 1.108.648.412.517.586 1.064.387 1.223zm14.18 9.727c.065.401-1.036.912-2.458 1.141-1.422.229-2.628.09-2.692-.311-.065-.401 1.036-.912 2.458-1.141 1.422-.229 2.628-.09 2.692.311zm.884 1.279c.065.401-1.478 1.199-2.901 1.429-1.422.229-2.628.09-2.693-.311-.065-.401 1.036-.912 2.458-1.141 1.424-.229 3.071-.378 3.136.023zm.553 1.679c.065.401-1.877 1.301-3.299 1.53-1.422.229-2.628.09-2.692-.311-.065-.401 1.036-.912 2.458-1.141 1.422-.229 3.468-.479 3.533-.078zm-2.585-4.869c.124.387-.758 1.016-1.97 1.405-1.212.39-2.296.392-2.42.005-.124-.387.758-1.016 1.97-1.405s2.296-.391 2.42-.005zm-.799-1.416c.124.387-.67.988-1.774 1.342s-2.1.329-2.224-.057c-.124-.387.67-.988 1.774-1.342 1.104-.355 2.099-.33 2.224.057zm-1.459-1.692c.194.302-.313.975-1.133 1.504-.82.529-1.643.713-1.837.411-.194-.302.313-.975 1.133-1.504.82-.528 1.642-.712 1.837-.411zm-1.102-1.499c.249.258-.118 1.017-.82 1.695-.702.678-1.473 1.018-1.723.76-.249-.258.118-1.017.82-1.695.702-.678 1.474-1.018 1.723-.76zm-1.11-1.741c.278.185.047 1.022-.517 1.871-.564.848-1.247 1.386-1.525 1.201-.278-.185-.047-1.022.517-1.871.564-.848 1.247-1.386 1.525-1.201zm-1.377-.393c.278.185.105.934-.387 1.674-.492.74-1.116 1.189-1.394 1.004-.278-.185-.105-.934.387-1.674.491-.739 1.115-1.189 1.394-1.004zm-1.225-1.17c.305.135.261.903-.099 1.715s-.899 1.36-1.204 1.225c-.305-.135-.261-.903.099-1.715.359-.812.899-1.361 1.204-1.225zm-1.259-.683c.305.135.284.851-.047 1.599s-.848 1.245-1.153 1.109-.284-.851.047-1.599.847-1.244 1.153-1.109zm-1.235-.552c.3.133.299.793-.003 1.475-.302.681-.79 1.126-1.09.993-.3-.133-.299-.793.003-1.475.302-.681.79-1.126 1.09-.993z"/><path fill="#5C913B" d="M9.812 3.992c.323.061.473.704.335 1.436-.138.732-.511 1.277-.834 1.216-.323-.061-.473-.704-.335-1.436.138-.733.511-1.277.834-1.216zM8.28 3.784c.276-.014.525.49.556 1.124.031.635-.167 1.16-.443 1.173-.276.014-.525-.49-.556-1.124-.032-.634.167-1.16.443-1.173zm10.596 21.305c-.023 0-.047-.001-.071-.005-.292-.039-.496-.307-.457-.598C19.829 13.45 26.63 8.516 30.569 7.998c.287-.038.559.166.597.458.039.292-.166.559-.458.597-3.627.477-9.903 5.127-11.305 15.574-.036.268-.264.462-.527.462z"/><path fill="#5C913B" d="M20.889 18.194c-.032.405 1.106.825 2.542.939 1.436.114 2.626-.122 2.659-.527.032-.405-1.106-.825-2.542-.939-1.437-.113-2.627.122-2.659.527z"/><ellipse transform="rotate(-2.063 23.613 16.845)" fill="#5C913B" cx="23.601" cy="16.836" rx="2.423" ry=".735"/><path fill="#5C913B" d="M22.093 15.236c.015.406 1.001.7 2.202.656 1.202-.043 2.164-.407 2.149-.813-.015-.406-1.001-.7-2.202-.656-1.202.043-2.164.407-2.149.813z"/><path fill="#5C913B" d="M22.693 13.638c-.038.347.826.726 1.929.847 1.103.121 2.028-.063 2.066-.41.038-.347-.826-.726-1.929-.847-1.103-.12-2.029.063-2.066.41zm1.512-1.171c-.034.311.677.643 1.588.742.911.099 1.677-.072 1.711-.382.034-.311-.677-.643-1.588-.742s-1.677.072-1.711.382zm1.465-1.4c-.098.297.427.736 1.172.982s1.428.205 1.526-.092c.098-.297-.427-.736-1.172-.982s-1.428-.205-1.526.092zm1.286-.769c-.102.31.481.78 1.303 1.052.822.271 1.571.24 1.673-.069.102-.31-.481-.78-1.303-1.052s-1.571-.241-1.673.069zm1.687-.746c-.091.276.441.699 1.188.946.747.247 1.427.223 1.518-.053.091-.276-.441-.7-1.188-.946-.747-.247-1.427-.223-1.518.053zm1.469-.767c-.08.242.365.605.993.813.628.207 1.202.18 1.282-.062.08-.242-.365-.605-.993-.813-.628-.208-1.202-.18-1.282.062z"/><path fill="#5C913B" d="M31.111 8.408c.014.254.56.431 1.221.396.661-.035 1.185-.27 1.172-.524-.014-.254-.56-.431-1.221-.396-.661.036-1.186.27-1.172.524z"/><path fill="#77B255" d="M30.815 8.187c.182.177.704-.063 1.166-.537.461-.474.688-1.002.506-1.18-.182-.177-.704.063-1.166.537-.462.475-.688 1.003-.506 1.18zm-15.062 8.296c-.103.393.942 1.008 2.335 1.375s2.607.346 2.71-.046c.104-.393-.942-1.008-2.335-1.375s-2.607-.347-2.71.046zm1.329-1.789c-.161.373.655 1.085 1.824 1.591s2.247.614 2.408.242c.161-.373-.655-1.085-1.824-1.591-1.168-.507-2.246-.615-2.408-.242zm.934-1.332c-.161.373.571 1.048 1.635 1.509 1.064.461 2.058.533 2.219.16.161-.373-.571-1.048-1.635-1.509-1.064-.461-2.058-.532-2.219-.16zm1.617-1.541c-.223.281.216 1.001.981 1.607.765.606 1.565.87 1.788.589.223-.281-.216-1.001-.981-1.607-.764-.606-1.565-.87-1.788-.589zm1.243-1.384c-.273.233.018 1.024.65 1.767.633.743 1.367 1.157 1.64.924.273-.233-.018-1.024-.65-1.767-.632-.743-1.367-1.157-1.64-.924zm1.275-1.625c-.295.157-.146 1.013.332 1.912s1.105 1.501 1.4 1.344c.295-.157.146-1.013-.332-1.912s-1.105-1.501-1.4-1.344zm1.409-.256c-.295.157-.196.92.221 1.704.417.784.994 1.293 1.289 1.136.295-.157.196-.92-.221-1.704-.417-.785-.994-1.293-1.289-1.136zm1.333-1.046c-.317.105-.348.873-.069 1.717.279.843.762 1.442 1.079 1.337.317-.105.348-.873.069-1.717-.279-.843-.762-1.441-1.079-1.337zm1.32-.556c-.317.105-.366.819-.109 1.596.257.777.722 1.321 1.039 1.216.317-.105.366-.819.109-1.596-.257-.776-.722-1.32-1.039-1.216zm1.282-.429c-.312.103-.375.76-.141 1.468s.676 1.198.988 1.095c.312-.103.375-.76.141-1.468-.233-.708-.676-1.198-.988-1.095z"/><path fill="#77B255" d="M28.96 6.106c-.327.029-.539.654-.474 1.397.066.742.384 1.321.711 1.292.327-.029.539-.654.474-1.397s-.384-1.321-.711-1.292zm1.545-.058c-.274-.04-.57.436-.663 1.065-.093.628.053 1.171.327 1.211.274.04.571-.436.663-1.065.092-.628-.054-1.17-.327-1.211z"/><path fill="#5C913B" d="M16.947 23.468c-.227-.016-.427-.177-.481-.41-.071-.304-1.857-7.429-12.713-7.742-.266.012-.525-.252-.517-.547.008-.294.253-.525.547-.517 11.696.33 13.702 8.485 13.72 8.568.065.286-.114.57-.399.636-.054.012-.106.016-.157.012z"/><path fill="#77B255" d="M16.196 21.209c.153.376-.805 1.121-2.14 1.663s-2.541.677-2.693.301c-.153-.376.805-1.121 2.14-1.663 1.334-.543 2.54-.677 2.693-.301zm-.66-1.124c.109.391-.849.998-2.139 1.356-1.29.358-2.423.331-2.532-.06-.109-.391.849-.998 2.139-1.356s2.423-.331 2.532.06zm-1.38-1.333c.109.391-.743.969-1.902 1.291-1.159.322-2.186.265-2.295-.126-.109-.391.743-.969 1.901-1.291 1.16-.322 2.187-.265 2.296.126z"/><path fill="#77B255" d="M13.101 17.41c.141.319-.568.942-1.583 1.389-1.015.448-1.953.552-2.094.233-.141-.319.568-.942 1.583-1.389 1.016-.448 1.953-.552 2.094-.233zm-1.795-.659c.126.286-.452.817-1.29 1.187-.839.37-1.621.438-1.747.152-.126-.286.452-.817 1.29-1.187.838-.37 1.62-.438 1.747-.152zm-1.82-.893c.183.253-.184.831-.82 1.29-.636.459-1.3.626-1.483.373-.183-.253.184-.831.82-1.29.636-.459 1.3-.627 1.483-.373zm-1.458-.345c.191.264-.223.889-.925 1.396-.702.507-1.425.703-1.616.439-.191-.264.223-.889.925-1.396.702-.507 1.425-.703 1.616-.439zm-1.834-.202c.17.235-.209.8-.847 1.261-.638.461-1.293.643-1.463.408-.17-.235.209-.8.847-1.261.638-.461 1.293-.643 1.463-.408zm-1.632-.287c.149.206-.165.687-.701 1.075-.536.387-1.092.534-1.241.328-.149-.206.165-.687.701-1.075.537-.388 1.092-.535 1.241-.328z"/><path fill="#77B255" d="M3.496 14.966c.064.246-.404.58-1.044.746-.64.166-1.211.101-1.275-.146-.064-.246.404-.58 1.044-.746.641-.165 1.212-.1 1.275.146z"/><path fill="#5C913B" d="M3.712 14.666c-.12.224-.69.153-1.273-.16-.583-.312-.958-.748-.838-.972.12-.224.69-.153 1.273.16s.958.748.838.972zm16.864 3.361c.217.343-.594 1.246-1.811 2.016s-2.38 1.117-2.597.774c-.217-.343.594-1.246 1.811-2.016s2.38-1.117 2.597-.774zm-1.808-1.304c.266.306-.297 1.232-1.258 2.067s-1.956 1.264-2.223.958c-.266-.307.297-1.232 1.258-2.067.962-.836 1.957-1.265 2.223-.958zm-1.292-.988c.266.306-.227 1.172-1.103 1.932-.875.761-1.801 1.129-2.067.823-.266-.307.227-1.172 1.103-1.932.875-.761 1.801-1.129 2.067-.823zm-2.007-.981c.298.201.096 1.019-.45 1.828-.546.809-1.23 1.302-1.527 1.101-.298-.201-.096-1.019.45-1.828.546-.809 1.23-1.302 1.527-1.101zm-1.603-.944c.331.139.292.981-.087 1.881-.379.899-.954 1.516-1.285 1.376-.331-.139-.292-.981.087-1.881.379-.899.954-1.516 1.285-1.376zm-1.705-1.164c.329.061.445.922.261 1.923-.185 1.002-.601 1.765-.929 1.704-.328-.061-.445-.922-.261-1.923.184-1.002.6-1.765.929-1.704zm-1.421.181c.328.061.464.818.303 1.691-.161.873-.558 1.532-.886 1.472-.329-.061-.464-.818-.303-1.691.161-.874.557-1.533.886-1.472zm-1.587-.594c.334.004.596.727.585 1.615-.011.888-.291 1.604-.625 1.6-.334-.004-.596-.727-.585-1.615.012-.888.291-1.604.625-1.6zm-1.426-.132c.334.004.596.671.586 1.489-.01.818-.289 1.478-.623 1.473-.334-.004-.596-.671-.586-1.489.01-.817.289-1.477.623-1.473zm-1.352-.022c.328.004.587.612.578 1.357-.009.745-.283 1.346-.612 1.342-.328-.004-.587-.612-.578-1.357.01-.745.284-1.346.612-1.342z"/><path fill="#5C913B" d="M4.852 12.122c.321-.071.712.461.873 1.188s.033 1.375-.288 1.446c-.321.071-.712-.461-.873-1.188-.161-.728-.032-1.375.288-1.446zm-1.49.411c.248-.121.676.244.954.815.278.571.303 1.132.054 1.253-.248.121-.676-.244-.954-.815-.278-.57-.302-1.131-.054-1.253z"/><path fill="#77B255" d="M18.447 24.397h-.024c-.294-.014-.521-.262-.508-.556.051-1.16-.052-7.065-7.123-9.303-.28-.088-.436-.388-.347-.668.089-.279.387-.437.668-.347 7.812 2.473 7.922 9.068 7.865 10.365-.012.286-.248.509-.531.509z"/><path fill="#A6D388" d="M18.441 23.459c-.057.402-1.178.575-2.503.386-1.325-.189-2.353-.668-2.295-1.07.057-.402 1.178-.575 2.503-.386 1.324.189 2.352.668 2.295 1.07z"/><path fill="#C6E5B3" d="M18.374 21.969c-.057.402-1.069.59-2.259.421-1.191-.17-2.109-.633-2.052-1.035.057-.402 1.069-.59 2.259-.421s2.109.633 2.052 1.035zm-.681-1.873c.001.349-.898.635-2.007.639-1.11.003-2.01-.277-2.012-.626-.001-.349.898-.635 2.007-.639 1.11-.003 2.011.277 2.012.626zm-.78-1.229c.001.312-.741.568-1.658.571-.917.003-1.66-.248-1.661-.56-.001-.312.741-.568 1.658-.571.916-.003 1.66.248 1.661.56zm-1.31-1.547c.066.305-.502.687-1.269.853-.767.166-1.442.053-1.508-.252-.066-.305.502-.687 1.269-.853.767-.166 1.442-.053 1.508.252zm-1.197-.9c.069.319-.561.725-1.406.909-.846.183-1.587.073-1.656-.245-.069-.319.561-.725 1.406-.909.845-.183 1.587-.074 1.656.245zm-1.094-.983c.062.284-.512.649-1.281.816-.769.167-1.442.071-1.504-.213-.061-.284.512-.649 1.281-.816.77-.166 1.443-.071 1.504.213zm-1.696-.791c.054.249-.427.564-1.073.704-.647.14-1.214.052-1.268-.196-.054-.249.427-.564 1.073-.704.646-.141 1.214-.053 1.268.196zm-1.143-.543c-.04.251-.602.37-1.256.265-.653-.105-1.15-.393-1.11-.644.04-.251.602-.37 1.256-.265.653.105 1.15.393 1.11.644z"/><path fill="#A6D388" d="M10.791 13.915c-.2.157-.694-.137-1.103-.657-.409-.52-.578-1.069-.379-1.226.2-.157.694.137 1.103.657.409.519.579 1.068.379 1.226zm12.338 8.043c.121.387-.766 1.01-1.981 1.39-1.215.38-2.299.374-2.42-.013-.121-.388.766-1.01 1.981-1.39 1.215-.38 2.299-.374 2.42.013zm-.693-1.359c.121.387-.678.982-1.785 1.328-1.107.346-2.102.313-2.224-.075s.678-.982 1.785-1.329c1.107-.345 2.102-.312 2.224.076zm-1.098-1.893c.192.303-.32.972-1.144 1.495-.824.522-1.648.7-1.84.397-.192-.303.32-.973 1.145-1.495.823-.522 1.647-.7 1.839-.397zm-.901-1.507c.247.26-.126 1.016-.833 1.688-.707.672-1.481 1.007-1.729.746-.247-.26.126-1.016.833-1.688.708-.672 1.482-1.007 1.729-.746zm-1.096-1.75c.277.187.039 1.023-.532 1.867-.57.844-1.257 1.376-1.534 1.189-.277-.187-.039-1.023.532-1.867.57-.844 1.257-1.376 1.534-1.189zm-1.374-.404c.277.187.098.935-.399 1.671s-1.125 1.181-1.402.993c-.277-.187-.098-.935.399-1.671.497-.735 1.125-1.18 1.402-.993zm-1.216-1.18c.304.138.254.905-.112 1.714-.366.809-.91 1.354-1.214 1.216-.304-.138-.254-.905.112-1.714.366-.809.91-1.353 1.214-1.216zm-1.254-.692c.304.138.278.854-.059 1.599s-.857 1.238-1.162 1.1c-.304-.138-.278-.854.059-1.599.338-.745.858-1.237 1.162-1.1zm-1.23-.561c.299.135.293.796-.014 1.475s-.799 1.12-1.098.984c-.299-.135-.293-.795.014-1.475.307-.679.799-1.12 1.098-.984z"/><path fill="#A6D388" d="M12.855 12.04c.322.063.467.707.324 1.439-.143.731-.521 1.273-.843 1.21s-.467-.707-.324-1.439c.143-.732.521-1.273.843-1.21zm-1.53-.22c.276-.012.521.494.548 1.129.026.635-.176 1.158-.453 1.17-.276.011-.521-.494-.548-1.129-.026-.634.176-1.158.453-1.17zm8.879 12.32l-.042-.001c-.293-.023-.513-.279-.491-.572.222-2.938 2.831-7.78 8.884-8.269.287-.021.55.195.573.488.024.293-.194.549-.488.573-6.041.487-7.776 5.553-7.907 7.289-.021.279-.253.492-.529.492z"/><path fill="#A6D388" d="M19.959 22.482c-.008.406.959.756 2.161.781 1.202.025 2.184-.283 2.192-.689.008-.406-.959-.755-2.162-.781-1.201-.026-2.182.283-2.191.689zm.976-1.739c-.058.344.783.772 1.878.955 1.094.183 2.029.053 2.086-.292.058-.344-.783-.772-1.878-.955-1.094-.183-2.028-.052-2.086.292zm.969-1.086c-.052.308.639.681 1.544.832.904.151 1.679.024 1.73-.284.051-.308-.64-.681-1.544-.832-.904-.151-1.679-.024-1.73.284zm1.542-1.315c-.115.291.384.76 1.114 1.048.73.288 1.414.286 1.529-.005.115-.291-.384-.76-1.114-1.048-.729-.288-1.414-.285-1.529.005zm1.169-.915c-.12.303.436.807 1.241 1.124s1.555.33 1.674.026c.12-.303-.436-.806-1.241-1.124-.804-.318-1.554-.33-1.674-.026z"/><path fill="#A6D388" d="M26.012 16.855c-.107.27.4.724 1.132 1.012.732.289 1.412.304 1.518.034.107-.27-.4-.723-1.132-1.012-.732-.289-1.412-.304-1.518-.034zm1.802-.506c-.093.237.33.625.945.868s1.19.248 1.283.011c.093-.237-.33-.625-.945-.868s-1.19-.248-1.283-.011z"/><path fill="#C6E5B3" d="M29.03 15.998c-.001.254.535.463 1.196.465.661.002 1.199-.202 1.199-.456.001-.254-.535-.462-1.196-.465-.661-.002-1.198.202-1.199.456z"/><path fill="#C6E5B3" d="M28.747 15.761c.172.188.707-.023 1.194-.47.488-.447.744-.961.572-1.149-.172-.188-.707.023-1.194.47-.488.447-.744.961-.572 1.149zM16.49 20.63c-.182.363.51 1.079 1.546 1.6 1.036.521 2.024.649 2.206.286.182-.363-.51-1.079-1.546-1.6-1.036-.521-2.024-.649-2.206-.286z"/><path fill="#C6E5B3" d="M17.879 19.161c-.239.268.159 1.011.887 1.66s1.513.958 1.752.69c.239-.268-.159-1.012-.887-1.661s-1.513-.957-1.752-.689zm.817-1.721c-.286.217-.041 1.023.549 1.801.589.778 1.299 1.233 1.585 1.016.286-.217.04-1.023-.549-1.801-.59-.778-1.299-1.233-1.585-1.016zm1.365-1.549c-.303.14-.204 1.003.223 1.928.426.925 1.018 1.562 1.321 1.422.303-.14.204-1.003-.223-1.928-.426-.925-1.018-1.562-1.321-1.422zm1.421-.176c-.303.14-.248.907.124 1.714.372.807.919 1.347 1.222 1.207.303-.14.248-.907-.124-1.713-.371-.807-.918-1.348-1.222-1.208zm1.391-.968c-.323.087-.398.852-.167 1.71.23.858.678 1.483 1.001 1.396.323-.087.398-.852.167-1.71-.23-.857-.679-1.482-1.001-1.396zm1.349-.479c-.323.087-.412.797-.2 1.587.212.79.646 1.36.968 1.274.323-.087.412-.797.2-1.587-.211-.791-.645-1.361-.968-1.274zm1.305-.356c-.317.085-.418.738-.225 1.457s.607 1.234.924 1.149c.317-.085.418-.738.225-1.457-.193-.719-.607-1.234-.924-1.149z"/><path fill="#C6E5B3" d="M27.013 13.577c-.328.01-.576.622-.553 1.367.023.745.308 1.34.636 1.33.328-.01.576-.622.552-1.367s-.307-1.34-.635-1.33zm1.546.031c-.271-.056-.594.403-.723 1.025s-.014 1.172.257 1.228c.271.056.594-.403.723-1.025s.014-1.172-.257-1.228z"/><path fill="#C1694F" d="M18 22.431c-6.499 0-11.768-1.216-11.768-2.716v4.031c0 2.099 5.333 3.8 11.768 3.8 6.436 0 11.768-1.702 11.768-3.8v-4.031c0 1.501-5.269 2.716-11.768 2.716z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1fab5.svg b/games/sound-soup/assets/img/1fab5.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#662113" d="M17.34 1.835C11.231 2.323 9 5.399 9 8.34c0 2.101-.348 17.904-.348 20.005s2.071 4.385 4.946 5.703c4.186 1.919 14.663 1.074 14.569-5.926-.107-7.999.045-18.757.045-18.757-.001-5.213-4.845-8.012-10.872-7.53z"/><path fill="#C1694F" d="M10.948 10.993c3.768 3.14 9.956 2.961 13.601 1.026 3.5-1.858 3.796-4.882 1.488-7.288C24.07 2.68 19.365 1.6 15.311 2.524c-4.561 1.04-8.058 5.389-4.363 8.469z"/><path fill="#FFE8B6" d="M11.949 10.568c3.271 2.726 8.37 2.407 11.807.891 3.147-1.389 3.52-4.01 1.292-6.327-1.71-1.778-5.792-2.718-9.312-1.916-3.959.902-6.995 4.678-3.787 7.352z"/><path fill="#662113" d="M9.142 15.03c-1.223-.876-3.315-2.484-3.81-2.804-.81-.525-2.583 1.725-1.219 3.512s4.088 4.296 4.746 7.729c.659 3.433.283-8.437.283-8.437z"/><ellipse transform="rotate(-75.345 4.875 13.817)" fill="#C1694F" cx="4.876" cy="13.818" rx="1.167" ry=".706"/><path fill="#D99E82" d="M18.666 11.588c-2.247 0-4.511-.762-5.608-1.658-.808-.66-1.223-1.544-1.138-2.425.068-.703.489-1.723 2.109-2.591 2.326-1.247 4.73-1.616 6.949-1.069 2.296.564 4.698 2.357 4.477 4.026-.236 1.768-3.604 3.299-5.267 3.59-.497.087-1.008.127-1.522.127zm.273-6.988c-1.451 0-2.958.403-4.438 1.196-.973.521-1.521 1.146-1.585 1.806-.053.542.23 1.109.775 1.554 1.183.966 4.009 1.728 6.326 1.32 1.747-.306 4.313-1.742 4.447-2.737.128-.962-1.752-2.438-3.724-2.923-.587-.145-1.189-.216-1.801-.216z"/><path fill="#D99E82" d="M18.432 9.424c-.986 0-1.906-.24-2.423-.663-.433-.354-.654-.835-.607-1.321.037-.38.255-.926 1.084-1.371.629-.337 2.067-.645 3.043-.544 1.105.111 2.625.869 2.589 1.853-.059 1.524-1.646 1.789-2.697 1.964-.329.055-.663.082-.989.082zm.687-2.918c-.79 0-1.784.243-2.162.445-.337.181-.542.394-.56.585-.014.145.077.313.244.45.402.329 1.489.556 2.615.37 1.406-.234 1.841-.472 1.861-1.016-.039-.213-.846-.736-1.688-.82-.098-.009-.202-.014-.31-.014z"/><path fill="#292F33" d="M14.213 34.188l.401-6.282.49 6.594zm11.985-1.648l.178-7.352.49 6.594z"/><path fill="#C1694F" d="M15.639 22.827l.712-4.589.09 4.99.445 4.722zm5.257 11.138l-.178-9.98.846 7.396zm2.762-13.812l.134-6.327.511 3.574c.016.111.01.224-.019.333l-.626 2.42z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1fab9.svg b/games/sound-soup/assets/img/1fab9.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36" xml:space="preserve"><path fill="#65471B" d="M20.409 24.866 20 22l7.548-16.983a2.84 2.84 0 0 1 1.476-1.456l5.517-2.364A.759.759 0 0 0 35 .5a.373.373 0 0 0-.479-.356l-8.296 2.489a2.842 2.842 0 0 0-1.762 1.529L19 16 0 36h9l1.859-1.859a8.688 8.688 0 0 1 2.616-1.797L21 29c2.463-1.642 10.996-2.61 13.969-2.904A1.144 1.144 0 0 0 36 24.958a.905.905 0 0 0-.956-.904l-14.635.812z"/><path fill="#BD9366" d="M33.485 14.893c-.061-.147-.1-.604-.31-1.04-.049-.076-.382-.523-.329-.491-.532-.572-.838-.829-1.454-1.274-.265-.008-.437-.029-.808-.525-2.567-1.545-5.955-3.929-10.796-3.958-4.472-.027-10.333 2.143-14.46 4.496-3.746 2.136-3.565 2.492-3.531 5.144l3.061 3.893c1.07 1.36 2.073 2.97 3.414 4.081.826.684 2.663 2.118 10.134 2.661.91.066 1.728.08 2.255-.006 2.763-.451 5.445-2.886 6.183-3.243 3.455-1.671 6.286-2.989 6.659-8.219a8.202 8.202 0 0 0-.018-1.519z"/><path fill="#77B255" d="M35.889 30.561c-1.763-2.455-5.232-6.206-8.654-3.772a.553.553 0 0 0-.179.236.757.757 0 0 0-.536.061c-1.764.919-4.409 1.574-3.225 8.428.031.182.158.339.341.423s.4.085.584.002c3.122-1.414 3.831-4.042 3.772-6.076.358.524.785.973 1.309 1.309 1.737 1.111 3.945.879 5.248.591a8.793 8.793 0 0 0 1.1-.31.548.548 0 0 0 .329-.377.615.615 0 0 0-.089-.515z"/><path fill="#5C913B" d="M24.5 34a.501.501 0 0 1-.5-.495c-.023-2.39.562-3.425 1.306-4.736.242-.428.505-.891.772-1.455.194-.41.634-.647 1.079-.598 2.452.284 5.13 2.016 6.678 3.413a.499.499 0 1 1-.67.742c-1.158-1.045-3.751-2.887-6.123-3.162-.34.623-.613 1.106-.866 1.553-.695 1.226-1.197 2.112-1.176 4.233a.5.5 0 0 1-.495.505H24.5z"/><path fill="#A57F5A" d="M33.182 14.629c-.362-.516-.971-2.118-1.403-1.877a1.01 1.01 0 0 0-.482-.144c-.069 0-.143.027-.216.044-.688-1.375-1.999-2.436-3.602-3.009-.265-.095-.493-.075-.679.015a55.723 55.723 0 0 1-.603-.451c-.512-.39-1.01.478-.505.863 1.13.862 1.668.95 1.722 2.407.224 6.007-.481 10.022-9.002 9.794-2.399-.064-5.718-1.876-8.156-3.884a7.352 7.352 0 0 1-.706-1.262c-.215-.508-.956-.61-1.387-.364-.054.031-.083.076-.127.112a9.476 9.476 0 0 0-1.866-1.036c-.506-.211-1.115.228-1.247.708-.161.584.201 1.036.708 1.247 3.812 1.589 1.494 3.724 8.324 5.482 1.264.325.725 2.279-.539 1.954-3.22-.829-10.199-5.75-10.199-5.75.086.096.847 10.93 14.543 11.461.372.014 12.158.67 15.078-10.759.443-1.737 1.813-3.455.344-5.551z"/><path fill="#BD9366" d="M19.759 24.881c-.025-.554.483-1.003 1.013-1.013.238-.004.473-.023.711-.044l.04-.003.037-.007c.114-.02.228-.038.342-.06.394-.077.783-.181 1.167-.299.795-.245 1.569-.553 2.354-.83.535-.189 1.087.215 1.246.707.177.546-.209 1.07-.707 1.246-1.699.6-3.358 1.281-5.189 1.315-.569.011-.99-.472-1.014-1.012z"/><path fill="#936D46" d="M18.242 9.687c-3.366-.51-6.829-.297-9.155 1.931-.386.369-.566.895-.57 1.441-.463.533-.712 1.175-.508 2.015.258 1.062.364 1.804 1.388 2.269 6.479 2.941 17.775 3.036 18.668-4.408.425-3.538-6.808-2.791-9.823-3.248z"/><path d="M18.826 28.109c.024.537-.477 1.005-1 1a19.065 19.065 0 0 1-6.425-1.17c-.526-.195-.827-.664-.698-1.23.113-.495.739-.88 1.23-.698 1.964.727 3.854 1.079 5.893 1.098.561.005.976.454 1 1zm14.147-13.543c-.184-.507-.682-.848-1.231-.696-.49.136-.881.721-.696 1.231.526 1.451 1.138 3.071.791 4.635-.15.675-.441 1.255-.957 1.722-.624.566-1.359 1.03-2.037 1.529-.434.32-.65.869-.356 1.369.252.429.931.678 1.369.356.833-.613 1.741-1.187 2.497-1.895.686-.642 1.154-1.511 1.381-2.417.497-1.984-.085-3.969-.761-5.834z" fill="#BD9366"/><path fill="#77B255" d="M24.986 3.599a.485.485 0 0 0-.002-.221c-.001-.002-.003-.004-.003-.007-.004-.015-.014-.027-.02-.041a.508.508 0 0 0-.093-.159c-.007-.007-.008-.018-.015-.025C20.95-.757 18.173.329 16.84.85c-.182.071-.335.133-.462.165a.5.5 0 0 0-.326.709c.016.031 1.155 2.214 4.08 2.724-.343.578-.639 1.28-.883 2.116a.502.502 0 0 0 .277.597c.305.135.621.202.946.202 1.412 0 2.985-1.256 4.45-3.597.021-.034.029-.073.041-.11.005-.015.018-.025.021-.041.004-.006.001-.011.002-.016z"/><path fill="#5C913B" d="M20.722 3.03c-.598-.322-1.275-.688-2.38-1.056a.5.5 0 1 1 .317-.948c1.187.396 1.937.799 2.538 1.124.374.202.7.375 1.06.513-.108.029-.22.043-.325.086a3.071 3.071 0 0 0-.811.493 22.623 22.623 0 0 1-.399-.212zm1.253 2.628C22.347 4.542 23.173 4 24.5 4a.5.5 0 0 0 0-1c-1.763 0-2.964.81-3.475 2.342a.5.5 0 0 0 .95.316z"/><g fill="#DEAD74"><path d="M21.272 10.135C9.833 9.704 7.708 14.489 9.381 16.409c.519.595 1.148.951 1.852 1.297 1.173.576.227 2.37-.945 1.795-4.128-2.026-4.263-5.481-1.868-7.823-1.461.576-1.931-1.41-.768-1.88 1.62-.654 3.298-1.073 5.004-1.339-.515-.093-1.426-.053-1.514-1.032-.166-1.857 2.62-.745 4.557-.272 1.363-.834 2.896-1.02 4.496-.893 1.153.091 1.358 1.26.647 1.838 1.89.064 1.627 2.081.43 2.035z"/><path d="M8.764 23.59c-1.97-3.539-4.057-3.525-5.385-5.45-2.436-3.533 2.76-4.666 1.844-7.751-.372-1.249-2.33-.717-1.957.539.284.957-1.482 1.487-2.295 3.977a4.908 4.908 0 0 0 .134 3.408c-1.169.207-1.011 1.771-.097 1.953.735.146 1.158.293 1.759.524.266.175 2.663.983 4.245 3.824.625 1.121 2.428.19 1.752-1.024zm25.914-9.412c-.411-.843-1.103-1.522-1.865-2.1a18.34 18.34 0 0 0-.311-1.108c.815-.233 1.052-1.608.025-1.947-.97-.32-2.074.404-2.122 1.651a26.297 26.297 0 0 0-4.936-2.326c-1.219-.426-1.896 1.485-.589 1.941 3.732 1.304 1.823 4.862-1.373 6.759-2.489 1.074-5.175 1.31-7.567.49-1.086-.372-1.905 1.477-.432 1.982 3.262 1.118 7.204.335 10.223-1.365 1.709-.963 3.306-3.694 2.988-5.638-.051-.309.293-.519.548-.349.873.581 1.684 1.398 1.644 1.761-.21 1.925-.414 2.872-2.426 4.353-.413.304-.677.887-.511 1.421.15.482.716.8 1.174.554.925-.497 1.815-1.149 2.524-2.177.735-1.039.82-2.079 1.102-3.154.439.752.569 1.72.185 2.798-.19.533.185 1.154.674 1.231 1.752.341 1.89-3.044 1.045-4.777zm-12.126 7.521c-.392-2.012-4.323.709-7.849-1.502-1.196-.75-2.219 1.001-1.154 1.669 2.487 1.56 5.411 1.335 8.202 1.024.545-.062.9-.685.801-1.191z"/></g></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/1fad9.svg b/games/sound-soup/assets/img/1fad9.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36" xml:space="preserve"><path fill="#C4C9C8" d="M8 8c0 .64.254 1.254.707 1.707a.256.256 0 0 1-.1.424l-.556.185A3 3 0 0 0 6 13.162v18.451c0 .002.002.004.004.004 0 .007-.004.014-.004.021 0 1.796 5.373 3.252 12 3.252s12-1.456 12-3.252c0-.007-.004-.014-.004-.021a.004.004 0 0 0 .004-.004V13.162a3 3 0 0 0-2.051-2.846l-.795-.265a.1.1 0 0 1-.04-.165l.178-.178A2.415 2.415 0 0 0 28 8H8z"/><path fill="#F19020" d="M29 6.8c0 1.657-4.925 3.2-11 3.2S7 8.457 7 6.8 11.925 4 18 4s11 1.143 11 2.8z"/><path fill="#F19020" d="M7 4h22v2.8H7z"/><ellipse fill="#F9CA55" cx="18" cy="4" rx="11" ry="3"/><ellipse fill="#AEB3B2" cx="18" cy="30.5" rx="11" ry="3"/><path fill="#F19020" d="M10.465 4c1.099-.582 4.053-1 7.535-1s6.436.418 7.535 1c.296-.157.465-.324.465-.5 0-.828-3.582-1.5-8-1.5s-8 .672-8 1.5c0 .176.169.343.465.5z"/><path fill="#AEB3B2" d="M18 12c-1.145 0-5.129-.07-8.335-.974a.5.5 0 1 1 .271-.963c3.083.87 6.952.937 8.064.937s4.981-.067 8.063-.937a.501.501 0 0 1 .271.963C23.129 11.93 19.145 12 18 12z"/><path fill="#FFF" d="M10 31a1 1 0 0 1-1-1V14a1 1 0 1 1 2 0v16a1 1 0 0 1-1 1z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/23f0.svg b/games/sound-soup/assets/img/23f0.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#FFCC4D" d="M20 6.042c0 1.112-.903 2.014-2 2.014s-2-.902-2-2.014V2.014C16 .901 16.903 0 18 0s2 .901 2 2.014v4.028z"/><path fill="#FFAC33" d="M9.18 36c-.224 0-.452-.052-.666-.159-.736-.374-1.035-1.28-.667-2.027l8.94-18.127c.252-.512.768-.835 1.333-.835s1.081.323 1.333.835l8.941 18.127c.368.747.07 1.653-.666 2.027-.736.372-1.631.07-1.999-.676L18.121 19.74l-7.607 15.425c-.262.529-.788.835-1.334.835z"/><path fill="#58595B" d="M18.121 20.392c-.263 0-.516-.106-.702-.295L3.512 5.998c-.388-.394-.388-1.031 0-1.424s1.017-.393 1.404 0L18.121 17.96 31.324 4.573c.389-.393 1.017-.393 1.405 0 .388.394.388 1.031 0 1.424l-13.905 14.1c-.187.188-.439.295-.703.295z"/><path fill="#DD2E44" d="M34.015 19.385c0 8.898-7.115 16.111-15.894 16.111-8.777 0-15.893-7.213-15.893-16.111 0-8.9 7.116-16.113 15.893-16.113 8.778-.001 15.894 7.213 15.894 16.113z"/><path fill="#E6E7E8" d="M30.041 19.385c0 6.674-5.335 12.084-11.92 12.084-6.583 0-11.919-5.41-11.919-12.084C6.202 12.71 11.538 7.3 18.121 7.3c6.585-.001 11.92 5.41 11.92 12.085z"/><path fill="#FFCC4D" d="M30.04 1.257c-1.646 0-3.135.676-4.214 1.77l8.429 8.544C35.333 10.478 36 8.968 36 7.299c0-3.336-2.669-6.042-5.96-6.042zm-24.08 0c1.645 0 3.135.676 4.214 1.77l-8.429 8.544C.667 10.478 0 8.968 0 7.299c0-3.336 2.668-6.042 5.96-6.042z"/><path fill="#414042" d="M23 20h-5c-.552 0-1-.447-1-1v-9c0-.552.448-1 1-1s1 .448 1 1v8h4c.553 0 1 .448 1 1 0 .553-.447 1-1 1z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/2600.svg b/games/sound-soup/assets/img/2600.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#FFAC33" d="M16 2s0-2 2-2 2 2 2 2v2s0 2-2 2-2-2-2-2V2zm18 14s2 0 2 2-2 2-2 2h-2s-2 0-2-2 2-2 2-2h2zM4 16s2 0 2 2-2 2-2 2H2s-2 0-2-2 2-2 2-2h2zm5.121-8.707s1.414 1.414 0 2.828-2.828 0-2.828 0L4.878 8.708s-1.414-1.414 0-2.829c1.415-1.414 2.829 0 2.829 0l1.414 1.414zm21 21s1.414 1.414 0 2.828-2.828 0-2.828 0l-1.414-1.414s-1.414-1.414 0-2.828 2.828 0 2.828 0l1.414 1.414zm-.413-18.172s-1.414 1.414-2.828 0 0-2.828 0-2.828l1.414-1.414s1.414-1.414 2.828 0 0 2.828 0 2.828l-1.414 1.414zm-21 21s-1.414 1.414-2.828 0 0-2.828 0-2.828l1.414-1.414s1.414-1.414 2.828 0 0 2.828 0 2.828l-1.414 1.414zM16 32s0-2 2-2 2 2 2 2v2s0 2-2 2-2-2-2-2v-2z"/><circle fill="#FFAC33" cx="18" cy="18" r="10"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/2601.svg b/games/sound-soup/assets/img/2601.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#CCD6DD" d="M27 8c-.701 0-1.377.106-2.015.298.005-.1.015-.197.015-.298 0-3.313-2.687-6-6-6-2.769 0-5.093 1.878-5.785 4.427C12.529 6.154 11.783 6 11 6c-3.314 0-6 2.686-6 6 0 3.312 2.686 6 6 6 2.769 0 5.093-1.878 5.785-4.428.686.273 1.432.428 2.215.428.375 0 .74-.039 1.096-.104-.058.36-.096.727-.096 1.104 0 3.865 3.135 7 7 7s7-3.135 7-7c0-3.866-3.135-7-7-7z"/><path fill="#E1E8ED" d="M31 22c-.467 0-.91.085-1.339.204.216-.526.339-1.1.339-1.704 0-2.485-2.015-4.5-4.5-4.5-1.019 0-1.947.351-2.701.921C22.093 14.096 19.544 12 16.5 12c-2.838 0-5.245 1.822-6.131 4.357C9.621 16.125 8.825 16 8 16c-4.418 0-8 3.582-8 8 0 4.419 3.582 8 8 8h23c2.762 0 5-2.238 5-5s-2.238-5-5-5z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/2615.svg b/games/sound-soup/assets/img/2615.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><ellipse fill="#99AAB5" cx="18" cy="26" rx="18" ry="10"/><ellipse fill="#CCD6DD" cx="18" cy="24" rx="18" ry="10"/><path fill="#F5F8FA" d="M18 31C3.042 31 1 16 1 12h34c0 2-1.958 19-17 19z"/><path fill="#CCD6DD" d="M34.385 9.644c2.442-10.123-9.781-7.706-12.204-5.799-1.34-.148-2.736-.234-4.181-.234-9.389 0-17 3.229-17 8.444C1 17.271 8.611 21.5 18 21.5s17-4.229 17-9.444c0-.863-.226-1.664-.615-2.412zm-2.503-2.692c-1.357-.938-3.102-1.694-5.121-2.25 1.875-.576 4.551-.309 5.121 2.25z"/><ellipse fill="#8A4B38" cx="18" cy="13" rx="15" ry="7"/><path fill="#D99E82" d="M20 17c-.256 0-.512-.098-.707-.293-2.337-2.337-2.376-4.885-.125-8.262.739-1.109.9-2.246.478-3.377-.461-1.236-1.438-1.996-1.731-2.077-.553 0-.958-.443-.958-.996 0-.552.491-.995 1.043-.995.997 0 2.395 1.153 3.183 2.625 1.034 1.933.91 4.039-.351 5.929-1.961 2.942-1.531 4.332-.125 5.738.391.391.391 1.023 0 1.414-.195.196-.451.294-.707.294zm-6-2c-.256 0-.512-.098-.707-.293-2.337-2.337-2.376-4.885-.125-8.262.727-1.091.893-2.083.494-2.947-.444-.961-1.431-1.469-1.684-1.499-.552 0-.989-.447-.989-1 0-.552.458-1 1.011-1 .997 0 2.585.974 3.36 2.423.481.899 1.052 2.761-.528 5.131-1.961 2.942-1.531 4.332-.125 5.738.391.391.391 1.023 0 1.414-.195.197-.451.295-.707.295z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/26a1.svg b/games/sound-soup/assets/img/26a1.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#FFAC33" d="M32.938 15.651C32.792 15.26 32.418 15 32 15H19.925L26.89 1.458c.219-.426.106-.947-.271-1.243C26.437.071 26.218 0 26 0c-.233 0-.466.082-.653.243L18 6.588 3.347 19.243c-.316.273-.43.714-.284 1.105S3.582 21 4 21h12.075L9.11 34.542c-.219.426-.106.947.271 1.243.182.144.401.215.619.215.233 0 .466-.082.653-.243L18 29.412l14.653-12.655c.317-.273.43-.714.285-1.106z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/26f0.svg b/games/sound-soup/assets/img/26f0.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#292F33" d="M19.083 35.5L12.25 12.292s-1.42.761-2.604 1.637c-.313.231-1.977 2.79-2.312 3.04-1.762 1.315-3.552 2.841-3.792 3.167C3.083 20.76 0 36 0 36l19.083-.5z"/><path fill="#4B545D" d="M26.5 15.25l-7.312-9.212L18 2.25l-5.373 6.484-.971 1.172C11.25 10.688 10 15.25 10 15.25L8.75 20 3.252 30.054.917 34.791 32 35l-5.5-19.75z"/><path fill="#292F33" d="M12 34h2l2-15.75 1.234-1.797 3.578-5.234.094-1.469-3.287 4.454L15 17.752l-2.388 7.362L12 27l-1.791 3.134L8 34zm12.059-17.616l-1.965 5.897L21 26l2.792 7.625 3.552-.062S23 26.625 23 26c0-.12.625-2.687.625-2.687l1.094-4.531L25 17.25l-.941-.866z"/><path fill="#292F33" d="M36 36s-.384-3.845-.854-7.083c0 0-.146-3.885-1.271-5.167-1.049-1.195-2.624-1.875-2.833-1.962l-1.229-3.523L27.953 13l-2.422-2.125-1.844-1.626-.505-.621L18 2.25l2.289 7.299.524 1.67 3.246 5.165.66 2.397 1.438 2.281.774 2.701L28 27.5 26 34l10 2z"/><path fill="#5C903F" d="M33.708 32.438c-1.345 0-4.958.562-5.458.562-2 0-4.25-1.75-5.25-1.75S19.406 33 17.406 33c-1.688 0-2.406-.812-4.719-.812-.748 0-4.096.871-4.812.781C6.052 32.741 5.115 32 4.656 32 2 32 0 36 0 36h36s-.875-3.562-2.292-3.562z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/26f5.svg b/games/sound-soup/assets/img/26f5.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#A7A9AC" d="M20 26c0 .553-.447 1-1 1-.552 0-1-.447-1-1V1c0-.552.448-1 1-1 .553 0 1 .448 1 1v25z"/><path fill="#D1D3D4" d="M3 24h31v8H12c-6 0-9-8-9-8z"/><path fill="#55ACEE" d="M0 30h36v6H0z"/><path fill="#FFAC33" d="M5 22s2-5 5-9 8-8 8-8-1 11-1 16v1s-3-1-6-1-6 1-6 1z"/><path fill="#F4900C" d="M20 2s6 6 9 11c2.771 4.618 4 9 4 9s-3-1-6-1-6 1-6 1v-1c0-9-1-19-1-19z"/><path fill="#E6E7E8" d="M2 24c-.552 0-1 .447-1 1s.448 1 1 1h33v-2H2z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/26fa.svg b/games/sound-soup/assets/img/26fa.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#F4900C" d="M16.812 33c-.588 0-1.112-.37-1.31-.924L8.549 12.608c-.152-.426-.087-.872.173-1.242.261-.369.685-.563 1.137-.563h16.313c.557 0 1.059.305 1.279.817l8.343 19.455c.184.43.14.917-.116 1.307-.259.39-.696.618-1.163.618H16.812z"/><path fill="#FFCC4D" d="M1.515 33c-.467 0-.904-.236-1.162-.625-.256-.391-.3-.885-.116-1.315l8.348-19.479c.218-.51.722-.843 1.278-.843.557 0 1.061.333 1.279.843L19.49 31.06c.184.431.139.924-.117 1.315-.256.389-.693.625-1.161.625H1.515z"/><path fill="#292F33" d="M9.859 14.182L7.077 33h5.563z"/><path fill="#FFAC33" d="M15.46 31.456L16.081 33H12.64zm-11.203 0L3.636 33h3.441z"/><path fill="#FFE8B6" d="M12.64 33s2.529-.645 3.766-1.786L9.859 14.182 12.64 33zm-5.563 0s-2.529-.645-3.766-1.786l6.546-17.031L7.077 33z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/2702.svg b/games/sound-soup/assets/img/2702.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#DD2E44" d="M16.806 10.675c-.92-2.047-2.003-4.066-3.026-6.26-.028-.068-.051-.138-.082-.206-.064-.142-.137-.277-.208-.413l-.052-.111-.002.003C11.798.698 8.343-.674 5.46.621 2.414 1.988 1.164 5.813 2.67 9.163c1.505 3.351 5.194 4.957 8.24 3.589.106-.047.205-.105.306-.159 1.935.438 1.994 1.877 1.994 1.877s4.618-1.521 3.596-3.795zM4.876 8.173c-.958-2.133-.252-4.527 1.575-5.347 1.826-.822 4.084.242 5.042 2.374.958 2.132.253 4.526-1.573 5.346-1.828.821-4.087-.241-5.044-2.373z"/><path fill="#99AAB5" d="M26.978 34.868c1.163-.657 2.187-2.474 1.529-3.638L16.754 10.559c-1.103.496-2.938 2.313-3.544 3.912l13.768 20.397z"/><path fill="#DD2E44" d="M30.54.62c-2.882-1.295-6.338.077-7.976 3.067l-.003-.003-.053.112c-.071.135-.145.27-.208.412-.03.068-.053.137-.081.206-1.023 2.194-2.107 4.213-3.026 6.26-1.021 2.274 3.597 3.796 3.597 3.796s.059-1.439 1.993-1.877c.102.054.2.111.307.159 3.045 1.368 6.733-.238 8.24-3.589 1.505-3.35.255-7.175-2.79-8.543zm.584 7.553c-.959 2.132-3.216 3.194-5.044 2.373-1.826-.82-2.531-3.214-1.572-5.346.956-2.132 3.214-3.195 5.041-2.374 1.827.82 2.532 3.214 1.575 5.347z"/><path fill="#CCD6DD" d="M9.022 34.868c-1.163-.657-2.187-2.474-1.529-3.638l11.753-20.671c1.103.496 2.938 2.313 3.544 3.912L9.022 34.868z"/><path fill="#99AAB5" d="M19.562 17.396c0 .863-.701 1.562-1.562 1.562-.863 0-1.562-.699-1.562-1.562 0-.863.699-1.562 1.562-1.562.862 0 1.562.699 1.562 1.562z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/270b.svg b/games/sound-soup/assets/img/270b.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#EF9645" d="M20.5 2.965c-1.381 0-2.5 1.119-2.5 2.5v.005L15.5.465c-1.381 0-2.5 1.119-2.5 2.5V4.25l-2.5-1.535c-1.381 0-2.5 1.119-2.5 2.5V8.75L7 18h13.542L20.5 2.965z"/><path fill="#FFDC5D" d="M31.375 16.219c-1.381-.611-3.354.208-4.75 2.188-.917 1.3-1.187 3.151-2.391 3.344-.46.073-1.234-.313-1.234-1.397V4.5s0-2-2-2-2 2-2 2v11.633c0-.029-1-.064-1-.082V2s0-2-2-2-2 2-2 2v14.053c0 .017-1 .041-1 .069V4.25s0-2-2-2-2 2-2 2v12.638c0 .118-1 .251-1 .398V8.75s0-2-2-2-2 2-2 2V24c0 6.627 5.373 12 12 12 4.775 0 8.06-2.598 9.896-5.292 1.557-2.285 2.009-4.658 2.104-5.375 0 0 .123-1.479 1.156-2.865 1.469-1.969 2.5-3.156 3.125-3.866.317-.358.625-1.706-.906-2.383z"/><path fill="#EF9645" d="M23.439 21.471c-.297-.266-.372-.552-.417-.808-1.892.259-4.457.789-6.427 2.715-2.556 2.499-2.992 5.2-2.971 7.007.017 1.457.812 2.147 1.045-.012.292-2.706 2.254-8.063 8.784-8.58.028-.002.387-.024.591-.035 0 0-.352-.06-.605-.287z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/2728.svg b/games/sound-soup/assets/img/2728.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#FFAC33" d="M34.347 16.893l-8.899-3.294-3.323-10.891c-.128-.42-.517-.708-.956-.708-.439 0-.828.288-.956.708l-3.322 10.891-8.9 3.294c-.393.146-.653.519-.653.938 0 .418.26.793.653.938l8.895 3.293 3.324 11.223c.126.424.516.715.959.715.442 0 .833-.291.959-.716l3.324-11.223 8.896-3.293c.391-.144.652-.518.652-.937 0-.418-.261-.792-.653-.938z"/><path fill="#FFCC4D" d="M14.347 27.894l-2.314-.856-.9-3.3c-.118-.436-.513-.738-.964-.738-.451 0-.846.302-.965.737l-.9 3.3-2.313.856c-.393.145-.653.52-.653.938 0 .418.26.793.653.938l2.301.853.907 3.622c.112.444.511.756.97.756.459 0 .858-.312.97-.757l.907-3.622 2.301-.853c.393-.144.653-.519.653-.937 0-.418-.26-.793-.653-.937zM10.009 6.231l-2.364-.875-.876-2.365c-.145-.393-.519-.653-.938-.653-.418 0-.792.26-.938.653l-.875 2.365-2.365.875c-.393.146-.653.52-.653.938 0 .418.26.793.653.938l2.365.875.875 2.365c.146.393.52.653.938.653.418 0 .792-.26.938-.653l.875-2.365 2.365-.875c.393-.146.653-.52.653-.938 0-.418-.26-.792-.653-.938z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/2b50.svg b/games/sound-soup/assets/img/2b50.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#FFAC33" d="M27.287 34.627c-.404 0-.806-.124-1.152-.371L18 28.422l-8.135 5.834c-.693.496-1.623.496-2.312-.008-.689-.499-.979-1.385-.721-2.194l3.034-9.792-8.062-5.681c-.685-.505-.97-1.393-.708-2.203.264-.808 1.016-1.357 1.866-1.363L12.947 13l3.179-9.549c.268-.809 1.023-1.353 1.874-1.353.851 0 1.606.545 1.875 1.353L23 13l10.036.015c.853.006 1.606.556 1.867 1.363.263.81-.022 1.698-.708 2.203l-8.062 5.681 3.034 9.792c.26.809-.033 1.695-.72 2.194-.347.254-.753.379-1.16.379z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/img/36-20e3.svg b/games/sound-soup/assets/img/36-20e3.svg @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#3B88C3" d="M36 32c0 2.209-1.791 4-4 4H4c-2.209 0-4-1.791-4-4V4c0-2.209 1.791-4 4-4h28c2.209 0 4 1.791 4 4v28z"/><path fill="#FFF" d="M10.219 21.683c0-4.682 4.744-11.255 7.875-14.17.248-.248.62-.496.992-.744.341-.217.683-.217.931-.217.867 0 2.201 1.147 2.201 2.264 0 .434-.279.806-.651 1.271-1.24 1.519-3.255 3.503-4.279 4.961l.062.062c.558-.248 1.271-.372 1.954-.372 3.721 0 6.479 3.287 6.479 6.945 0 4.03-3.194 7.69-7.783 7.69-4.773-.001-7.781-3.566-7.781-7.69zm4.837 0c0 1.736 1.085 3.225 2.945 3.225 1.768 0 2.946-1.427 2.946-3.225 0-1.643-.992-3.225-2.946-3.225-1.767 0-2.945 1.396-2.945 3.225z"/></svg> -\ No newline at end of file diff --git a/games/sound-soup/assets/manifest.js b/games/sound-soup/assets/manifest.js @@ -1,723 +0,0 @@ -// Generated by tools/sound-soup-assets.py â do not edit by hand. -window.SS_ASSETS = { - "images": { - "1f30a": "img/1f30a.svg", - "1f319": "img/1f319.svg", - "1f31f": "img/1f31f.svg", - "1f320": "img/1f320.svg", - "1f331": "img/1f331.svg", - "1f333": "img/1f333.svg", - "1f334": "img/1f334.svg", - "1f33a": "img/1f33a.svg", - "1f33b": "img/1f33b.svg", - "1f34d": "img/1f34d.svg", - "1f356": "img/1f356.svg", - "1f360": "img/1f360.svg", - "1f371": "img/1f371.svg", - "1f372": "img/1f372.svg", - "1f373": "img/1f373.svg", - "1f37d": "img/1f37d.svg", - "1f381": "img/1f381.svg", - "1f38b": "img/1f38b.svg", - "1f3a8": "img/1f3a8.svg", - "1f3a9": "img/1f3a9.svg", - "1f3c3": "img/1f3c3.svg", - "1f3ca": "img/1f3ca.svg", - "1f3dd": "img/1f3dd.svg", - "1f3ea": "img/1f3ea.svg", - "1f3ee": "img/1f3ee.svg", - "1f400": "img/1f400.svg", - "1f409": "img/1f409.svg", - "1f40f": "img/1f40f.svg", - "1f410": "img/1f410.svg", - "1f411": "img/1f411.svg", - "1f414": "img/1f414.svg", - "1f419": "img/1f419.svg", - "1f41a": "img/1f41a.svg", - "1f41b": "img/1f41b.svg", - "1f41c": "img/1f41c.svg", - "1f41d": "img/1f41d.svg", - "1f41f": "img/1f41f.svg", - "1f422": "img/1f422.svg", - "1f423": "img/1f423.svg", - "1f424": "img/1f424.svg", - "1f425": "img/1f425.svg", - "1f426": "img/1f426.svg", - "1f427": "img/1f427.svg", - "1f428": "img/1f428.svg", - "1f42c": "img/1f42c.svg", - "1f42e": "img/1f42e.svg", - "1f42f": "img/1f42f.svg", - "1f430": "img/1f430.svg", - "1f431": "img/1f431.svg", - "1f436": "img/1f436.svg", - "1f437": "img/1f437.svg", - "1f438": "img/1f438.svg", - "1f43a": "img/1f43a.svg", - "1f43b": "img/1f43b.svg", - "1f43c": "img/1f43c.svg", - "1f444": "img/1f444.svg", - "1f456": "img/1f456.svg", - "1f457": "img/1f457.svg", - "1f45c": "img/1f45c.svg", - "1f466": "img/1f466.svg", - "1f467": "img/1f467.svg", - "1f468": "img/1f468.svg", - "1f468-200d-1f467": "img/1f468-200d-1f467.svg", - "1f469": "img/1f469.svg", - "1f469-200d-1f467": "img/1f469-200d-1f467.svg", - "1f474": "img/1f474.svg", - "1f475": "img/1f475.svg", - "1f47d": "img/1f47d.svg", - "1f48d": "img/1f48d.svg", - "1f48e": "img/1f48e.svg", - "1f490": "img/1f490.svg", - "1f4a8": "img/1f4a8.svg", - "1f4cc": "img/1f4cc.svg", - "1f4d6": "img/1f4d6.svg", - "1f4e6": "img/1f4e6.svg", - "1f50c": "img/1f50c.svg", - "1f512": "img/1f512.svg", - "1f514": "img/1f514.svg", - "1f51f": "img/1f51f.svg", - "1f578": "img/1f578.svg", - "1f58a": "img/1f58a.svg", - "1f58c": "img/1f58c.svg", - "1f5d1": "img/1f5d1.svg", - "1f5fa": "img/1f5fa.svg", - "1f60b": "img/1f60b.svg", - "1f620": "img/1f620.svg", - "1f622": "img/1f622.svg", - "1f634": "img/1f634.svg", - "1f680": "img/1f680.svg", - "1f68c": "img/1f68c.svg", - "1f690": "img/1f690.svg", - "1f695": "img/1f695.svg", - "1f69a": "img/1f69a.svg", - "1f6a2": "img/1f6a2.svg", - "1f6a4": "img/1f6a4.svg", - "1f6a9": "img/1f6a9.svg", - "1f6b0": "img/1f6b0.svg", - "1f6c1": "img/1f6c1.svg", - "1f6cf": "img/1f6cf.svg", - "1f6d6": "img/1f6d6.svg", - "1f6e3": "img/1f6e3.svg", - "1f6e9": "img/1f6e9.svg", - "1f6f6": "img/1f6f6.svg", - "1f6f7": "img/1f6f7.svg", - "1f7e5": "img/1f7e5.svg", - "1f7e9": "img/1f7e9.svg", - "1f917": "img/1f917.svg", - "1f934": "img/1f934.svg", - "1f941": "img/1f941.svg", - "1f944": "img/1f944.svg", - "1f945": "img/1f945.svg", - "1f95a": "img/1f95a.svg", - "1f95b": "img/1f95b.svg", - "1f95c": "img/1f95c.svg", - "1f963": "img/1f963.svg", - "1f964": "img/1f964.svg", - "1f965": "img/1f965.svg", - "1f96b": "img/1f96b.svg", - "1f975": "img/1f975.svg", - "1f97e": "img/1f97e.svg", - "1f980": "img/1f980.svg", - "1f981": "img/1f981.svg", - "1f986": "img/1f986.svg", - "1f987": "img/1f987.svg", - "1f989": "img/1f989.svg", - "1f98a": "img/1f98a.svg", - "1f98e": "img/1f98e.svg", - "1f99c": "img/1f99c.svg", - "1f9a9": "img/1f9a9.svg", - "1f9ab": "img/1f9ab.svg", - "1f9b5": "img/1f9b5.svg", - "1f9b6": "img/1f9b6.svg", - "1f9b7": "img/1f9b7.svg", - "1f9c2": "img/1f9c2.svg", - "1f9d1-200d-1f373": "img/1f9d1-200d-1f373.svg", - "1f9d2": "img/1f9d2.svg", - "1f9dc-200d-2640-fe0f": "img/1f9dc-200d-2640-fe0f.svg", - "1f9de": "img/1f9de.svg", - "1f9e2": "img/1f9e2.svg", - "1f9e5": "img/1f9e5.svg", - "1f9e6": "img/1f9e6.svg", - "1f9ed": "img/1f9ed.svg", - "1f9f1": "img/1f9f1.svg", - "1f9f8": "img/1f9f8.svg", - "1f9fc": "img/1f9fc.svg", - "1fa93": "img/1fa93.svg", - "1fa9d": "img/1fa9d.svg", - "1faa8": "img/1faa8.svg", - "1faad": "img/1faad.svg", - "1fab4": "img/1fab4.svg", - "1fab5": "img/1fab5.svg", - "1fab9": "img/1fab9.svg", - "1fad9": "img/1fad9.svg", - "23f0": "img/23f0.svg", - "2600": "img/2600.svg", - "2601": "img/2601.svg", - "2615": "img/2615.svg", - "26a1": "img/26a1.svg", - "26f0": "img/26f0.svg", - "26f5": "img/26f5.svg", - "26fa": "img/26fa.svg", - "2702": "img/2702.svg", - "270b": "img/270b.svg", - "2728": "img/2728.svg", - "2b50": "img/2b50.svg", - "36-20e3": "img/36-20e3.svg" - }, - "phonemes": { - "a": { - "dur": 0.45, - "file": "phonemes/a.mp3" - }, - "b": { - "dur": 0.06, - "file": "phonemes/b.mp3" - }, - "ch": { - "dur": 0.13, - "file": "phonemes/ch.mp3" - }, - "d": { - "dur": 0.06, - "file": "phonemes/d.mp3" - }, - "e": { - "dur": 0.45, - "file": "phonemes/e.mp3" - }, - "ee": { - "dur": 0.45, - "file": "phonemes/ee.mp3" - }, - "f": { - "dur": 0.36, - "file": "phonemes/f.mp3" - }, - "g": { - "dur": 0.06, - "file": "phonemes/g.mp3" - }, - "h": { - "dur": 0.26, - "file": "phonemes/h.mp3" - }, - "i": { - "dur": 0.45, - "file": "phonemes/i.mp3" - }, - "j": { - "dur": 0.13, - "file": "phonemes/j.mp3" - }, - "k": { - "dur": 0.06, - "file": "phonemes/k.mp3" - }, - "l": { - "dur": 0.29, - "file": "phonemes/l.mp3" - }, - "m": { - "dur": 0.18, - "file": "phonemes/m.mp3" - }, - "n": { - "dur": 0.25, - "file": "phonemes/n.mp3" - }, - "ng": { - "dur": 0.14, - "file": "phonemes/ng.mp3" - }, - "o": { - "dur": 0.45, - "file": "phonemes/o.mp3" - }, - "oa": { - "dur": 0.45, - "file": "phonemes/oa.mp3" - }, - "oo": { - "dur": 0.45, - "file": "phonemes/oo.mp3" - }, - "p": { - "dur": 0.06, - "file": "phonemes/p.mp3" - }, - "r": { - "dur": 0.2, - "file": "phonemes/r.mp3" - }, - "s": { - "dur": 0.21, - "file": "phonemes/s.mp3" - }, - "sh": { - "dur": 0.27, - "file": "phonemes/sh.mp3" - }, - "t": { - "dur": 0.06, - "file": "phonemes/t.mp3" - }, - "th": { - "dur": 0.21, - "file": "phonemes/th.mp3" - }, - "u": { - "dur": 0.45, - "file": "phonemes/u.mp3" - }, - "uu": { - "dur": 0.45, - "file": "phonemes/uu.mp3" - }, - "v": { - "dur": 0.18, - "file": "phonemes/v.mp3" - }, - "w": { - "dur": 0.21, - "file": "phonemes/w.mp3" - }, - "y": { - "dur": 0.19, - "file": "phonemes/y.mp3" - }, - "z": { - "dur": 0.22, - "file": "phonemes/z.mp3" - } - }, - "words": { - "ant": { - "dur": 0.62, - "file": "words/ant.mp3" - }, - "bag": { - "dur": 0.47, - "file": "words/bag.mp3" - }, - "bat": { - "dur": 0.4, - "file": "words/bat.mp3" - }, - "bath": { - "dur": 0.5, - "file": "words/bath.mp3" - }, - "bed": { - "dur": 0.4, - "file": "words/bed.mp3" - }, - "bee": { - "dur": 0.51, - "file": "words/bee.mp3" - }, - "bell": { - "dur": 0.36, - "file": "words/bell.mp3" - }, - "bin": { - "dur": 1.01, - "file": "words/bin.mp3" - }, - "boat": { - "dur": 0.38, - "file": "words/boat.mp3" - }, - "book": { - "dur": 0.39, - "file": "words/book.mp3" - }, - "boot": { - "dur": 0.4, - "file": "words/boot.mp3" - }, - "box": { - "dur": 0.5, - "file": "words/box.mp3" - }, - "brick": { - "dur": 0.44, - "file": "words/brick.mp3" - }, - "brush": { - "dur": 0.46, - "file": "words/brush.mp3" - }, - "bug": { - "dur": 0.35, - "file": "words/bug.mp3" - }, - "bus": { - "dur": 0.77, - "file": "words/bus.mp3" - }, - "cab": { - "dur": 0.55, - "file": "words/cab.mp3" - }, - "can": { - "dur": 0.5, - "file": "words/can.mp3" - }, - "cap": { - "dur": 0.47, - "file": "words/cap.mp3" - }, - "cat": { - "dur": 0.52, - "file": "words/cat.mp3" - }, - "chick": { - "dur": 0.53, - "file": "words/chick.mp3" - }, - "clock": { - "dur": 0.48, - "file": "words/clock.mp3" - }, - "coat": { - "dur": 0.44, - "file": "words/coat.mp3" - }, - "cook": { - "dur": 0.35, - "file": "words/cook.mp3" - }, - "crab": { - "dur": 0.53, - "file": "words/crab.mp3" - }, - "cup": { - "dur": 0.43, - "file": "words/cup.mp3" - }, - "cut": { - "dur": 0.43, - "file": "words/cut.mp3" - }, - "dad": { - "dur": 0.49, - "file": "words/dad.mp3" - }, - "dish": { - "dur": 0.45, - "file": "words/dish.mp3" - }, - "dog": { - "dur": 0.45, - "file": "words/dog.mp3" - }, - "dress": { - "dur": 0.62, - "file": "words/dress.mp3" - }, - "drum": { - "dur": 0.44, - "file": "words/drum.mp3" - }, - "duck": { - "dur": 0.55, - "file": "words/duck.mp3" - }, - "egg": { - "dur": 0.39, - "file": "words/egg.mp3" - }, - "feet": { - "dur": 0.51, - "file": "words/feet.mp3" - }, - "fish": { - "dur": 0.57, - "file": "words/fish.mp3" - }, - "flag": { - "dur": 0.71, - "file": "words/flag.mp3" - }, - "fox": { - "dur": 0.57, - "file": "words/fox.mp3" - }, - "frog": { - "dur": 0.51, - "file": "words/frog.mp3" - }, - "gem": { - "dur": 0.47, - "file": "words/gem.mp3" - }, - "gift": { - "dur": 0.47, - "file": "words/gift.mp3" - }, - "goat": { - "dur": 0.4, - "file": "words/goat.mp3" - }, - "green": { - "dur": 0.5, - "file": "words/green.mp3" - }, - "ham": { - "dur": 0.64, - "file": "words/ham.mp3" - }, - "hand": { - "dur": 0.54, - "file": "words/hand.mp3" - }, - "hat": { - "dur": 0.42, - "file": "words/hat.mp3" - }, - "hen": { - "dur": 0.4, - "file": "words/hen.mp3" - }, - "hill": { - "dur": 0.35, - "file": "words/hill.mp3" - }, - "hook": { - "dur": 0.42, - "file": "words/hook.mp3" - }, - "hot": { - "dur": 0.44, - "file": "words/hot.mp3" - }, - "hug": { - "dur": 0.46, - "file": "words/hug.mp3" - }, - "hut": { - "dur": 0.36, - "file": "words/hut.mp3" - }, - "jam": { - "dur": 0.48, - "file": "words/jam.mp3" - }, - "kid": { - "dur": 0.46, - "file": "words/kid.mp3" - }, - "king": { - "dur": 0.68, - "file": "words/king.mp3" - }, - "leg": { - "dur": 0.47, - "file": "words/leg.mp3" - }, - "lip": { - "dur": 0.4, - "file": "words/lip.mp3" - }, - "lock": { - "dur": 0.46, - "file": "words/lock.mp3" - }, - "log": { - "dur": 0.53, - "file": "words/log.mp3" - }, - "lunch": { - "dur": 0.59, - "file": "words/lunch.mp3" - }, - "mad": { - "dur": 0.51, - "file": "words/mad.mp3" - }, - "map": { - "dur": 0.48, - "file": "words/map.mp3" - }, - "milk": { - "dur": 0.78, - "file": "words/milk.mp3" - }, - "mom": { - "dur": 0.49, - "file": "words/mom.mp3" - }, - "moon": { - "dur": 0.53, - "file": "words/moon.mp3" - }, - "mug": { - "dur": 0.47, - "file": "words/mug.mp3" - }, - "nest": { - "dur": 1.1, - "file": "words/nest.mp3" - }, - "net": { - "dur": 0.43, - "file": "words/net.mp3" - }, - "nut": { - "dur": 0.44, - "file": "words/nut.mp3" - }, - "pan": { - "dur": 0.46, - "file": "words/pan.mp3" - }, - "pants": { - "dur": 0.54, - "file": "words/pants.mp3" - }, - "pen": { - "dur": 0.65, - "file": "words/pen.mp3" - }, - "pig": { - "dur": 0.46, - "file": "words/pig.mp3" - }, - "pin": { - "dur": 0.55, - "file": "words/pin.mp3" - }, - "plant": { - "dur": 0.61, - "file": "words/plant.mp3" - }, - "plug": { - "dur": 0.48, - "file": "words/plug.mp3" - }, - "pot": { - "dur": 0.49, - "file": "words/pot.mp3" - }, - "ram": { - "dur": 0.48, - "file": "words/ram.mp3" - }, - "rat": { - "dur": 0.44, - "file": "words/rat.mp3" - }, - "red": { - "dur": 0.44, - "file": "words/red.mp3" - }, - "ring": { - "dur": 0.49, - "file": "words/ring.mp3" - }, - "road": { - "dur": 0.51, - "file": "words/road.mp3" - }, - "rock": { - "dur": 0.45, - "file": "words/rock.mp3" - }, - "run": { - "dur": 0.46, - "file": "words/run.mp3" - }, - "sad": { - "dur": 0.56, - "file": "words/sad.mp3" - }, - "seed": { - "dur": 0.64, - "file": "words/seed.mp3" - }, - "sheep": { - "dur": 0.63, - "file": "words/sheep.mp3" - }, - "shell": { - "dur": 0.49, - "file": "words/shell.mp3" - }, - "ship": { - "dur": 0.44, - "file": "words/ship.mp3" - }, - "shop": { - "dur": 0.52, - "file": "words/shop.mp3" - }, - "six": { - "dur": 0.67, - "file": "words/six.mp3" - }, - "soap": { - "dur": 0.5, - "file": "words/soap.mp3" - }, - "sock": { - "dur": 0.56, - "file": "words/sock.mp3" - }, - "spoon": { - "dur": 0.73, - "file": "words/spoon.mp3" - }, - "sun": { - "dur": 0.53, - "file": "words/sun.mp3" - }, - "swim": { - "dur": 0.58, - "file": "words/swim.mp3" - }, - "tap": { - "dur": 0.4, - "file": "words/tap.mp3" - }, - "teeth": { - "dur": 0.57, - "file": "words/teeth.mp3" - }, - "ten": { - "dur": 0.41, - "file": "words/ten.mp3" - }, - "tent": { - "dur": 0.52, - "file": "words/tent.mp3" - }, - "tree": { - "dur": 0.46, - "file": "words/tree.mp3" - }, - "truck": { - "dur": 0.39, - "file": "words/truck.mp3" - }, - "van": { - "dur": 0.49, - "file": "words/van.mp3" - }, - "web": { - "dur": 0.46, - "file": "words/web.mp3" - }, - "yam": { - "dur": 0.72, - "file": "words/yam.mp3" - }, - "zap": { - "dur": 0.78, - "file": "words/zap.mp3" - } - } -}; diff --git a/games/sound-soup/audio.js b/games/sound-soup/audio.js @@ -1,258 +0,0 @@ -// Sound Soup â audio: recorded phonemes + words (Web Audio), speech synthesis -// for everything else, and a few oscillator SFX. -// -// Recorded clips are listed in assets/manifest.js (generated by -// tools/sound-soup-assets.py). A word with no recording is spoken by the -// browser instead; phonemes are never synthesised (a synthetic "suh" would -// break blending), so a phoneme with no clip is simply silent. -(function (root) { - 'use strict'; - - const SS = root.SS; - const TEST = !!root.__TEST_MODE__; - const scale = (ms) => (TEST ? ms * 0.1 : ms); // test mode runs every wait at 10% speed - - class SoundSoupAudio { - constructor(assets, base) { - this.assets = assets || { phonemes: {}, words: {} }; - this.base = base || 'assets/audio/'; - this.ctx = null; - this.ready = false; - this.buffers = {}; // key -> { buffer, offset } | null (failed) - this.pending = {}; // key -> Promise - this.log = []; // every clip played, for tests: { kind, id, t } - this.voiceMuted = false; - this.sfxMuted = false; - this._voice = null; - this._unlock = null; - this._active = []; // scheduled sources, so a blend can be cancelled - this._timers = []; - } - - // Create the context and preload the phoneme set. Safe to call repeatedly. - init() { - if (this.ctx) return; - const AC = root.AudioContext || root.webkitAudioContext; - if (!AC) return; - try { this.ctx = new AC(); } catch (_) { return; } - this.master = this.ctx.createGain(); - this.master.connect(this.ctx.destination); - if (root.KGames && root.KGames.installAudioUnlock) { - this._unlock = root.KGames.installAudioUnlock(() => [this.ctx], () => { this.ready = true; }); - } else { - this.ready = true; - } - Object.keys(this.assets.phonemes || {}).forEach((id) => this.load('ph:' + id, this.assets.phonemes[id].file)); - } - - // ââ loading ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ - - load(key, file) { - if (this.buffers[key] !== undefined) return Promise.resolve(this.buffers[key]); - if (this.pending[key]) return this.pending[key]; - const url = this.base + file; - const p = fetch(url) - .then((r) => { if (!r.ok) throw new Error(r.status); return r.arrayBuffer(); }) - .then((ab) => new Promise((res, rej) => this.ctx.decodeAudioData(ab, res, rej))) - .then((buffer) => { - // Skip the encoder's leading silence so blends stay tight. - const d = buffer.getChannelData(0); - let i = 0; - while (i < d.length && Math.abs(d[i]) < 0.012) i++; - const entry = { buffer, offset: Math.max(0, i - 40) / buffer.sampleRate }; - this.buffers[key] = entry; - return entry; - }) - .catch(() => { this.buffers[key] = null; return null; }); - this.pending[key] = p; - return p; - } - - hasWord(w) { return !!(this.assets.words && this.assets.words[w]); } - hasPhoneme(p) { return !!(this.assets.phonemes && this.assets.phonemes[p]); } - - // Effective playable length of a clip in seconds (for timeline planning). - phonemeDuration(p) { - const e = this.buffers['ph:' + p]; - if (e) return e.buffer.duration - e.offset; - const m = this.assets.phonemes && this.assets.phonemes[p]; - return m ? m.dur : 0.25; - } - phonemeDurations() { - const out = {}; - Object.keys(SS.PHONEMES).forEach((p) => { out[p] = this.phonemeDuration(p); }); - return out; - } - - preloadWord(w) { - if (!this.ctx || !this.hasWord(w)) return Promise.resolve(null); - return this.load('w:' + w, this.assets.words[w].file); - } - - // ââ playback âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ - - _now() { return this.ctx ? this.ctx.currentTime : 0; } - - // Play one recorded phoneme. hold > 0 sustains a continuant / vowel by - // looping its steady middle. Returns the seconds it will take. - playPhoneme(id, opts) { - opts = opts || {}; - const hold = opts.hold || 0; - const type = (SS.PHONEMES[id] || {}).type; - // `at` is when the clip will sound (log order is schedule order, not play order) - this.log.push({ kind: 'phoneme', id, hold, t: Date.now(), at: Date.now() + scale((opts.delay || 0) * 1000) }); - if (!this.ctx) return 0.25; - const entry = this.buffers['ph:' + id]; - const at = this._now() + (opts.delay || 0); - if (!entry) return this.phonemeDuration(id) + (type === 'stop' ? 0 : hold); - const src = this.ctx.createBufferSource(); - src.buffer = entry.buffer; - const g = this.ctx.createGain(); - src.connect(g); g.connect(this.master); - const base = entry.buffer.duration - entry.offset; - let total = base; - if (hold > 0 && type !== 'stop' && base > 0.12) { - src.loop = true; - src.loopStart = entry.offset + base * 0.3; - src.loopEnd = entry.offset + base * 0.7; - total = base + hold; - } - g.gain.setValueAtTime(1, at); - g.gain.setValueAtTime(1, at + Math.max(0, total - 0.035)); - g.gain.linearRampToValueAtTime(0.0001, at + total); - src.start(at, entry.offset); - src.stop(at + total + 0.01); - this._active.push(src); - return total; - } - - // Play the recorded word, or speak it. Resolves when it is (about) done. - playWord(w, opts) { - opts = opts || {}; - this.log.push({ kind: 'word', id: w, t: Date.now(), at: Date.now() + scale((opts.delay || 0) * 1000) }); - if (this.ctx && this.hasWord(w)) { - return this.preloadWord(w).then((entry) => { - if (!entry) return this.speak(w, opts.voice); - const at = this._now() + (opts.delay || 0); - const src = this.ctx.createBufferSource(); - src.buffer = entry.buffer; - src.connect(this.master); - src.start(at, entry.offset); - this._active.push(src); - const dur = entry.buffer.duration - entry.offset; - return this._sleep(((opts.delay || 0) + dur) * 1000); - }); - } - return this.speak(w, opts.voice); - } - - // All the sounds of a word with clear gaps (used to re-demonstrate). - playSegmented(units, opts) { - opts = opts || {}; - const gap = opts.gap == null ? 0.35 : opts.gap; - let t = 0; - units.forEach((unit, ui) => { - unit.forEach((p) => { t += this.playPhoneme(p, { delay: t, hold: opts.hold || 0.2 }) + 0.02; }); - if (ui < units.length - 1) t += gap; - if (opts.onUnit) this._timer(() => opts.onUnit(ui), (t - gap) * 1000); - }); - return this._sleep(t * 1000); - } - - // The stir: run SS.blendTimeline and fire onEvent for each event at the - // right moment (visuals hang off these). Resolves after the word plays. - blend(word, units, onEvent) { - const durations = this.phonemeDurations(); - const events = SS.blendTimeline(units, durations); - const t0 = Date.now(); - let last = 0; - events.forEach((ev) => { - if (ev.kind === 'phoneme') this.playPhoneme(ev.id, { delay: ev.t, hold: ev.len - durations[ev.id] }); - if (ev.kind === 'word') this._timer(() => this.playWord(word), ev.t * 1000); - if (onEvent) this._timer(() => onEvent(ev), ev.t * 1000); - last = ev.t; - }); - const wordLen = this.assets.words && this.assets.words[word] ? this.assets.words[word].dur : 0.7; - return this._sleep((last + wordLen + 0.15) * 1000).then(() => events); - } - - // Stop every scheduled clip and pending callback (e.g. leaving a screen). - cancel() { - this._active.forEach((s) => { try { s.stop(); } catch (_) {} }); - this._active = []; - this._timers.forEach(clearTimeout); - this._timers = []; - try { if (root.speechSynthesis) root.speechSynthesis.cancel(); } catch (_) {} - } - - // ââ speech synthesis âââââââââââââââââââââââââââââââââââââââââââââââââââ - - _pickVoice() { - if (this._voice || !root.speechSynthesis) return this._voice; - let voices = []; - try { voices = root.speechSynthesis.getVoices() || []; } catch (_) {} - const en = voices.filter((v) => /^en[-_]/i.test(v.lang)); - const prefer = en.find((v) => /child|kid|samantha|karen|moira|google us english/i.test(v.name)); - this._voice = prefer || en.find((v) => /^en[-_]US/i.test(v.lang)) || en[0] || null; - return this._voice; - } - - // Speak text. Resolves when finished, or after an estimate if the browser - // never reports the end (headless / no voices installed). - speak(text, voice) { - this.log.push({ kind: 'speak', id: text, t: Date.now(), at: Date.now() }); - const est = Math.min(6000, 350 + 65 * text.length); - if (TEST) return this._sleep(0); - if (this.voiceMuted) return this._sleep(150); // muted: keep the flow moving - if (!root.speechSynthesis) return this._sleep(est); // no TTS at all: leave time to read - return new Promise((resolve) => { - let done = false; - const finish = () => { if (!done) { done = true; resolve(); } }; - try { - const u = new SpeechSynthesisUtterance(text); - const v = this._pickVoice(); - if (v) u.voice = v; - u.lang = (v && v.lang) || 'en-US'; - u.pitch = voice && voice.pitch != null ? voice.pitch : 1.05; - u.rate = voice && voice.rate != null ? voice.rate : 0.95; - u.onend = finish; u.onerror = finish; - root.speechSynthesis.cancel(); - root.speechSynthesis.speak(u); - setTimeout(finish, est + 2500); - } catch (_) { finish(); } - }); - } - - // ââ SFX (oscillators; incidental, not the taught audio) âââââââââââââââ - - _tone(freq, dur, type, gain, when, slideTo) { - if (!this.ctx || this.sfxMuted) return; - const at = this._now() + (when || 0); - const o = this.ctx.createOscillator(); - const g = this.ctx.createGain(); - o.type = type || 'sine'; - o.frequency.setValueAtTime(freq, at); - if (slideTo) o.frequency.exponentialRampToValueAtTime(slideTo, at + dur); - g.gain.setValueAtTime(0.0001, at); - g.gain.exponentialRampToValueAtTime(gain || 0.15, at + 0.01); - g.gain.exponentialRampToValueAtTime(0.0001, at + dur); - o.connect(g); g.connect(this.master); - o.start(at); o.stop(at + dur + 0.02); - } - plop() { this._tone(320, 0.18, 'sine', 0.2, 0, 140); } - bubble() { this._tone(500 + Math.random() * 400, 0.12, 'sine', 0.06, 0, 900); } - tick() { this._tone(900, 0.05, 'square', 0.04); } - wrong() { this._tone(200, 0.25, 'triangle', 0.12, 0, 150); } - ding() { this._tone(880, 0.35, 'sine', 0.12); this._tone(1320, 0.5, 'sine', 0.08, 0.08); } - sparkle() { [0, 0.07, 0.14, 0.21].forEach((d, i) => this._tone(1200 + i * 300, 0.25, 'sine', 0.07, d)); } - fanfare() { [523, 659, 784, 1046].forEach((f, i) => this._tone(f, 0.3, 'triangle', 0.1, i * 0.11)); } - whoosh() { this._tone(300, 0.3, 'sawtooth', 0.03, 0, 1200); } - stirSwirl(){ [0, 0.15, 0.3].forEach((d) => this._tone(220, 0.25, 'sine', 0.05, d, 330)); } - - // ââ utils ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ - _timer(fn, ms) { const id = setTimeout(fn, scale(ms)); this._timers.push(id); return id; } - _sleep(ms) { return new Promise((r) => this._timer(r, ms)); } - } - - root.SoundSoupAudio = SoundSoupAudio; -}(window)); diff --git a/games/sound-soup/game.js b/games/sound-soup/game.js @@ -1,722 +0,0 @@ -// Sound Soup â the game. Plain DOM + CSS animations, keyboard only. -// -// A customer walks up to the soup stall and orders a word. In "cook" mode the -// word is printed in sound boxes and the child presses each letter; in -// "order" mode the customer says the word and the child picks the sounds by -// ear. Every accepted sound drops into the pot; SPACE stirs, which plays the -// blend ramp (sounds with shrinking gaps until they fuse into the word). -// Pure logic lives in lib.js, audio in audio.js; this file is UI + flow. -// -// Wall-clock rule: every delay is a setTimeout so the flow keeps moving in -// throttled / headless browsers. window.__TEST_MODE__ shortens them all. -(function (root) { - 'use strict'; - - const SS = root.SS; - const ASSETS = root.SS_ASSETS || { phonemes: {}, words: {}, images: {} }; - const TEST = !!root.__TEST_MODE__; - - const T = (ms) => (TEST ? Math.min(ms, 15) : ms); - const sleep = (ms) => new Promise((r) => setTimeout(r, T(ms))); - const img = (cp, cls) => '<img class="ss-img ' + (cls || '') + '" src="assets/img/' + cp + '.svg" alt="" draggable="false">'; - const esc = (s) => String(s).replace(/[&<>"]/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"' }[c])); - const pick = (arr) => arr[Math.floor(Math.random() * arr.length)]; - const cap = (s) => s.charAt(0).toUpperCase() + s.slice(1); - const BLOB_COLORS = ['#E63946', '#1D7CF2', '#2EC4B6', '#9B5DE5', '#F4A261', '#06A77D']; - const WALKERS = ['1f9d2', '1f467', '1f466', '1f469', '1f468', '1f475', '1f474', '1f427', '1f43c', '1f981', '1f42f', '1f43b', '1f9ab', '1f425', '1f428', '1f42e', '1f430']; - const DECOR = { - lanterns: '<span class="d-lantern1">' + img('1f3ee') + '</span><span class="d-lantern2">' + img('1f3ee') + '</span>', - flowers: '<span class="d-flower1">' + img('1f33a') + '</span><span class="d-flower2">' + img('1f33b') + '</span><span class="d-flower3">' + img('1f33a') + '</span>', - bunting: '<div class="d-bunting"></div>', - cat: '<span class="d-cat">' + img('1f431') + '</span>', - lights: '<div class="d-lights"></div>', - fountain: '<span class="d-fountain">' + img('1f30a') + '</span>', - parrot: '<span class="d-parrot">' + img('1f99c') + '</span>', - }; - - const LINES = { - hello: ['Hello!', 'Hi there!', 'Good morning!', 'Ahoy!', 'Yoo-hoo!', 'Hello, chef!'], - again: ['It is me again!', 'I am back!', 'Remember me?', 'Back for more!'], - cookOrder: ['Can you cook this for me?', 'One of these, please!', 'This is what I would like.', 'I would love one of these!'], - orderIntro: ['I would like a', 'Could I have a', 'Please may I have a', 'Today I want a'], - happy: ['Yum! Thank you!', 'Perfect! Just what I wanted!', 'Delicious!', 'Oh, wonderful!', 'You are the best cook on the island!', 'Mmm, my favourite!'], - wrongDish: ['Hmm, that is not it.', 'Oops! Not that one.', 'Not quite! Try again.'], - instrCook: 'Sound it out! Press each letter.', - instrOrder: 'Which sounds do you hear? Press the letters.', - stir: 'Now press space to stir the soup!', - choose: 'What did you make? Pick the picture.', - }; - - class SoundSoup { - constructor(container) { - this.container = container; - this.storage = null; - try { this.storage = root.localStorage; } catch (_) {} - this.storage = this.storage || { getItem() { return null; }, setItem() {}, removeItem() {} }; - this.store = SS.loadStore(this.storage); - this.profile = null; - this.audio = new root.SoundSoupAudio(ASSETS, 'assets/audio/'); - this.state = 'boot'; - this.busy = false; - this.order = null; - this.sel = 0; - this.ordersToday = 0; - this.todayWords = []; - this.todaySounds = []; - this.applied = {}; - this._build(); - this._bindKeys(); - this._watchSize(); - this.showProfiles(); - if (!TEST) this._ambient(); - } - - now() { return root.__SS_NOW__ || Date.now(); } - save() { SS.saveStore(this.storage, this.store); } - - // ââ DOM ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ - - _build() { - const r = document.createElement('div'); - r.className = 'ss-root' + (TEST ? ' ss-test' : ''); - r.innerHTML = - '<div class="ss-layer ss-sky"><div class="ss-sun"></div><div class="ss-cloud"></div><div class="ss-cloud c2"></div>' + - '<span class="ss-bird">' + img('1f426') + '</span><span class="ss-bird k2">' + img('1f426') + '</span></div>' + - '<div class="ss-sea"><span class="ss-boat">' + img('26f5') + '</span><span class="ss-boat b2">' + img('1f6a4') + '</span></div>' + - '<div class="ss-street"><div class="ss-houses"><div class="ss-house h1"></div><div class="ss-house h2"></div><div class="ss-house h3"></div>' + - '<div class="ss-house h4"></div><div class="ss-house h5"></div><div class="ss-house h6"></div></div>' + - '<span class="ss-palm p1">' + img('1f334') + '</span><span class="ss-palm p2">' + img('1f334') + '</span><div class="ss-walkers"></div></div>' + - '<div class="ss-customer"><div class="ss-bubble"></div><div class="ss-cust-body"></div><div class="ss-cust-name"></div></div>' + - '<div class="ss-stall"><div class="ss-poles"></div><div class="ss-awning"></div><div class="ss-sign">Sound Soup</div><div class="ss-decor"></div>' + - '<div class="ss-counter"></div>' + - '<div class="ss-pot"><div class="ss-steam"><div class="ss-puff" style="left:10%"></div><div class="ss-puff" style="left:45%;animation-delay:-0.9s"></div><div class="ss-puff" style="left:75%;animation-delay:-1.7s"></div></div>' + - '<div class="ss-pot-body"><div class="ss-soup"><div class="ss-bub" style="left:20%"></div><div class="ss-bub" style="left:50%;animation-delay:-0.6s"></div><div class="ss-bub" style="left:75%;animation-delay:-1.1s"></div></div></div>' + - '<div class="ss-blobs"></div><span class="ss-spoon">' + img('1f944') + '</span></div>' + - '<div class="ss-dish"></div></div>' + - '<div class="ss-boxes"></div><div class="ss-prompt"></div><div class="ss-choices"></div>' + - '<div class="ss-hud"><div class="ss-chip ss-who"></div><div class="ss-chip right ss-recipes-chip"></div></div>' + - '<div class="ss-day"></div>' + - '<div class="ss-overlay" hidden><div class="ss-panel"></div></div>'; - this.container.appendChild(r); - this.$ = (sel) => r.querySelector(sel); - this.root = r; - this.$boxes = this.$('.ss-boxes'); this.$prompt = this.$('.ss-prompt'); this.$choices = this.$('.ss-choices'); - this.$customer = this.$('.ss-customer'); this.$bubble = this.$('.ss-bubble'); this.$custBody = this.$('.ss-cust-body'); - this.$custName = this.$('.ss-cust-name'); this.$pot = this.$('.ss-pot'); this.$blobs = this.$('.ss-blobs'); - this.$dish = this.$('.ss-dish'); this.$overlay = this.$('.ss-overlay'); this.$panel = this.$('.ss-panel'); - this.$who = this.$('.ss-who'); this.$recipesChip = this.$('.ss-recipes-chip'); this.$day = this.$('.ss-day'); - this.$decor = this.$('.ss-decor'); this.$walkers = this.$('.ss-walkers'); - - const btnV = document.getElementById('btn-voice'), btnS = document.getElementById('btn-sfx'); - if (btnV) btnV.addEventListener('click', () => { this.audio.voiceMuted = !this.audio.voiceMuted; btnV.classList.toggle('muted', this.audio.voiceMuted); btnV.textContent = 'Speech: ' + (this.audio.voiceMuted ? 'OFF' : 'ON'); if (this.audio.voiceMuted) this.audio.cancel(); }); - if (btnS) btnS.addEventListener('click', () => { this.audio.sfxMuted = !this.audio.sfxMuted; btnS.classList.toggle('muted', this.audio.sfxMuted); btnS.textContent = 'SFX: ' + (this.audio.sfxMuted ? 'OFF' : 'ON'); }); - } - - _watchSize() { - const apply = () => { - const w = this.container.clientWidth || 800, h = this.container.clientHeight || 600; - this.root.style.setProperty('--u', (Math.min(w, h * 1.25) / 100) + 'px'); - }; - apply(); - if (root.ResizeObserver) { try { new ResizeObserver(apply).observe(this.container); } catch (_) {} } - root.addEventListener('resize', apply); - } - - _ambient() { - const spawn = () => { - if (document.visibilityState === 'hidden') return; - const el = document.createElement('span'); - el.className = 'ss-walker'; - const rev = Math.random() < 0.5; - el.style.left = rev ? '100%' : '-10%'; - el.style.transform = 'translateX(0)'; - el.innerHTML = img(pick(WALKERS)); - this.$walkers.appendChild(el); - requestAnimationFrame(() => requestAnimationFrame(() => { el.style.transform = 'translateX(' + (rev ? '-115vw' : '115vw') + ')'; })); - setTimeout(() => el.remove(), 15000); - }; - setTimeout(spawn, 2500); - setInterval(spawn, 11000 + Math.random() * 6000); - } - - setPrompt(html) { this.$prompt.innerHTML = html || ''; } - - toast(text) { - const t = document.createElement('div'); - t.className = 'ss-toast'; t.textContent = text; - this.root.appendChild(t); - setTimeout(() => t.remove(), T(2600)); - } - - fx(emoji, x, y) { - const e = document.createElement('span'); - e.className = 'ss-fx'; e.textContent = emoji; - e.style.left = x + '%'; e.style.top = y + '%'; - this.root.appendChild(e); - setTimeout(() => e.remove(), T(1400)); - } - - // ââ Keyboard âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ - - _bindKeys() { - root.addEventListener('keydown', (e) => { - if (e.ctrlKey || e.metaKey || e.altKey) return; - const k = e.key; - if ([' ', 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Backspace'].indexOf(k) >= 0) e.preventDefault(); - this.onKey(k); - }); - } - - onKey(k) { - const letter = k.length === 1 && /[a-z]/i.test(k) ? k.toLowerCase() : null; - const s = this.state; - if (s === 'profiles') return this._keyProfiles(k, letter); - if (s === 'avatar') return this._keyAvatar(k); - if (s === 'name') return this._keyName(k, letter); - if (s === 'confirmDelete') return this._keyConfirm(letter); - if (s === 'menu') return this._keyMenu(k); - if (s === 'book') return this._keyBook(k); - if (s === 'dayend') { if (k === 'Enter' || k === ' ') this.newDay(); else if (k === 'Backspace') this.showProfiles(); return; } - if (s === 'newsound' || s === 'upgrade') { if ((k === 'Enter' || k === ' ') && this._continue) { const c = this._continue; this._continue = null; c(); } return; } - if (k === 'Backspace' || k === 'Escape') { if (!this.busy) this.showMenu(); return; } - if (s === 'cook') { if (letter) this.typeLetter(letter); else if (k === ' ' || k === 'Enter') this.hint(); return; } - if (s === 'stir') { if (k === ' ' || k === 'Enter') this.stir(); return; } - if (s === 'choose') return this._keyChoose(k); - } - - // ââ Profiles âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ - - showProfiles() { - this.audio.cancel(); - this.state = 'profiles'; this.busy = false; this.sel = 0; - const list = this.store.profiles; - const idx = list.findIndex((p) => p.id === this.store.last); - if (idx >= 0) this.sel = idx; - this._renderProfiles(); - this.audio.speak(list.length ? 'Who is cooking today? Use the arrows and press enter.' : 'Welcome to Sound Soup! Press enter to make a new cook.'); - } - - _renderProfiles() { - const list = this.store.profiles; - const items = list.map((p, i) => '<div class="ss-item' + (i === this.sel ? ' sel' : '') + '">' + img(p.avatar) + esc(p.name) + - '<small>' + SS.masteredWords(p).length + ' recipes</small></div>'); - if (list.length < SS.MAX_PROFILES) items.push('<div class="ss-item new' + (this.sel === list.length ? ' sel' : '') + '">⨠New cook</div>'); - this._panel('<h2>Who is cooking today?</h2><div class="ss-list">' + items.join('') + '</div>' + - '<p><kbd>â</kbd> <kbd>â</kbd> choose ¡ <kbd>Enter</kbd> start' + (list.length ? ' ¡ <kbd>X</kbd> remove a cook' : '') + '</p>'); - } - - _keyProfiles(k, letter) { - const list = this.store.profiles; - const n = list.length + (list.length < SS.MAX_PROFILES ? 1 : 0); - if (k === 'ArrowUp') { this.sel = (this.sel + n - 1) % n; this._renderProfiles(); this.audio.init(); this.audio.tick(); return; } - if (k === 'ArrowDown') { this.sel = (this.sel + 1) % n; this._renderProfiles(); this.audio.init(); this.audio.tick(); return; } - if (/^[1-9]$/.test(k) && +k <= n) { this.sel = +k - 1; this._renderProfiles(); return; } - if (k === 'Enter' || k === ' ') { - this.audio.init(); - if (this.sel < list.length) this.start(list[this.sel]); - else this.showAvatar(); - return; - } - if ((letter === 'x' || k === 'Delete') && this.sel < list.length) { - this.state = 'confirmDelete'; - const p = list[this.sel]; - this._panel('<h2>Remove ' + esc(p.name) + '?</h2><p>' + img(p.avatar) + '</p><p>All of ' + esc(p.name) + '\'s recipes will be forgotten.</p><p><kbd>Y</kbd> yes, remove ¡ any other key: keep</p>'); - this.audio.speak('Remove ' + p.name + '? Press Y to remove, or any other key to keep.'); - } - } - - _keyConfirm(letter) { - if (letter === 'y') { - const p = this.store.profiles[this.sel]; - this.store.profiles = this.store.profiles.filter((x) => x !== p); - if (this.store.last === p.id) this.store.last = null; - this.save(); - } - this.showProfiles(); - } - - showAvatar() { - this.state = 'avatar'; this.avatarSel = Math.floor(Math.random() * SS.AVATARS.length); - this._renderAvatar(); - this.audio.speak('Pick a picture for yourself. Use the arrows, then press enter.'); - } - _renderAvatar() { - this._panel('<h2>Pick your picture</h2><div class="ss-avatars">' + - SS.AVATARS.map((a, i) => img(a, i === this.avatarSel ? 'sel' : '')).join('') + - '</div><p><kbd>â</kbd> <kbd>â</kbd> choose ¡ <kbd>Enter</kbd> next</p>'); - } - _keyAvatar(k) { - const n = SS.AVATARS.length; - if (k === 'ArrowLeft' || k === 'ArrowUp') { this.avatarSel = (this.avatarSel + n - 1) % n; this._renderAvatar(); this.audio.tick(); } - if (k === 'ArrowRight' || k === 'ArrowDown') { this.avatarSel = (this.avatarSel + 1) % n; this._renderAvatar(); this.audio.tick(); } - if (k === 'Enter' || k === ' ') { this.state = 'name'; this.nameBuf = ''; this._renderName(); this.audio.speak('Now type your name and press enter.'); } - if (k === 'Backspace') this.showProfiles(); - } - _renderName() { - this._panel('<h2>What is your name?</h2><p>' + img(SS.AVATARS[this.avatarSel]) + '</p><div class="ss-namefield">' + esc(this.nameBuf) + '</div>' + - '<p>Type your name ¡ <kbd>Enter</kbd> when done</p>'); - } - _keyName(k, letter) { - if (letter && this.nameBuf.length < 12) { this.nameBuf += letter; this._renderName(); this.audio.tick(); return; } - if (k === 'Backspace') { this.nameBuf = this.nameBuf.slice(0, -1); this._renderName(); return; } - if (k === 'Enter') { - const p = SS.newProfile(cap(this.nameBuf || 'cook'), SS.AVATARS[this.avatarSel], this.now()); - this.store.profiles.push(p); - this.save(); - this.start(p); - } - } - - // Test / debug helper: create (or reuse) a profile and start cooking. - quickStart(name) { - let p = this.store.profiles.find((x) => x.name === name); - if (!p) { p = SS.newProfile(name, SS.AVATARS[0], this.now()); this.store.profiles.push(p); this.save(); } - this.audio.init(); - return this.start(p); - } - - // ââ Menu / recipe book âââââââââââââââââââââââââââââââââââââââââââââââââ - - showMenu() { - this.audio.cancel(); - this._resumeState = this.state; this.state = 'menu'; this.sel = 0; - this._renderMenu(); - this.audio.speak('Menu. Recipe book, change cook, or back to cooking.'); - } - _menuItems() { return ['đ Recipe book', 'đ Change cook', 'đ˛ Back to cooking']; } - _renderMenu() { - this._panel('<h2>Menu</h2><div class="ss-list">' + this._menuItems().map((t, i) => '<div class="ss-item' + (i === this.sel ? ' sel' : '') + '">' + t + '</div>').join('') + - '</div><p><kbd>â</kbd> <kbd>â</kbd> ¡ <kbd>Enter</kbd> ¡ <kbd>âŤ</kbd> back</p>'); - } - _keyMenu(k) { - const n = 3; - if (k === 'ArrowUp') { this.sel = (this.sel + n - 1) % n; this._renderMenu(); this.audio.tick(); } - if (k === 'ArrowDown') { this.sel = (this.sel + 1) % n; this._renderMenu(); this.audio.tick(); } - if (k === 'Backspace' || k === 'Escape') this._closeOverlay(); - if (k === 'Enter' || k === ' ') { - if (this.sel === 0) this.showBook(); - else if (this.sel === 1) { this.$overlay.hidden = true; this.showProfiles(); } - else this._closeOverlay(); - } - } - _closeOverlay() { - this.$overlay.hidden = true; - this.state = this._resumeState || 'cook'; - this.audio.speak(this.state === 'cook' ? 'Back to cooking!' : ''); - } - - showBook() { - this.state = 'book'; this.sel = 0; - this._renderBook(); - const n = SS.masteredWords(this.profile).length; - this.audio.speak(n ? 'Your recipe book has ' + n + ' recipes. Press enter to hear one.' : 'Your recipe book is empty. Cook some soup!'); - } - _bookEntries() { - const p = this.profile; - const solid = SS.masteredWords(p), shaky = SS.shakyWords(p); // both skip words no longer in WORDS - return solid.map((w) => ({ w, solid: true })).concat(shaky.map((w) => ({ w, solid: false }))); - } - _renderBook() { - const entries = this._bookEntries(); - const cells = entries.map((e, i) => '<div class="ss-recipe' + (e.solid ? '' : ' shaky') + (i === this.sel ? ' sel' : '') + '">' + img(SS.WORD_BY_NAME[e.w].emoji) + e.w + '</div>'); - this._panel('<h2>Recipe book</h2>' + (entries.length ? '<div class="ss-recipes">' + cells.join('') + '</div>' : '<p>No recipes yet â cook some soup!</p>') + - '<p><kbd>â</kbd> <kbd>â</kbd> choose ¡ <kbd>Enter</kbd> hear it ¡ <kbd>âŤ</kbd> back</p>'); - } - _keyBook(k) { - const entries = this._bookEntries(); const n = entries.length || 1; - if (k === 'ArrowLeft' || k === 'ArrowUp') { this.sel = (this.sel + n - 1) % n; this._renderBook(); this.audio.tick(); } - if (k === 'ArrowRight' || k === 'ArrowDown') { this.sel = (this.sel + 1) % n; this._renderBook(); this.audio.tick(); } - if (k === 'Backspace' || k === 'Escape') this._closeOverlay(); - if ((k === 'Enter' || k === ' ') && entries.length) { - const word = SS.WORD_BY_NAME[entries[this.sel].w]; - this.audio.cancel(); - this.audio.playSegmented(SS.phonemeUnits(word), { gap: 0.3 }).then(() => this.audio.playWord(word.w)); - } - } - - _panel(html) { this.$panel.innerHTML = html; this.$overlay.hidden = false; } - - // ââ Start / HUD / stall ââââââââââââââââââââââââââââââââââââââââââââââââ - - async start(profile) { - this.profile = profile; - this.store.last = profile.id; - this.save(); - this.$overlay.hidden = true; - this.ordersToday = 0; this.todayWords = []; this.todaySounds = []; - this.root.classList.remove('ss-dusk'); - this.applied = {}; - this._applyStall(true); - this._hud(); - this.audio.init(); - this.busy = true; this.state = 'arrive'; - root.__SS_READY__ = true; - await this.audio.speak('Hello ' + profile.name + '! Welcome to your soup stall.'); - this.nextOrder(); - } - - _hud() { - const p = this.profile; if (!p) return; - this.$who.innerHTML = img(p.avatar) + esc(p.name); - this.$recipesChip.innerHTML = 'đ ' + SS.masteredWords(p).length + ' <small>⍠menu</small>'; - let dots = ''; - for (let i = 0; i < SS.ORDERS_PER_DAY; i++) dots += '<span class="' + (i < this.ordersToday ? 'on' : '') + '"></span>'; - this.$day.innerHTML = dots; - } - - // Decorate the stall for every upgrade earned; announce new ones if asked. - _applyStall(silent) { - const ups = SS.stallUpgrades(SS.masteredWords(this.profile).length); - const fresh = []; - ups.forEach((u) => { - if (this.applied[u.id]) return; - this.applied[u.id] = true; - this.root.classList.add('has-' + u.id); - if (DECOR[u.id]) this.$decor.insertAdjacentHTML('beforeend', DECOR[u.id]); - if (!silent) fresh.push(u); - }); - return fresh; - } - - // ââ Order flow âââââââââââââââââââââââââââââââââââââââââââââââââââââââââ - - async nextOrder() { - this.busy = true; this.state = 'arrive'; - this.$blobs.innerHTML = ''; this.$blobs.className = 'ss-blobs'; - this.$dish.className = 'ss-dish'; this.$dish.innerHTML = ''; - this.$choices.innerHTML = ''; - this.$pot.classList.remove('glow', 'cooking', 'stirring'); - if (SS.shouldIntroduce(this.profile)) { - const g = SS.introduceNext(this.profile); - this.save(); - this.todaySounds.push(g); - await this.showNewSound(g); - } - const next = SS.pickNext(this.profile, this.now(), Math.random); - if (root.__SS_FORCE_MODE__ && this.profile.words[next.word.w]) next.mode = root.__SS_FORCE_MODE__; // test hook - const word = next.word; - this.order = { - word, mode: next.mode, review: next.review, idx: 0, buffer: '', missed: false, boxMissed: false, - customer: SS.customerFor(word.w), units: SS.phonemeUnits(word), graphemes: SS.graphemes(word), - }; - this.audio.preloadWord(word.w); - await this.arrive(); - } - - async showNewSound(g) { - this.state = 'newsound'; - const ps = SS.graphemePhonemes(g); - const example = SS.availableWords(this.profile.introduced).find((w) => SS.graphemes(w).map(SS.baseGrapheme).indexOf(g) >= 0); - this._panel('<h2>A new ingredient!</h2><div class="ss-big-letter">' + g + '</div>' + - (example ? '<p>like in <b>' + example.w + '</b> ' + img(example.emoji) + '</p>' : '') + '<p><kbd>Enter</kbd> to keep cooking</p>'); - this.audio.sparkle(); - await this.audio.speak('A new ingredient! This one says'); - for (let rep = 0; rep < 2; rep++) { - let t = 0; - ps.forEach((p) => { t += this.audio.playPhoneme(p, { delay: t, hold: 0.5 }) + 0.05; }); - await sleep(t * 1000 + 500); - } - if (example) { await this.audio.speak('Like in'); await this.audio.playWord(example.w); } - await new Promise((resolve) => { - this._continue = resolve; - if (TEST) resolve(); - else setTimeout(() => { if (this._continue === resolve) { this._continue = null; resolve(); } }, 9000); - }); - this.$overlay.hidden = true; - } - - _renderCustomer() { - const c = this.order.customer; - this.$custBody.innerHTML = img(c.emoji) + (c.hat ? '<span class="ss-cust-hat">' + img(c.hat) + '</span>' : ''); - this.$custName.textContent = c.name; - this.$custName.style.background = c.color; - this.$bubble.className = 'ss-bubble'; this.$bubble.innerHTML = ''; - } - - _renderBoxes() { - const o = this.order; - this.$boxes.innerHTML = o.graphemes.map((g, i) => { - const cls = ['ss-box']; - if (g.length > 1) cls.push('wide'); - if (i < o.idx) cls.push('done'); - else if (i === o.idx) cls.push('next'); - if (o.mode === 'order' && i >= o.idx) cls.push('hidden-letter'); - let inner = esc(g); - if (i === o.idx && o.buffer) inner = '<span class="ss-partial">' + esc(o.buffer) + '</span>' + esc(g.slice(o.buffer.length)); - return '<div class="' + cls.join(' ') + '" data-i="' + i + '">' + inner + '</div>'; - }).join(''); - } - - async arrive() { - const o = this.order, c = o.customer; - this._renderCustomer(); - this.$customer.classList.remove('in', 'out', 'happy', 'puzzled'); - void this.$customer.offsetWidth; // restart the walk-in transition - this.$customer.classList.add('in'); - this.setPrompt(''); - this.$boxes.innerHTML = ''; - await sleep(1100); - if (o.mode === 'cook') this.$bubble.innerHTML = '<span class="ss-word">' + esc(o.word.w) + '</span>'; - else this.$bubble.innerHTML = img(o.word.emoji); - this.$bubble.classList.add('show'); - this.audio.plop(); - this._renderBoxes(); - const hello = pick(LINES.hello) + (o.review ? ' ' + pick(LINES.again) : ''); - await this.audio.speak(hello, c.voice); - if (o.mode === 'cook') { - await this.audio.speak(pick(LINES.cookOrder), c.voice); - this.setPrompt(LINES.instrCook); - this.audio.speak(LINES.instrCook); - } else { - await this.audio.speak(pick(LINES.orderIntro), c.voice); - await this.audio.playWord(o.word.w); - this.setPrompt(LINES.instrOrder); - this.audio.speak(LINES.instrOrder); - } - this.$pot.classList.add('cooking'); - this.state = 'cook'; this.busy = false; - } - - // Replay the demonstration for the current box. - hint() { - if (this.busy) return; - const o = this.order; - this.audio.cancel(); - if (o.mode === 'order') { - this.audio.playWord(o.word.w).then(() => this.audio.playSegmented(o.units, { gap: 0.3, onUnit: (i) => this._flashBox(i) })); - } else { - this._flashBox(o.idx); - let t = 0; - o.units[o.idx].forEach((p) => { t += this.audio.playPhoneme(p, { delay: t, hold: 0.35 }) + 0.04; }); - } - } - - _flashBox(i) { - const b = this.$boxes.querySelector('[data-i="' + i + '"]'); - if (!b) return; - b.classList.add('speaking'); - if (this.order.mode === 'order') b.classList.add('reveal'); - setTimeout(() => b.classList.remove('speaking', 'reveal'), T(650)); - } - - typeLetter(ch) { - if (this.busy) return; - const o = this.order; - const expected = o.graphemes[o.idx]; - const attempt = o.buffer + ch; - if (expected.indexOf(attempt) === 0) { - o.buffer = attempt; - if (attempt === expected) this._acceptGrapheme(); - else { this._renderBoxes(); this.audio.tick(); } - } else { - this._wrongKey(); - } - } - - _acceptGrapheme() { - const o = this.order; - SS.recordGrapheme(this.profile, o.graphemes[o.idx], !o.boxMissed); - o.boxMissed = false; - const unit = o.units[o.idx]; - let t = 0; - unit.forEach((p) => { t += this.audio.playPhoneme(p, { delay: t, hold: 0.25 }) + 0.03; }); - this.audio.plop(); - const blob = document.createElement('div'); - blob.className = 'ss-blob' + (o.graphemes[o.idx].length > 1 ? ' wide' : ''); - blob.textContent = o.graphemes[o.idx]; - blob.style.background = BLOB_COLORS[o.idx % BLOB_COLORS.length]; - blob.dataset.unit = String(o.idx); - this.$blobs.appendChild(blob); - o.idx += 1; o.buffer = ''; - this._renderBoxes(); - if (o.idx >= o.graphemes.length) this._wordComplete(); - } - - async _wordComplete() { - const o = this.order; - this.busy = true; this.state = 'stir'; - await sleep(500); - this.setPrompt('Press <kbd>space</kbd> to stir!'); - this.audio.speak(LINES.stir); - this.busy = false; - this.$pot.classList.add('glow'); - } - - async _wrongKey() { - const o = this.order; - o.missed = true; - if (!o.boxMissed) { o.boxMissed = true; SS.recordGrapheme(this.profile, o.graphemes[o.idx], false); } - o.buffer = ''; - this._renderBoxes(); - this.busy = true; - this.audio.cancel(); - this.audio.wrong(); - this.$pot.classList.remove('wobble'); void this.$pot.offsetWidth; this.$pot.classList.add('wobble'); - const box = this.$boxes.querySelector('[data-i="' + o.idx + '"]'); - if (box) { box.classList.add('hint'); setTimeout(() => box.classList.remove('hint'), T(1000)); } - if (o.mode === 'cook') { - await this.audio.speak('This one says'); - this._flashBox(o.idx); - let t = 0; - o.units[o.idx].forEach((p) => { t += this.audio.playPhoneme(p, { delay: t, hold: 0.45 }) + 0.04; }); - await sleep(t * 1000 + 200); - } else { - await this.audio.speak('Listen again.'); - await this.audio.playWord(o.word.w); - await this.audio.playSegmented(o.units, { gap: 0.35, onUnit: (i) => this._flashBox(i) }); - await this.audio.speak('Which sound comes next?'); - } - this.busy = false; - } - - async stir() { - if (this.busy) return; - const o = this.order; - this.busy = true; this.state = 'blend'; - this.setPrompt(''); - this.$pot.classList.add('stirring'); - this.audio.stirSwirl(); - const blobs = Array.from(this.$blobs.children); - const boxes = Array.from(this.$boxes.children); - await this.audio.blend(o.word.w, o.units, (ev) => { - if (ev.kind === 'pass') { this.$blobs.className = 'ss-blobs pass' + ev.pass; } - if (ev.kind === 'phoneme') { - const b = blobs[ev.unit], x = boxes[ev.unit]; - if (b) { b.classList.add('hot'); setTimeout(() => b.classList.remove('hot'), T(ev.len * 1000)); } - if (x) { x.classList.add('speaking'); x.classList.add('reveal'); setTimeout(() => x.classList.remove('speaking'), T(ev.len * 1000)); } - } - if (ev.kind === 'word') { - this.$blobs.classList.add('fused'); - boxes.forEach((x) => x.classList.add('done', 'reveal')); - this.$pot.classList.remove('stirring'); this.$pot.classList.add('erupt'); - this.audio.sparkle(); - this.fx('â¨', 44, 52); this.fx('â¨', 56, 50); this.fx('đ¨', 50, 46); - } - }); - this.$pot.classList.remove('erupt'); - this.$dish.innerHTML = o.mode === 'cook' ? '<span class="ss-mystery">â</span>' : img(o.word.emoji) + '<span class="ss-dishname">' + esc(o.word.w) + '</span>'; - this.$dish.classList.add('up'); - this.audio.whoosh(); - await sleep(700); - if (o.mode === 'cook') this.choose(); else this.serve(); - } - - choose() { - const o = this.order; - const cards = [o.word].concat(SS.distractors(o.word, this.profile, Math.random)); - for (let i = cards.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [cards[i], cards[j]] = [cards[j], cards[i]]; } - o.cards = cards; this.sel = 0; - this._renderCards(); - this.setPrompt('What did you make?'); - this.audio.speak(LINES.choose); - this.state = 'choose'; this.busy = false; - } - _renderCards() { - this.$choices.innerHTML = this.order.cards.map((w, i) => '<div class="ss-card' + (i === this.sel ? ' sel' : '') + '" data-w="' + w.w + '">' + img(w.emoji) + '<span class="ss-num">' + (i + 1) + '</span></div>').join(''); - } - _keyChoose(k) { - if (this.busy) return; - const n = this.order.cards.length; - if (k === 'ArrowLeft' || k === 'ArrowUp') { this.sel = (this.sel + n - 1) % n; this._renderCards(); this.audio.tick(); } - if (k === 'ArrowRight' || k === 'ArrowDown') { this.sel = (this.sel + 1) % n; this._renderCards(); this.audio.tick(); } - if (/^[1-9]$/.test(k) && +k <= n) { this.sel = +k - 1; this._renderCards(); this._confirmChoice(); } - if (k === 'Enter' || k === ' ') this._confirmChoice(); - } - async _confirmChoice() { - const o = this.order, chosen = o.cards[this.sel]; - this.busy = true; - const cardEls = Array.from(this.$choices.children); - if (chosen.w === o.word.w) { - cardEls[this.sel].classList.add('yes'); - this.audio.ding(); - await sleep(500); - this.$choices.innerHTML = ''; - this.serve(); - return; - } - o.missed = true; - cardEls[this.sel].classList.add('no'); - this.audio.wrong(); - this.$customer.classList.remove('puzzled'); void this.$customer.offsetWidth; this.$customer.classList.add('puzzled'); - await this.audio.speak(pick(LINES.wrongDish) + ' That is a', o.customer.voice); - await this.audio.playWord(chosen.w); - await this.audio.speak('Listen to the soup again.'); - await this.audio.playSegmented(o.units, { gap: 0.12, hold: 0.1 }); - await this.audio.playWord(o.word.w); - cardEls[this.sel].classList.remove('no'); - this.busy = false; - } - - async serve() { - const o = this.order, p = this.profile; - this.state = 'serve'; this.busy = true; - this.setPrompt(''); - this.$dish.innerHTML = img(o.word.emoji) + '<span class="ss-dishname">' + esc(o.word.w) + '</span>'; - this.$dish.classList.add('up'); - this.$boxes.querySelectorAll('.ss-box').forEach((b) => b.classList.add('done', 'reveal')); - await sleep(400); - this.$dish.classList.add('served'); - this.audio.whoosh(); - await sleep(900); - this.$customer.classList.add('happy'); - this.$bubble.innerHTML = '<span class="ss-heart">â¤</span>'; - this.audio.fanfare(); - this.fx('â¤ď¸', 68, 40); this.fx('â¤ď¸', 74, 44); this.fx('â', 62, 42); - - const wasMastered = SS.isMastered(SS.wordRecord(p, o.word.w)); - const rec = SS.recordOrder(p, o.word.w, !o.missed, this.now()); - this.ordersToday += 1; - this.todayWords.push(o.word.w); - this.save(); - this._hud(); - - await this.audio.playWord(o.word.w); - await this.audio.speak(pick(LINES.happy), o.customer.voice); - if (!wasMastered && SS.isMastered(rec)) { - this.toast('đ New recipe: ' + o.word.w + '!'); - this.audio.sparkle(); - await this.audio.speak('A new recipe for your book!'); - } - const fresh = this._applyStall(false); - for (const u of fresh) { - this.toast('đ Your stall got ' + u.label + '!'); - this.audio.fanfare(); - await this.audio.speak('Your stall got ' + u.label + '!'); - await sleep(600); - } - this.$customer.classList.remove('in'); this.$customer.classList.add('out'); - this.$bubble.classList.remove('show'); - await sleep(900); - if (this.ordersToday >= SS.ORDERS_PER_DAY) this.dayEnd(); - else this.nextOrder(); - } - - async dayEnd() { - this.state = 'dayend'; this.busy = false; - this.root.classList.add('ss-dusk'); - this.$pot.classList.remove('cooking', 'glow'); - this.$boxes.innerHTML = ''; this.$blobs.innerHTML = ''; - const pics = this.todayWords.map((w) => img(SS.WORD_BY_NAME[w].emoji)).join(''); - const sounds = this.todaySounds.length ? '<p>New sounds: <b>' + this.todaySounds.join(', ') + '</b></p>' : ''; - this._panel('<h2>The stall is closed for today!</h2><div class="ss-summary">' + pics + '</div>' + sounds + - '<p>' + SS.masteredWords(this.profile).length + ' recipes in your book</p>' + - '<p><kbd>Enter</kbd> open the stall again ¡ <kbd>âŤ</kbd> change cook</p>'); - this.audio.fanfare(); - await this.audio.speak('The stall is closed for today. Great cooking, ' + this.profile.name + '! Press enter to open again, or take a break.'); - } - - newDay() { - this.$overlay.hidden = true; - this.root.classList.remove('ss-dusk'); - this.ordersToday = 0; this.todayWords = []; this.todaySounds = []; - this._hud(); - this.nextOrder(); - } - } - - document.addEventListener('DOMContentLoaded', () => { - const container = document.getElementById('game-container'); - root.__SS_GAME__ = new SoundSoup(container); - }); - root.SoundSoup = SoundSoup; -}(window)); diff --git a/games/sound-soup/index.html b/games/sound-soup/index.html @@ -1,32 +0,0 @@ -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="UTF-8"> - <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover"> - <title>Sound Soup â KGames</title> - <link rel="preconnect" href="https://fonts.googleapis.com"> - <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> - <link href="https://fonts.googleapis.com/css2?family=Fredoka:wght@400;600&display=swap" rel="stylesheet"> - <link rel="stylesheet" href="style.css?v=1"> -</head> -<body> - <nav><a href="../../index.html">← Back to KGames</a></nav> - <div id="audio-controls"> - <button id="btn-voice" type="button">Speech: ON</button> - <button id="btn-sfx" type="button">SFX: ON</button> - </div> - <div id="game-container"></div> - <div id="attribution"> - Pictures: <a href="https://github.com/jdecked/twemoji" target="_blank" rel="noopener">Twemoji</a> CC-BY 4.0 · - Voices: <a href="https://commons.wikimedia.org/" target="_blank" rel="noopener">Wikimedia Commons</a> recordings, CC-BY-SA 3.0 · - <a href="CREDITS.md" target="_blank" rel="noopener">full credits</a> - </div> - <script src="../../js/shared/game-shared.js"></script> - <script src="lib.js?v=1"></script> - <script src="assets/manifest.js?v=1"></script> - <script src="audio.js?v=1"></script> - <script src="game.js?v=1"></script> - <script src="../../js/shared/touch-controls.js"></script> - <script>KGames.initTouchControls({ layout: 'text-nav' });</script> -</body> -</html> diff --git a/games/sound-soup/lib.js b/games/sound-soup/lib.js @@ -1,469 +0,0 @@ -// Sound Soup â pure game logic (no DOM, no audio). Dual-export for unit tests. -// -// Contents: -// PHONEMES / GRAPHEMES / SEQUENCE â the synthetic-phonics teaching content -// WORDS â decodable words with a picture referent -// CUSTOMERS â the regulars (one per word, by hash) -// segmentation helpers â graphemes / phonemes / keys for a word -// scheduler â mastery tracking + spaced repetition -// blend timeline â timing plan for the sound-fusing "stir" -// profile store â on-device, multi-child progress -(function (root, factory) { - if (typeof module === 'object' && module.exports) module.exports = factory(); - else root.SS = factory(); -}(typeof self !== 'undefined' ? self : this, function () { - 'use strict'; - - const DAY = 24 * 60 * 60 * 1000; - const MIN = 60 * 1000; - - // ââ Sounds âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ - // type: 'cont' = stretchable consonant, 'stop' = short burst, 'vowel'. - const PHONEMES = { - s: { ipa: 's', type: 'cont' }, a: { ipa: 'ĂŚ', type: 'vowel' }, - t: { ipa: 't', type: 'stop' }, p: { ipa: 'p', type: 'stop' }, - i: { ipa: 'ÉŞ', type: 'vowel' }, n: { ipa: 'n', type: 'cont' }, - m: { ipa: 'm', type: 'cont' }, d: { ipa: 'd', type: 'stop' }, - g: { ipa: 'g', type: 'stop' }, o: { ipa: 'É', type: 'vowel' }, - k: { ipa: 'k', type: 'stop' }, e: { ipa: 'É', type: 'vowel' }, - u: { ipa: 'Ę', type: 'vowel' }, r: { ipa: 'Éš', type: 'cont' }, - h: { ipa: 'h', type: 'cont' }, b: { ipa: 'b', type: 'stop' }, - f: { ipa: 'f', type: 'cont' }, l: { ipa: 'l', type: 'cont' }, - j: { ipa: 'dĘ', type: 'stop' }, v: { ipa: 'v', type: 'cont' }, - w: { ipa: 'w', type: 'cont' }, y: { ipa: 'j', type: 'cont' }, - z: { ipa: 'z', type: 'cont' }, sh: { ipa: 'Ę', type: 'cont' }, - ch: { ipa: 'tĘ', type: 'stop' }, th: { ipa: 'θ', type: 'cont' }, - ng: { ipa: 'Ĺ', type: 'cont' }, ee: { ipa: 'iË', type: 'vowel' }, - oo: { ipa: 'uË', type: 'vowel' }, uu: { ipa: 'Ę', type: 'vowel' }, - oa: { ipa: 'oĘ', type: 'vowel' }, - }; - - // Spelling unit -> phoneme ids. Doubled consonants (ll, ss, gg, ff, zz) and - // 'ck' are handled by graphemePhonemes() without needing their own entry. - const GRAPHEMES = { - s: ['s'], a: ['a'], t: ['t'], p: ['p'], i: ['i'], n: ['n'], m: ['m'], d: ['d'], - g: ['g'], o: ['o'], c: ['k'], k: ['k'], ck: ['k'], e: ['e'], u: ['u'], r: ['r'], - h: ['h'], b: ['b'], f: ['f'], l: ['l'], j: ['j'], v: ['v'], w: ['w'], x: ['k', 's'], - y: ['y'], z: ['z'], sh: ['sh'], ch: ['ch'], th: ['th'], ng: ['ng'], ee: ['ee'], - oo: ['oo'], oa: ['oa'], - }; - - // Teaching order (Letters and Sounds phase 2 -> 3, continuants early). - const SEQUENCE = [ - 's', 'a', 't', 'p', 'i', 'n', // the classic opening six - 'm', 'd', 'g', 'o', 'c', 'k', 'ck', 'e', 'u', 'r', 'h', 'b', 'f', 'l', - 'j', 'v', 'w', 'x', 'y', 'z', - 'sh', 'ch', 'th', 'ng', 'ee', 'oo', 'oa', - ]; - const STARTING_GRAPHEMES = 6; // s a t p i n are known from the first order - - // ââ Words ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ - // g: graphemes joined by '-'; p: phoneme override (space-separated) when a - // grapheme is ambiguous (book: 'oo' -> /Ę/); emoji: Twemoji codepoint file. - const WORDS = [ - { w: 'tap', g: 't-a-p', emoji: '1f6b0' }, - { w: 'pin', g: 'p-i-n', emoji: '1f4cc' }, - { w: 'pan', g: 'p-a-n', emoji: '1f373' }, - { w: 'ant', g: 'a-n-t', emoji: '1f41c' }, - { w: 'nap', g: 'n-a-p', emoji: '1f634' }, - { w: 'pants', g: 'p-a-n-t-s', emoji: '1f456' }, - { w: 'map', g: 'm-a-p', emoji: '1f5fa' }, - { w: 'sad', g: 's-a-d', emoji: '1f622' }, - { w: 'mad', g: 'm-a-d', emoji: '1f620' }, - { w: 'dad', g: 'd-a-d', emoji: '1f468-200d-1f467' }, - { w: 'pig', g: 'p-i-g', emoji: '1f437' }, - { w: 'dog', g: 'd-o-g', emoji: '1f436' }, - { w: 'pot', g: 'p-o-t', emoji: '1f372' }, - { w: 'mom', g: 'm-o-m', emoji: '1f469-200d-1f467' }, - { w: 'cat', g: 'c-a-t', emoji: '1f431' }, - { w: 'cap', g: 'c-a-p', emoji: '1f9e2' }, - { w: 'can', g: 'c-a-n', emoji: '1f96b' }, - { w: 'kid', g: 'k-i-d', emoji: '1f466' }, - { w: 'sock', g: 's-o-ck', emoji: '1f9e6' }, - { w: 'pen', g: 'p-e-n', emoji: '1f58a' }, - { w: 'ten', g: 't-e-n', emoji: '1f51f' }, - { w: 'net', g: 'n-e-t', emoji: '1f945' }, - { w: 'egg', g: 'e-gg', emoji: '1f95a' }, - { w: 'gem', g: 'g-e-m', emoji: '1f48e' }, - { w: 'tent', g: 't-e-n-t', emoji: '26fa' }, - { w: 'nest', g: 'n-e-s-t', emoji: '1fab9' }, - { w: 'sun', g: 's-u-n', emoji: '2600' }, - { w: 'cup', g: 'c-u-p', emoji: '1f964' }, - { w: 'nut', g: 'n-u-t', emoji: '1f95c' }, - { w: 'cut', g: 'c-u-t', emoji: '2702' }, - { w: 'mug', g: 'm-u-g', emoji: '2615' }, - { w: 'duck', g: 'd-u-ck', emoji: '1f986' }, - { w: 'rat', g: 'r-a-t', emoji: '1f400' }, - { w: 'ram', g: 'r-a-m', emoji: '1f40f' }, - { w: 'red', g: 'r-e-d', emoji: '1f7e5' }, - { w: 'run', g: 'r-u-n', emoji: '1f3c3' }, - { w: 'rock', g: 'r-o-ck', emoji: '1faa8' }, - { w: 'crab', g: 'c-r-a-b', emoji: '1f980' }, - { w: 'drum', g: 'd-r-u-m', emoji: '1f941' }, - { w: 'truck', g: 't-r-u-ck', emoji: '1f69a' }, - { w: 'dress', g: 'd-r-e-ss', emoji: '1f457' }, - { w: 'hat', g: 'h-a-t', emoji: '1f3a9' }, - { w: 'hen', g: 'h-e-n', emoji: '1f414' }, - { w: 'hot', g: 'h-o-t', emoji: '1f975' }, - { w: 'hug', g: 'h-u-g', emoji: '1f917' }, - { w: 'hut', g: 'h-u-t', emoji: '1f6d6' }, - { w: 'ham', g: 'h-a-m', emoji: '1f356' }, - { w: 'hand', g: 'h-a-n-d', emoji: '270b' }, - { w: 'bus', g: 'b-u-s', emoji: '1f68c' }, - { w: 'bed', g: 'b-e-d', emoji: '1f6cf' }, - { w: 'bag', g: 'b-a-g', emoji: '1f45c' }, - { w: 'bat', g: 'b-a-t', emoji: '1f987' }, - { w: 'bug', g: 'b-u-g', emoji: '1f41b' }, - { w: 'bin', g: 'b-i-n', emoji: '1f5d1' }, - { w: 'cab', g: 'c-a-b', emoji: '1f695' }, - { w: 'bell', g: 'b-e-ll', emoji: '1f514' }, - { w: 'brick', g: 'b-r-i-ck', emoji: '1f9f1' }, - { w: 'fan', g: 'f-a-n', emoji: '1faad' }, - { w: 'gift', g: 'g-i-f-t', emoji: '1f381' }, - { w: 'frog', g: 'f-r-o-g', emoji: '1f438' }, - { w: 'leg', g: 'l-e-g', emoji: '1f9b5' }, - { w: 'lip', g: 'l-i-p', emoji: '1f444' }, - { w: 'log', g: 'l-o-g', emoji: '1fab5' }, - { w: 'lock', g: 'l-o-ck', emoji: '1f512' }, - { w: 'clock', g: 'c-l-o-ck', emoji: '23f0' }, - { w: 'flag', g: 'f-l-a-g', emoji: '1f6a9' }, - { w: 'plug', g: 'p-l-u-g', emoji: '1f50c' }, - { w: 'milk', g: 'm-i-l-k', emoji: '1f95b' }, - { w: 'hill', g: 'h-i-ll', emoji: '26f0' }, - { w: 'plant', g: 'p-l-a-n-t', emoji: '1fab4' }, - { w: 'sled', g: 's-l-e-d', emoji: '1f6f7' }, - { w: 'jam', g: 'j-a-m', emoji: '1fad9' }, - { w: 'jet', g: 'j-e-t', emoji: '1f6e9' }, - { w: 'van', g: 'v-a-n', emoji: '1f690' }, - { w: 'web', g: 'w-e-b', emoji: '1f578' }, - { w: 'swim', g: 's-w-i-m', emoji: '1f3ca' }, - { w: 'box', g: 'b-o-x', emoji: '1f4e6' }, - { w: 'fox', g: 'f-o-x', emoji: '1f98a' }, - { w: 'six', g: 's-i-x', emoji: '36-20e3' }, - { w: 'ax', g: 'a-x', emoji: '1fa93' }, - { w: 'yum', g: 'y-u-m', emoji: '1f60b' }, - { w: 'yam', g: 'y-a-m', emoji: '1f360' }, - { w: 'zap', g: 'z-a-p', emoji: '26a1' }, - { w: 'ship', g: 'sh-i-p', emoji: '1f6a2' }, - { w: 'shop', g: 'sh-o-p', emoji: '1f3ea' }, - { w: 'fish', g: 'f-i-sh', emoji: '1f41f' }, - { w: 'shell', g: 'sh-e-ll', emoji: '1f41a' }, - { w: 'dish', g: 'd-i-sh', emoji: '1f37d' }, - { w: 'brush', g: 'b-r-u-sh', emoji: '1f58c' }, - { w: 'chick', g: 'ch-i-ck', emoji: '1f424' }, - { w: 'lunch', g: 'l-u-n-ch', emoji: '1f371' }, - { w: 'bath', g: 'b-a-th', emoji: '1f6c1' }, - { w: 'ring', g: 'r-i-ng', emoji: '1f48d' }, - { w: 'king', g: 'k-i-ng', emoji: '1f934' }, - { w: 'bee', g: 'b-ee', emoji: '1f41d' }, - { w: 'tree', g: 't-r-ee', emoji: '1f333' }, - { w: 'feet', g: 'f-ee-t', emoji: '1f9b6' }, - { w: 'sheep', g: 'sh-ee-p', emoji: '1f411' }, - { w: 'teeth', g: 't-ee-th', emoji: '1f9b7' }, - { w: 'seed', g: 's-ee-d', emoji: '1f331' }, - { w: 'green', g: 'g-r-ee-n', emoji: '1f7e9' }, - { w: 'moon', g: 'm-oo-n', emoji: '1f319' }, - { w: 'boot', g: 'b-oo-t', emoji: '1f97e' }, - { w: 'spoon', g: 's-p-oo-n', emoji: '1f944' }, - { w: 'zoom', g: 'z-oo-m', emoji: '1f680' }, - { w: 'book', g: 'b-oo-k', emoji: '1f4d6', p: 'b uu k' }, - { w: 'cook', g: 'c-oo-k', emoji: '1f9d1-200d-1f373', p: 'k uu k' }, - { w: 'hook', g: 'h-oo-k', emoji: '1fa9d', p: 'h uu k' }, - { w: 'boat', g: 'b-oa-t', emoji: '26f5' }, - { w: 'goat', g: 'g-oa-t', emoji: '1f410' }, - { w: 'coat', g: 'c-oa-t', emoji: '1f9e5' }, - { w: 'soap', g: 's-oa-p', emoji: '1f9fc' }, - { w: 'road', g: 'r-oa-d', emoji: '1f6e3' }, - ]; - const WORD_BY_NAME = {}; - WORDS.forEach((w) => { WORD_BY_NAME[w.w] = w; }); - - // ââ Customers ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ - // voice: {pitch, rate} for speech synthesis so each regular sounds different. - const CUSTOMERS = [ - { id: 'ember', name: 'Ember', kind: 'a sea-dragon', emoji: '1f409', color: '#E63946', voice: { pitch: 0.7, rate: 0.9 } }, - { id: 'pip', name: 'Captain Pip', kind: 'a parrot', emoji: '1f99c', color: '#2EC4B6', voice: { pitch: 1.6, rate: 1.15 } }, - { id: 'reginald', name: 'Sir Reginald', kind: 'a very polite lizard', emoji: '1f98e', color: '#1D7CF2', hat: '1f3a9', voice: { pitch: 0.9, rate: 0.85 } }, - { id: 'cornelius',name: 'Cornelius', kind: 'a coconut crab', emoji: '1f980', color: '#E63946', voice: { pitch: 0.6, rate: 1.0 } }, - { id: 'ottilie', name: 'Auntie Ottilie', kind: 'an octopus', emoji: '1f419', color: '#9B5DE5', voice: { pitch: 1.2, rate: 0.95 } }, - { id: 'marina', name: 'Marina', kind: 'a mermaid', emoji: '1f9dc-200d-2640-fe0f', color: '#2EC4B6', voice: { pitch: 1.3, rate: 1.0 } }, - { id: 'zephyr', name: 'Zephyr', kind: 'an island spirit', emoji: '1f9de', color: '#1D7CF2', voice: { pitch: 1.0, rate: 1.1 } }, - { id: 'shelby', name: 'Grandpa Shelby', kind: 'a turtle', emoji: '1f422', color: '#2EC4B6', voice: { pitch: 0.75, rate: 0.75 } }, - { id: 'flossie', name: 'Flossie', kind: 'a flamingo', emoji: '1f9a9', color: '#FF7AA2', voice: { pitch: 1.5, rate: 1.05 } }, - { id: 'hopper', name: 'Prince Hopper', kind: 'a frog', emoji: '1f438', color: '#2EC4B6', voice: { pitch: 1.1, rate: 1.2 } }, - { id: 'hoot', name: 'Professor Hoot', kind: 'an owl', emoji: '1f989', color: '#FFD23F', voice: { pitch: 0.85, rate: 0.9 } }, - { id: 'splash', name: 'Splash', kind: 'a dolphin', emoji: '1f42c', color: '#1D7CF2', voice: { pitch: 1.4, rate: 1.1 } }, - ]; - - const AVATARS = ['1f9d2', '1f467', '1f466', '1f431', '1f436', '1f430', '1f43c', '1f981', '1f42f', '1f427', '1f43b', '1f47d']; - - // ââ Deterministic hashing (same word -> same regular every time) âââââââ - function hashStr(s) { - let h = 2166136261; - for (let i = 0; i < s.length; i++) { h ^= s.charCodeAt(i); h = Math.imul(h, 16777619) >>> 0; } - return h >>> 0; - } - function customerFor(word) { return CUSTOMERS[hashStr(word) % CUSTOMERS.length]; } - - function mulberry32(seed) { - let a = seed >>> 0; - return function () { - a = (a + 0x6D2B79F5) >>> 0; - let t = a; - t = Math.imul(t ^ (t >>> 15), t | 1); - t ^= t + Math.imul(t ^ (t >>> 7), t | 61); - return ((t ^ (t >>> 14)) >>> 0) / 4294967296; - }; - } - - // ââ Segmentation âââââââââââââââââââââââââââââââââââââââââââââââââââââââ - function graphemes(word) { return word.g.split('-'); } - - function graphemePhonemes(g) { - if (GRAPHEMES[g]) return GRAPHEMES[g]; - if (g.length === 2 && g[0] === g[1] && GRAPHEMES[g[0]]) return GRAPHEMES[g[0]]; // ll, ss, gg ⌠- throw new Error('unknown grapheme: ' + g); - } - - // Phoneme ids for a word, one entry per grapheme (x -> ['k','s'] is one entry). - function phonemeUnits(word) { - const gs = graphemes(word); - if (word.p) { - const ps = word.p.split(' '); - if (ps.length !== gs.length) throw new Error('phoneme override length mismatch: ' + word.w); - return ps.map((p) => [p]); - } - return gs.map(graphemePhonemes); - } - - // Which teaching-sequence grapheme a spelling unit needs (ll -> l, ck -> ck). - function baseGrapheme(g) { - if (SEQUENCE.indexOf(g) >= 0) return g; - if (g.length === 2 && g[0] === g[1]) return g[0]; - return g; - } - - function wordIsAvailable(word, known) { - return graphemes(word).every((g) => known.indexOf(baseGrapheme(g)) >= 0); - } - - function availableWords(known) { return WORDS.filter((w) => wordIsAvailable(w, known)); } - - // ââ Profiles âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ - const STORE_KEY = 'kg-sound-soup-v1'; - const MAX_PROFILES = 6; - - function newProfile(name, avatar, now, id) { - return { - id: id || ('p' + now.toString(36) + Math.floor(Math.random() * 1e6).toString(36)), - name: name || 'Cook', - avatar: avatar || AVATARS[0], - created: now, - introduced: SEQUENCE.slice(0, STARTING_GRAPHEMES), - graphemes: {}, // g -> { hits, misses } - words: {}, // w -> { reps, lapses, streak, interval, due, last, seen, mode } - orders: 0, - recent: [], // first-try results of the last few orders - sinceIntro: 0, - lastWord: null, - }; - } - - function loadStore(storage) { - let data = null; - try { data = JSON.parse(storage.getItem(STORE_KEY)); } catch (_) {} - if (!data || !Array.isArray(data.profiles)) data = { version: 1, profiles: [], last: null }; - return data; - } - function saveStore(storage, data) { - try { storage.setItem(STORE_KEY, JSON.stringify(data)); } catch (_) {} - } - - // ââ Mastery + scheduling âââââââââââââââââââââââââââââââââââââââââââââââ - const INTERVALS_DAYS = [1, 3, 7, 14, 30, 60]; - const MASTERED_STREAK = 2; - const ORDER_MODE_UNLOCK = 5; // mastered words before "take the order" appears - const ORDERS_PER_DAY = 6; // a natural stopping point - - function wordRecord(profile, w) { return profile.words[w] || null; } - function isMastered(rec) { return !!rec && rec.streak >= MASTERED_STREAK; } - // Words a profile has met that still exist in WORDS (a saved profile must - // survive a word being removed from the list in a later version). - function servedWords(profile) { return Object.keys(profile.words).filter((w) => WORD_BY_NAME[w]); } - function masteredWords(profile) { return servedWords(profile).filter((w) => isMastered(profile.words[w])); } - function shakyWords(profile) { return servedWords(profile).filter((w) => !isMastered(profile.words[w])); } - - // Apply the result of one served order. firstTry = no wrong keys / wrong picks. - function recordOrder(profile, w, firstTry, now) { - const rec = profile.words[w] || { reps: 0, lapses: 0, streak: 0, interval: 0, due: now, last: null, seen: 0 }; - rec.seen += 1; - rec.last = now; - if (firstTry) { - rec.reps += 1; - rec.streak += 1; - rec.interval = INTERVALS_DAYS[Math.min(rec.streak - 1, INTERVALS_DAYS.length - 1)]; - rec.due = now + rec.interval * DAY; - } else { - rec.lapses += 1; - rec.streak = 0; - rec.interval = 0; - rec.due = now + 5 * MIN; // the regular wanders back a little later - } - profile.words[w] = rec; - profile.orders += 1; - profile.sinceIntro += 1; - profile.lastWord = w; - profile.recent = profile.recent.concat([!!firstTry]).slice(-8); - return rec; - } - - function recordGrapheme(profile, g, hit) { - const key = baseGrapheme(g); - const s = profile.graphemes[key] || { hits: 0, misses: 0 }; - if (hit) s.hits += 1; else s.misses += 1; - profile.graphemes[key] = s; - } - - function dueWords(profile, now) { - return servedWords(profile) - .filter((w) => profile.words[w].due <= now) - .sort((a, b) => profile.words[a].due - profile.words[b].due); - } - - function unseenWords(profile) { - return availableWords(profile.introduced).map((w) => w.w).filter((w) => !profile.words[w]); - } - - function nextGrapheme(profile) { - return SEQUENCE.find((g) => profile.introduced.indexOf(g) < 0) || null; - } - - // Mastery-based pacing: a new sound arrives when the child is fluent with the - // current pool (recent orders mostly first-try) or the pool has run dry. - function shouldIntroduce(profile) { - if (!nextGrapheme(profile)) return false; - if (unseenWords(profile).length === 0) return true; - if (profile.sinceIntro < 3) return false; - const r = profile.recent.slice(-5); - if (r.length < 4) return false; - const acc = r.filter(Boolean).length / r.length; - return acc >= 0.8; - } - - function introduceNext(profile) { - const g = nextGrapheme(profile); - if (!g) return null; - profile.introduced = profile.introduced.concat([g]); - profile.sinceIntro = 0; - return g; - } - - // Pick the next order. Returns { word, mode, review } â mode is 'cook' - // (word shown, child decodes) or 'order' (word heard, child encodes). - function pickNext(profile, now, rng) { - rng = rng || Math.random; - const avoid = profile.lastWord; - const due = dueWords(profile, now).filter((w) => w !== avoid); - const fresh = unseenWords(profile).filter((w) => w !== avoid); - const newest = profile.introduced[profile.introduced.length - 1]; - let w = null, review = false; - - if (due.length && (fresh.length === 0 || rng() < 0.6)) { - w = due[Math.floor(rng() * Math.min(due.length, 3))]; // one of the most overdue - review = true; - } else if (fresh.length) { - const withNewest = fresh.filter((x) => graphemes(WORD_BY_NAME[x]).map(baseGrapheme).indexOf(newest) >= 0); - const pool = withNewest.length ? withNewest : fresh; - w = pool[Math.floor(rng() * pool.length)]; - } else { - // nothing due and nothing new: practise the least recently served word - const served = servedWords(profile).filter((x) => x !== avoid); - const pool = served.length ? served : servedWords(profile); - pool.sort((a, b) => profile.words[a].last - profile.words[b].last); - w = pool[0] || availableWords(profile.introduced)[0].w; - review = true; - } - - let mode = 'cook'; - const rec = profile.words[w]; - if (rec && masteredWords(profile).length >= ORDER_MODE_UNLOCK && rng() < 0.55) mode = 'order'; - return { word: WORD_BY_NAME[w], mode, review }; - } - - // Two distractor pictures for the "what did you make?" check. Prefers words - // the child has met that share letters with the target (cat/cap, pin/pan). - function distractors(word, profile, rng) { - rng = rng || Math.random; - const seen = servedWords(profile); - const usable = (list) => list.filter((w) => w !== word.w && WORD_BY_NAME[w] && WORD_BY_NAME[w].emoji !== word.emoji); - const shares = (w) => WORD_BY_NAME[w].w.split('').some((c) => word.w.indexOf(c) >= 0); - let pool = usable(seen); - if (pool.length < 2) pool = usable(availableWords(profile.introduced).map((x) => x.w)); - if (pool.length < 2) pool = usable(WORDS.map((x) => x.w)); - const near = pool.filter(shares), far = pool.filter((w) => !shares(w)); - const pick = []; - const take = (arr) => { while (arr.length && pick.length < 2) pick.push(arr.splice(Math.floor(rng() * arr.length), 1)[0]); }; - take(near); take(far); - return pick.map((w) => WORD_BY_NAME[w]); - } - - // ââ Stall growth âââââââââââââââââââââââââââââââââââââââââââââââââââââââ - const STALL_UPGRADES = [ - { at: 2, id: 'awning', label: 'a striped awning' }, - { at: 5, id: 'sign', label: 'a painted sign' }, - { at: 9, id: 'lanterns', label: 'paper lanterns' }, - { at: 14, id: 'flowers', label: 'hibiscus flowers' }, - { at: 20, id: 'bunting', label: 'party bunting' }, - { at: 28, id: 'cat', label: 'a stall cat' }, - { at: 38, id: 'lights', label: 'fairy lights' }, - { at: 50, id: 'fountain', label: 'a little fountain' }, - { at: 65, id: 'parrot', label: 'a parrot on the roof' }, - ]; - function stallUpgrades(masteredCount) { return STALL_UPGRADES.filter((u) => masteredCount >= u.at); } - - // ââ Blend timeline âââââââââââââââââââââââââââââââââââââââââââââââââââââ - // Three passes: stretched with big gaps, then closer, then fused, then the - // whole word. Returns [{ t, kind:'phoneme'|'word'|'pass', ... }] in seconds. - const BLEND_PASSES = [ - { gap: 0.55, hold: 0.45 }, - { gap: 0.22, hold: 0.18 }, - { gap: 0.0, hold: 0.0 }, - ]; - const PASS_PAUSE = 0.55; - - // units: [[phonemeId, ...] per grapheme]; durations: phonemeId -> seconds. - function blendTimeline(units, durations, opts) { - opts = opts || {}; - const passes = opts.passes || BLEND_PASSES; - const dur = (p) => (durations && durations[p]) || 0.25; - const ev = []; - let t = opts.start || 0; - passes.forEach((pass, pi) => { - ev.push({ t, kind: 'pass', pass: pi }); - units.forEach((unit, ui) => { - unit.forEach((p, k) => { - const type = (PHONEMES[p] || {}).type; - const stretch = type === 'stop' ? 0 : pass.hold; - const len = dur(p) + stretch; - ev.push({ t, kind: 'phoneme', id: p, unit: ui, pass: pi, len }); - t += len + (k < unit.length - 1 ? 0.02 : 0); - }); - if (ui < units.length - 1) t += pass.gap; - }); - t += PASS_PAUSE; - }); - ev.push({ t, kind: 'word' }); - return ev; - } - - return { - DAY, MIN, PHONEMES, GRAPHEMES, SEQUENCE, STARTING_GRAPHEMES, WORDS, WORD_BY_NAME, CUSTOMERS, AVATARS, - STORE_KEY, MAX_PROFILES, INTERVALS_DAYS, MASTERED_STREAK, ORDER_MODE_UNLOCK, ORDERS_PER_DAY, - STALL_UPGRADES, BLEND_PASSES, PASS_PAUSE, - hashStr, customerFor, mulberry32, - graphemes, graphemePhonemes, phonemeUnits, baseGrapheme, wordIsAvailable, availableWords, - newProfile, loadStore, saveStore, - wordRecord, isMastered, servedWords, masteredWords, shakyWords, recordOrder, recordGrapheme, dueWords, unseenWords, - nextGrapheme, shouldIntroduce, introduceNext, pickNext, distractors, stallUpgrades, blendTimeline, - }; -})); diff --git a/games/sound-soup/style.css b/games/sound-soup/style.css @@ -1,266 +0,0 @@ -/* Sound Soup â DOM game styles. Everything scales from --u (1% of the shorter - side of #game-container) so the street looks the same on a phone and a TV. */ - -* { margin: 0; padding: 0; box-sizing: border-box; } -html, body { height: 100%; overflow: hidden; } -body { - background: #1A1A2E; - font-family: 'Fredoka', sans-serif; - display: flex; - flex-direction: column; - color: #1A1A2E; -} -nav { width: 100%; padding: 0.6rem 1.25rem; flex-shrink: 0; } -nav a { color: #aaa; text-decoration: none; font-size: 1rem; } -nav a:hover { color: #fff; } -#game-container { flex: 1; width: 100%; min-height: 0; position: relative; overflow: hidden; } - -#audio-controls { position: fixed; top: 0.45rem; right: 1rem; display: flex; gap: 0.4rem; z-index: 10; } -#audio-controls button { - background: rgba(255,255,255,0.08); border: 1px solid rgba(255,255,255,0.18); color: #bbb; - font-family: 'Fredoka', sans-serif; font-size: 0.85rem; padding: 0.25rem 0.65rem; border-radius: 6px; - cursor: pointer; user-select: none; -} -#audio-controls button:hover { background: rgba(255,255,255,0.16); color: #fff; } -#audio-controls button.muted { color: #555; border-color: rgba(255,255,255,0.08); } - -#attribution { - flex-shrink: 0; text-align: center; font-size: 0.65rem; color: #556; padding: 0.25rem 0.5rem 0.35rem; - white-space: nowrap; overflow: hidden; text-overflow: ellipsis; -} -#attribution a { color: #778; text-decoration: none; } -#attribution a:hover { color: #aab; } - -/* ââ Scene ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ -.ss-root { - --u: 5px; /* set by JS: 1% of min(width, height) */ - --red: #E63946; --blue: #1D7CF2; --yellow: #FFD23F; --teal: #2EC4B6; - position: absolute; inset: 0; overflow: hidden; user-select: none; - background: linear-gradient(#7fd3ff 0%, #bfeaff 42%, #ffe9b3 60%, #f6d38a 60.5%, #e9bf6e 100%); - font-size: calc(var(--u) * 3.2); -} -.ss-root.ss-dusk { background: linear-gradient(#f79d65 0%, #ffd29d 40%, #ffe9b3 60%, #d9b06a 60.5%, #c9a05a 100%); } -.ss-layer { position: absolute; inset: 0; pointer-events: none; } -.ss-img { display: inline-block; width: 1em; height: 1em; vertical-align: -0.15em; } - -.ss-sun { position: absolute; right: 12%; top: 5%; width: calc(var(--u) * 12); height: calc(var(--u) * 12); - border-radius: 50%; background: radial-gradient(circle, #fff6c2 0%, #FFD23F 55%, rgba(255,210,63,0) 72%); } -.ss-cloud { position: absolute; top: 8%; width: calc(var(--u) * 16); height: calc(var(--u) * 5); background: #fff; - border-radius: 999px; opacity: 0.9; animation: ss-drift 60s linear infinite; } -.ss-cloud::before { content: ''; position: absolute; left: 25%; top: -50%; width: 45%; height: 130%; background: #fff; border-radius: 50%; } -.ss-cloud.c2 { top: 16%; width: calc(var(--u) * 11); animation-duration: 85s; animation-delay: -30s; } -@keyframes ss-drift { from { transform: translateX(-30vw); } to { transform: translateX(110vw); } } - -.ss-sea { position: absolute; left: 0; right: 0; bottom: 48%; height: calc(var(--u) * 16); max-height: 24%; - background: linear-gradient(#3fb9e8, #1D7CF2); } -.ss-sea::after { content: ''; position: absolute; inset: 0; - background: repeating-linear-gradient(90deg, rgba(255,255,255,0.18) 0 6%, transparent 6% 14%); - mask: linear-gradient(transparent, rgba(0,0,0,0.6)); -webkit-mask: linear-gradient(transparent, rgba(0,0,0,0.6)); } -.ss-boat { position: absolute; top: 6%; font-size: calc(var(--u) * 6); animation: ss-sail 45s linear infinite; } -.ss-boat.b2 { top: 40%; font-size: calc(var(--u) * 4); animation-duration: 70s; animation-delay: -35s; animation-direction: reverse; } -@keyframes ss-sail { from { transform: translateX(-15vw); } to { transform: translateX(105vw); } } -.ss-bird { position: absolute; top: 12%; font-size: calc(var(--u) * 2.4); animation: ss-fly 28s linear infinite; } -.ss-bird.k2 { top: 20%; animation-duration: 36s; animation-delay: -12s; } -@keyframes ss-fly { from { transform: translateX(110vw) translateY(0); } 50% { transform: translateX(50vw) translateY(-3vh); } to { transform: translateX(-20vw) translateY(0); } } - -.ss-street { position: absolute; left: 0; right: 0; top: 52%; bottom: 0; } -.ss-houses { position: absolute; left: 0; right: 0; bottom: 42%; height: calc(var(--u) * 20); max-height: 34%; display: flex; align-items: flex-end; gap: 1.5%; padding: 0 2%; } -.ss-house { flex: 1; height: 80%; border-radius: calc(var(--u) * 1.2) calc(var(--u) * 1.2) 0 0; position: relative; - box-shadow: inset 0 calc(var(--u) * -1) rgba(0,0,0,0.12); } -.ss-house::before { content: ''; position: absolute; left: -3%; right: -3%; top: -12%; height: 16%; border-radius: 40%; background: var(--roof, #c0392b); } -.ss-house::after { content: ''; position: absolute; left: 22%; top: 28%; width: 56%; height: 34%; - background: repeating-linear-gradient(90deg, var(--shutter, #fff) 0 30%, transparent 30% 70%, var(--shutter, #fff) 70% 100%); - border-radius: calc(var(--u) * 0.5); opacity: 0.85; } -.ss-house.h1 { background: #ffb4a2; --roof: #e63946; --shutter: #2EC4B6; height: 92%; } -.ss-house.h2 { background: #ffd6a5; --roof: #1D7CF2; --shutter: #fff; height: 72%; } -.ss-house.h3 { background: #caffbf; --roof: #f4a261; --shutter: #E63946; height: 84%; } -.ss-house.h4 { background: #9bf6ff; --roof: #e63946; --shutter: #FFD23F; height: 66%; } -.ss-house.h5 { background: #bdb2ff; --roof: #2EC4B6; --shutter: #fff; height: 88%; } -.ss-house.h6 { background: #ffc6ff; --roof: #1D7CF2; --shutter: #FFD23F; height: 70%; } -.ss-palm { position: absolute; bottom: min(52%, calc(var(--u) * 28)); font-size: calc(var(--u) * 13); transform-origin: 50% 100%; animation: ss-sway 5s ease-in-out infinite; } -.ss-palm.p1 { left: 1%; } .ss-palm.p2 { right: 2%; animation-delay: -2.5s; font-size: calc(var(--u) * 11); } -@keyframes ss-sway { 0%, 100% { transform: rotate(-2deg); } 50% { transform: rotate(2.5deg); } } -.ss-walker { position: absolute; bottom: 38%; font-size: calc(var(--u) * 5.5); transition: transform 14s linear; will-change: transform; } - -/* ââ Stall âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ -.ss-stall { position: absolute; left: 50%; bottom: 3%; width: calc(var(--u) * 60); height: calc(var(--u) * 42); - transform: translateX(-50%); } -.ss-awning { position: absolute; left: -4%; right: -4%; top: 0; height: 22%; border-radius: calc(var(--u) * 2) calc(var(--u) * 2) 0 0; - background: repeating-linear-gradient(90deg, var(--red) 0 12%, #fff 12% 24%); opacity: 0; transition: opacity 0.6s, transform 0.6s; - transform: translateY(-20%); box-shadow: 0 calc(var(--u) * 0.8) 0 rgba(0,0,0,0.12); } -.ss-awning::after { content: ''; position: absolute; left: 0; right: 0; bottom: -18%; height: 20%; - background: radial-gradient(circle at 50% 0, transparent 60%, var(--red) 62%) 0 0 / 12% 100%; } -.ss-root.has-awning .ss-awning { opacity: 1; transform: none; } -.ss-poles { position: absolute; left: 2%; right: 2%; top: 20%; height: 35%; } -.ss-poles::before, .ss-poles::after { content: ''; position: absolute; top: 0; bottom: 0; width: calc(var(--u) * 1.4); background: #8b5a2b; border-radius: 999px; } -.ss-poles::before { left: 0; } .ss-poles::after { right: 0; } -.ss-sign { position: absolute; left: 50%; top: 24%; transform: translateX(-50%) rotate(-2deg); background: #fff; color: var(--red); - padding: 0.1em 0.6em; border-radius: 0.4em; font-weight: 600; font-size: calc(var(--u) * 3.4); border: calc(var(--u) * 0.5) solid var(--yellow); - opacity: 0; transition: opacity 0.6s; white-space: nowrap; } -.ss-root.has-sign .ss-sign { opacity: 1; } -.ss-decor { position: absolute; inset: 0; pointer-events: none; } -.ss-decor > * { position: absolute; font-size: calc(var(--u) * 4.5); } -.ss-decor .d-lantern1 { left: 3%; top: 26%; animation: ss-swing 3s ease-in-out infinite; } .ss-decor .d-lantern2 { right: 3%; top: 26%; animation: ss-swing 3.4s ease-in-out infinite reverse; } -.ss-decor .d-flower1 { left: -6%; bottom: -2%; } .ss-decor .d-flower2 { right: -6%; bottom: -2%; } .ss-decor .d-flower3 { left: 12%; bottom: -3%; font-size: calc(var(--u) * 3.5); } -.ss-decor .d-bunting { left: 0; right: 0; top: -10%; height: 8%; font-size: 1px; background: repeating-linear-gradient(90deg, var(--yellow) 0 12%, var(--teal) 12% 24%, var(--red) 24% 36%, var(--blue) 36% 48%); - clip-path: polygon(0 0, 100% 0, 100% 40%, 94% 100%, 88% 40%, 82% 100%, 76% 40%, 70% 100%, 64% 40%, 58% 100%, 52% 40%, 46% 100%, 40% 40%, 34% 100%, 28% 40%, 22% 100%, 16% 40%, 10% 100%, 4% 40%, 0 100%); } -.ss-decor .d-cat { right: 4%; bottom: 3%; font-size: calc(var(--u) * 5); } -.ss-decor .d-lights { left: 0; right: 0; top: 19%; height: 3%; background: radial-gradient(circle, #fff 30%, transparent 32%) 0 0 / 6% 100%; font-size: 1px; animation: ss-twinkle 1.6s steps(2) infinite; } -.ss-decor .d-fountain { left: -20%; bottom: 0; font-size: calc(var(--u) * 7); } -.ss-decor .d-parrot { right: 8%; top: -8%; font-size: calc(var(--u) * 5); animation: ss-bob 2.2s ease-in-out infinite; } -@keyframes ss-swing { 0%, 100% { transform: rotate(-6deg); } 50% { transform: rotate(6deg); } } -@keyframes ss-twinkle { 50% { opacity: 0.35; } } -@keyframes ss-bob { 50% { transform: translateY(-8%); } } - -.ss-counter { position: absolute; left: 0; right: 0; bottom: 0; height: 44%; background: #d98c45; border-radius: calc(var(--u) * 1.5); - box-shadow: inset 0 calc(var(--u) * 1.2) 0 #f2a95c, 0 calc(var(--u) * 0.8) 0 #a8642e; } -.ss-counter::after { content: ''; position: absolute; left: 0; right: 0; top: 30%; bottom: 0; - background: repeating-linear-gradient(90deg, transparent 0 15%, rgba(0,0,0,0.08) 15% 16%); } - -.ss-pot { position: absolute; left: 50%; bottom: 36%; width: calc(var(--u) * 26); height: calc(var(--u) * 18); transform: translateX(-50%); - transform-origin: 50% 100%; } -.ss-pot-body { position: absolute; left: 0; right: 0; bottom: 0; height: 78%; background: linear-gradient(#2f6ed6, #163f8f); border-radius: 12% 12% 40% 40% / 10% 10% 45% 45%; - box-shadow: inset calc(var(--u) * -1.5) calc(var(--u) * -1) rgba(0,0,0,0.25); } -.ss-pot-body::before, .ss-pot-body::after { content: ''; position: absolute; top: 18%; width: 14%; height: 20%; background: var(--red); border-radius: 999px; } -.ss-pot-body::before { left: -11%; } .ss-pot-body::after { right: -11%; } -.ss-soup { position: absolute; left: 4%; right: 4%; top: 12%; height: 26%; border-radius: 50%; background: radial-gradient(ellipse at 50% 40%, #ffe27a, #f2b13a); - overflow: hidden; box-shadow: inset 0 calc(var(--u) * 0.5) rgba(255,255,255,0.4); transition: background 0.4s; } -.ss-pot.glow .ss-soup { background: radial-gradient(ellipse at 50% 40%, #fff6b0, #ffc94d); box-shadow: 0 0 calc(var(--u) * 4) #ffd23f; } -.ss-bub { position: absolute; bottom: 0; width: 9%; height: 30%; border-radius: 50%; background: rgba(255,255,255,0.55); animation: ss-bub 1.6s ease-in infinite; } -@keyframes ss-bub { 0% { transform: translateY(80%) scale(0.4); opacity: 0; } 30% { opacity: 1; } 100% { transform: translateY(-120%) scale(1); opacity: 0; } } -.ss-steam { position: absolute; left: 0; right: 0; bottom: 90%; height: 60%; } -.ss-puff { position: absolute; bottom: 0; width: 14%; height: 45%; border-radius: 50%; background: rgba(255,255,255,0.85); filter: blur(1px); - animation: ss-puff 2.6s ease-out infinite; opacity: 0; } -.ss-pot.cooking .ss-puff { opacity: 1; } -@keyframes ss-puff { 0% { transform: translateY(0) scale(0.6); opacity: 0; } 25% { opacity: 0.85; } 100% { transform: translateY(-150%) scale(1.5); opacity: 0; } } -.ss-spoon { position: absolute; right: -6%; top: -46%; font-size: calc(var(--u) * 9); transform-origin: 20% 90%; transform: rotate(35deg); transition: transform 0.4s; } -.ss-pot.stirring .ss-spoon { animation: ss-stir 0.9s ease-in-out infinite; } -@keyframes ss-stir { 0%, 100% { transform: rotate(35deg) translateX(0); } 50% { transform: rotate(15deg) translateX(-45%); } } -.ss-pot.wobble { animation: ss-wobble 0.5s ease-in-out; } -@keyframes ss-wobble { 0%, 100% { transform: translateX(-50%) rotate(0); } 25% { transform: translateX(-50%) rotate(-4deg); } 75% { transform: translateX(-50%) rotate(4deg); } } -.ss-pot.erupt .ss-soup { animation: ss-erupt 0.6s ease-out; } -@keyframes ss-erupt { 0% { transform: scaleY(1); } 40% { transform: scaleY(1.5) translateY(-20%); } 100% { transform: scaleY(1); } } - -/* letter blobs that fall into the pot and then fuse */ -.ss-blobs { position: absolute; left: 0; right: 0; top: -30%; height: 60%; display: flex; justify-content: center; align-items: center; gap: calc(var(--u) * 1.2); - transition: gap 0.5s; } -.ss-blobs.pass1 { gap: calc(var(--u) * 0.4); } .ss-blobs.pass2 { gap: 0; } -.ss-blob { width: calc(var(--u) * 6); height: calc(var(--u) * 6); border-radius: 50%; display: flex; align-items: center; justify-content: center; - color: #fff; font-weight: 600; font-size: calc(var(--u) * 3.4); box-shadow: 0 calc(var(--u) * 0.4) rgba(0,0,0,0.2); animation: ss-drop 0.55s cubic-bezier(.2,1.4,.5,1); } -.ss-blob.wide { width: auto; padding: 0 0.4em; border-radius: 999px; } -.ss-blob.hot { transform: scale(1.25); box-shadow: 0 0 calc(var(--u) * 3) #fff; transition: transform 0.1s; } -.ss-blobs.fused .ss-blob { border-radius: 0; box-shadow: none; } -.ss-blobs.fused .ss-blob:first-child { border-radius: 999px 0 0 999px; } .ss-blobs.fused .ss-blob:last-child { border-radius: 0 999px 999px 0; } -.ss-blobs.fused .ss-blob:only-child { border-radius: 999px; } -@keyframes ss-drop { 0% { transform: translateY(-300%) scale(0.6); opacity: 0; } 60% { opacity: 1; } 100% { transform: none; } } -/* Entrance for things that must stay visible even if the animation never advances - (a background tab / headless frame keeps the timeline at 0): never start hidden. */ -@keyframes ss-pop { 0% { transform: scale(0.88); } 100% { transform: none; } } -.ss-dish { position: absolute; left: 50%; bottom: 40%; transform: translateX(-50%); font-size: calc(var(--u) * 9); opacity: 0; - transition: transform 0.9s cubic-bezier(.3,1.2,.4,1), opacity 0.4s; display: flex; flex-direction: column; align-items: center; } -.ss-dish.up { opacity: 1; transform: translateX(-50%) translateY(-90%); } -.ss-dish.served { opacity: 1; transform: translateX(-50%) translateY(-90%) translateX(calc(var(--u) * 42)) scale(0.85); } -.ss-dish .ss-mystery { font-size: calc(var(--u) * 7); animation: ss-bob 1s ease-in-out infinite; } -.ss-dish .ss-dishname { font-size: calc(var(--u) * 3.2); font-weight: 600; color: #fff; background: var(--teal); padding: 0 0.5em; border-radius: 999px; margin-top: 0.1em; } - -/* ââ Customer âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ -.ss-customer { position: absolute; left: 50%; bottom: 8%; transform: translateX(calc(var(--u) * 26)) translateX(60vw); transition: transform 1.1s cubic-bezier(.3,1.1,.4,1); - display: flex; flex-direction: column; align-items: center; width: calc(var(--u) * 28); } -.ss-customer.in { transform: translateX(calc(var(--u) * 26)); } -.ss-customer.out { transform: translateX(calc(var(--u) * 26)) translateX(60vw); transition-duration: 1.4s; } -.ss-cust-body { position: relative; font-size: calc(var(--u) * 17); line-height: 1; animation: ss-idle 2.4s ease-in-out infinite; transform-origin: 50% 100%; } -.ss-cust-hat { position: absolute; left: 22%; top: -30%; font-size: 45%; transform: rotate(-12deg); } -.ss-customer.happy .ss-cust-body { animation: ss-happy 0.5s ease-in-out 3; } -.ss-customer.puzzled .ss-cust-body { animation: ss-puzzled 0.7s ease-in-out 2; } -@keyframes ss-idle { 50% { transform: scaleY(0.97) scaleX(1.02); } } -@keyframes ss-happy { 50% { transform: translateY(-18%) rotate(-6deg); } } -@keyframes ss-puzzled { 0%, 100% { transform: rotate(0); } 25% { transform: rotate(-9deg); } 75% { transform: rotate(9deg); } } -.ss-cust-name { font-size: calc(var(--u) * 2.6); font-weight: 600; color: #fff; background: rgba(26,26,46,0.55); padding: 0 0.6em; border-radius: 999px; margin-top: 0.3em; white-space: nowrap; } -.ss-bubble { position: absolute; bottom: 104%; left: 50%; transform: translateX(-50%) scale(0); transform-origin: 50% 100%; background: #fff; border-radius: 1em; - padding: 0.35em 0.7em; font-size: calc(var(--u) * 4); min-width: 2.5em; text-align: center; box-shadow: 0 0.2em 0 rgba(0,0,0,0.12); transition: transform 0.35s cubic-bezier(.3,1.4,.5,1); white-space: nowrap; } -.ss-bubble::after { content: ''; position: absolute; left: 50%; bottom: -0.45em; width: 0.9em; height: 0.9em; background: #fff; transform: translateX(-50%) rotate(45deg); border-radius: 0.15em; } -.ss-bubble.show { transform: translateX(-50%) scale(1); } -.ss-bubble .ss-img { width: 1.6em; height: 1.6em; } -.ss-bubble .ss-word { font-weight: 600; letter-spacing: 0.06em; color: #1A1A2E; } -.ss-bubble .ss-heart { color: var(--red); animation: ss-bob 0.8s ease-in-out infinite; } - -/* hearts / sparkles */ -.ss-fx { position: absolute; font-size: calc(var(--u) * 4); animation: ss-rise 1.3s ease-out forwards; pointer-events: none; } -@keyframes ss-rise { 0% { transform: translateY(0) scale(0.6); opacity: 0; } 20% { opacity: 1; } 100% { transform: translateY(-250%) scale(1.3); opacity: 0; } } - -/* ââ Sound boxes (Elkonin boxes) âââââââââââââââââââââââââââââââââââââââââââ */ -.ss-boxes { position: absolute; left: 50%; top: 4%; transform: translateX(-50%); display: flex; gap: calc(var(--u) * 1.4); align-items: center; } -.ss-box { width: calc(var(--u) * 11); height: calc(var(--u) * 11); border-radius: calc(var(--u) * 2.2); background: rgba(255,255,255,0.92); border: calc(var(--u) * 0.6) solid rgba(26,26,46,0.15); - display: flex; align-items: center; justify-content: center; font-size: calc(var(--u) * 6.2); font-weight: 600; color: #1A1A2E; text-transform: lowercase; - transition: transform 0.15s, background 0.25s, border-color 0.25s; box-shadow: 0 calc(var(--u) * 0.5) 0 rgba(0,0,0,0.12); } -.ss-box.wide { width: auto; min-width: calc(var(--u) * 11); padding: 0 0.25em; } -.ss-box.next { border-color: var(--yellow); transform: scale(1.06); animation: ss-pulse 1.1s ease-in-out infinite; } -.ss-box.done { background: var(--teal); color: #fff; border-color: transparent; } -.ss-box.hint { animation: ss-hint 0.45s ease-in-out 2; border-color: var(--red); } -.ss-box.hidden-letter { color: transparent; } -.ss-box.hidden-letter.done, .ss-box.hidden-letter.reveal { color: #fff; } -.ss-box .ss-partial { color: var(--blue); } -.ss-box.speaking { background: var(--yellow); } -@keyframes ss-pulse { 50% { transform: scale(1.14); } } -@keyframes ss-hint { 0%, 100% { transform: translateX(0); } 25% { transform: translateX(-8%); } 75% { transform: translateX(8%); } } - -/* ââ Prompt line âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ -.ss-prompt { position: absolute; left: 50%; top: 21%; transform: translateX(-50%); background: rgba(26,26,46,0.72); color: #fff; padding: 0.3em 1em; border-radius: 999px; - font-size: calc(var(--u) * 3.4); white-space: nowrap; max-width: 96%; overflow: hidden; text-overflow: ellipsis; transition: opacity 0.3s; } -.ss-prompt:empty { opacity: 0; } -.ss-prompt kbd { background: #fff; color: #1A1A2E; border-radius: 0.3em; padding: 0 0.4em; font-family: inherit; font-weight: 600; margin: 0 0.1em; } - -/* ââ Choice cards ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ -.ss-choices { position: absolute; left: 50%; top: 26%; transform: translateX(-50%); display: flex; gap: calc(var(--u) * 3); } -.ss-choices:empty { display: none; } -.ss-card { width: calc(var(--u) * 19); height: calc(var(--u) * 21); background: #fff; border-radius: calc(var(--u) * 2.5); display: flex; flex-direction: column; align-items: center; justify-content: center; - border: calc(var(--u) * 0.8) solid transparent; transition: transform 0.15s, border-color 0.15s; box-shadow: 0 calc(var(--u) * 0.6) 0 rgba(0,0,0,0.15); animation: ss-pop 0.4s cubic-bezier(.2,1.3,.5,1); } -.ss-card .ss-img { width: calc(var(--u) * 11); height: calc(var(--u) * 11); } -.ss-card .ss-num { font-size: calc(var(--u) * 2.6); color: #889; margin-top: 0.3em; } -.ss-card.sel { border-color: var(--yellow); transform: scale(1.1) translateY(-4%); } -.ss-card.no { animation: ss-hint 0.4s ease-in-out 2; opacity: 0.5; } -.ss-card.yes { border-color: var(--teal); transform: scale(1.15); } - -/* ââ HUD âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ -.ss-hud { position: absolute; left: 0; right: 0; top: 0; padding: 1.2%; display: flex; justify-content: space-between; align-items: flex-start; pointer-events: none; font-size: calc(var(--u) * 3); } -.ss-chip { background: rgba(255,255,255,0.85); border-radius: 999px; padding: 0.15em 0.7em 0.15em 0.35em; display: flex; align-items: center; gap: 0.35em; font-weight: 600; color: #1A1A2E; } -.ss-chip .ss-img { width: 1.5em; height: 1.5em; } -.ss-chip.right { padding: 0.15em 0.5em; } -.ss-chip small { font-weight: 400; color: #667; font-size: 0.75em; } -.ss-day { position: absolute; left: 50%; bottom: 0.8%; transform: translateX(-50%); display: flex; gap: 0.35em; font-size: calc(var(--u) * 2.2); } -.ss-day span { width: 1em; height: 1em; border-radius: 50%; background: rgba(255,255,255,0.55); border: 2px solid rgba(26,26,46,0.35); } -.ss-day span.on { background: var(--yellow); border-color: var(--red); } - -/* ââ Overlays (profiles, menu, book, day end, new sound) âââââââââââââââââââ */ -.ss-overlay { position: absolute; inset: 0; background: rgba(26,26,46,0.62); display: flex; align-items: center; justify-content: center; padding: 3%; z-index: 5; } -.ss-overlay[hidden] { display: none; } -.ss-panel { background: #fff; border-radius: calc(var(--u) * 3); padding: calc(var(--u) * 3) calc(var(--u) * 4); max-width: 94%; max-height: 94%; overflow: auto; text-align: center; - box-shadow: 0 calc(var(--u) * 1) 0 rgba(0,0,0,0.25); animation: ss-pop 0.35s cubic-bezier(.2,1.3,.5,1); } -.ss-panel h2 { font-size: calc(var(--u) * 5); color: var(--red); margin-bottom: 0.3em; } -.ss-panel p { font-size: calc(var(--u) * 3); color: #445; margin: 0.2em 0; } -.ss-panel kbd { background: #1A1A2E; color: #fff; border-radius: 0.3em; padding: 0 0.4em; font-family: inherit; font-weight: 600; } -.ss-list { display: flex; flex-direction: column; gap: 0.3em; margin: 0.6em 0; align-items: stretch; } -.ss-item { display: flex; align-items: center; gap: 0.5em; padding: 0.3em 0.8em; border-radius: 999px; font-size: calc(var(--u) * 3.6); font-weight: 600; color: #1A1A2E; - border: calc(var(--u) * 0.5) solid transparent; background: #f2f4f8; text-align: left; } -.ss-item .ss-img { width: 1.6em; height: 1.6em; } -.ss-item.sel { border-color: var(--yellow); background: #fff8d6; transform: scale(1.03); } -.ss-item.new { color: var(--blue); } -.ss-item small { margin-left: auto; font-weight: 400; color: #778; font-size: 0.7em; } -.ss-avatars { display: flex; gap: 0.4em; justify-content: center; flex-wrap: wrap; margin: 0.5em 0; } -.ss-avatars .ss-img { width: calc(var(--u) * 8); height: calc(var(--u) * 8); padding: 0.1em; border-radius: 1em; border: calc(var(--u) * 0.5) solid transparent; } -.ss-avatars .ss-img.sel { border-color: var(--yellow); background: #fff8d6; transform: scale(1.12); } -.ss-namefield { display: inline-block; min-width: 8em; font-size: calc(var(--u) * 5); font-weight: 600; border-bottom: 0.12em solid var(--blue); padding: 0 0.3em; min-height: 1.3em; text-transform: capitalize; } -.ss-namefield::after { content: '|'; color: var(--blue); animation: ss-twinkle 1s steps(2) infinite; } -.ss-big-letter { font-size: calc(var(--u) * 14); font-weight: 600; color: var(--blue); line-height: 1.1; background: #eef4ff; border-radius: 0.2em; display: inline-block; padding: 0 0.3em; margin: 0.1em 0; } -.ss-recipes { display: grid; grid-template-columns: repeat(auto-fill, minmax(calc(var(--u) * 14), 1fr)); gap: 0.5em; margin: 0.6em 0; max-height: calc(var(--u) * 46); overflow: auto; } -.ss-recipe { display: flex; flex-direction: column; align-items: center; padding: 0.3em; border-radius: 1em; background: #f2f4f8; font-size: calc(var(--u) * 2.8); font-weight: 600; border: calc(var(--u) * 0.4) solid transparent; } -.ss-recipe .ss-img { width: calc(var(--u) * 7); height: calc(var(--u) * 7); } -.ss-recipe.shaky { opacity: 0.7; border-style: dashed; border-color: #ccd; } -.ss-recipe.sel { border-color: var(--yellow); background: #fff8d6; } -.ss-summary { display: flex; gap: 0.4em; justify-content: center; flex-wrap: wrap; font-size: calc(var(--u) * 6); margin: 0.3em 0; } -.ss-summary .ss-img { width: 1em; height: 1em; } -.ss-toast { position: absolute; left: 50%; top: 45%; transform: translate(-50%, -50%); background: var(--yellow); color: #1A1A2E; font-weight: 600; font-size: calc(var(--u) * 4.5); - padding: 0.4em 1em; border-radius: 999px; box-shadow: 0 0.2em 0 rgba(0,0,0,0.2); animation: ss-pop 0.4s cubic-bezier(.2,1.3,.5,1); z-index: 4; white-space: nowrap; } - -/* test mode: no waiting on transitions */ -.ss-root.ss-test *, .ss-root.ss-test *::before, .ss-root.ss-test *::after { animation-duration: 0.01s !important; transition-duration: 0.01s !important; } diff --git a/games/story-stamp/CREDITS.md b/games/story-stamp/CREDITS.md @@ -0,0 +1,335 @@ +# Story Stamp â asset credits + +Generated by `tools/story-stamp-assets.py`. Every third-party file used by the game is listed here with its source, author and license. + +## Pictures + +All pictures are [Fluent Emoji](https://github.com/microsoft/fluentui-emoji) 3D graphics, Š Microsoft Corporation, licensed [MIT](https://github.com/microsoft/fluentui-emoji/blob/main/LICENSE). Files: `games/story-stamp/assets/img/*.webp` (scaled to 192 px, WebP). Assets used: Alarm clock, Alien, Alien monster, Angry face, Ant, Artist palette, Automobile, Axe, Baby chick, Backhand index pointing up/Default, Balloon, Basket, Bat, Bathtub, Bear, Bed, Beetle, Bell, Bento box, Billed cap, Bird, Bone, Books, Bowling, Boy/Default, Brain, Bread, Brick, Bucket, Bug, Bullseye, Bus, Butter, Butterfly, Cactus, Camel, Camping, Canned food, Card file box, Carrot, Cat, Cat face, Chains, Cherry blossom, Chicken, Child/Default, Cloud, Cloud with lightning, Cloud with lightning and rain, Cloud with rain, Clown face, Coat, Coin, Collision, Comet, Cook/Default, Cooking, Cow, Cow face, Cowboy hat face, Crab, Crayon, Crescent moon, Crocodile, Crown, Crying face, Crystal ball, Cup with straw, Cupcake, Cut of meat, Cyclone, Deciduous tree, Deer, Delivery truck, Dog, Dog face, Dolphin, Dragon, Dragon face, Dress, Drum, Duck, Ear of corn, Egg, Electric plug, Ewe, Face savoring food, Fire engine, Fish, Flamingo, Flashlight, Flexed biceps/Default, Fog, Foot/Default, Fork and knife, Fork and knife with plate, Fox, French fries, Frog, Garlic, Gem stone, Ghost, Girl/Default, Glass of milk, Globe showing americas, Goal net, Goat, Goblin, Hammer, Hamster, Handbag, Hatching chick, Health worker/Default, Helicopter, High voltage, Hiking boot, Hippopotamus, Honeybee, Hook, Hot beverage, Hot dog, Hot face, Hot pepper, House, Hut, Jack-o-lantern, Jar, Jeans, Keycap 10, Keycap 6, Kick scooter, Koala, Ladder, Laptop, Lemon, Light bulb, Lion, Lizard, Lobster, Locked, Locomotive, Lollipop, Magic wand, Magnet, Mammoth, Man/Default, Mango, Melon, Microphone, Minibus, Motor boat, Motorway, Mouth, Mushroom, National park, Nauseated face, Nest with eggs, Nesting dolls, Night with stars, Octopus, Open book, Otter, Owl, Ox, Oyster, Package, Paintbrush, Palm tree, Panda, Parrot, Paw prints, Peanuts, Pen, Penguin, People hugging, Performing arts, Person cartwheeling/Default, Person elf/Default, Person genie, Person merpeople/Default, Person running/Default, Person swimming/Default, Pick, Pickup truck, Pie, Pig, Pig face, Pile of poo, Pizza, Popcorn, Postal horn, Pot of food, Potable water, Potted plant, Poultry leg, Prince/Default, Princess/Default, Printer, Rabbit, Rabbit face, Raccoon, Rainbow, Raised hand/Default, Ram, Rat, Red paper lantern, Red question mark, Rescue workers helmet, Ring, Ringed planet, Robot, Rock, Rocket, Rooster, Safety vest, Sailboat, Sandwich, Sauropod, Scarf, Scissors, Seedling, Shark, Ship, Shopping cart, Shrimp, Skunk, Sled, Sleeping face, Sloth, Small airplane, Snail, Snake, Snowflake, Soap, Soccer ball, Socks, Speedboat, Spider web, Spiral shell, Spoon, Sport utility vehicle, Squid, Star, Sun, Sunset, T-rex, Taxi, Teddy bear, Telescope, Tennis, Tent, Thumbs up/Default, Ticket, Tiger face, Toilet, Tooth, Toothbrush, Top hat, Tractor, Tram, Triangular flag, Tropical fish, Turtle, Unicorn, Wastebasket, Water wave, Wheel, Window, Wing, Woman mage/Default, Woman/Default, Womans hat, Wood, World map, Wrapped gift, Yo-yo, Zany face, Zipper-mouth face. + +## Audio + +Recordings from [Wikimedia Commons](https://commons.wikimedia.org/). Phoneme clips are *cut from* the listed originals (an isolated sound is extracted from an IPA "sa ⌠sa" recording or from the start of a word, and continuants are held longer by time-stretching); word clips are trimmed and level-matched. Modified files are `games/story-stamp/assets/audio/**/*.mp3`. + +### Dvortygirl â [CC BY-SA 3.0](http://creativecommons.org/licenses/by-sa/3.0/) + +- [En-us-ant.ogg](https://commons.wikimedia.org/wiki/File:En-us-ant.ogg) â word "ant" +- [En-us-are.ogg](https://commons.wikimedia.org/wiki/File:En-us-are.ogg) â phoneme /ar/ â cut 0.00â0.38s of the original +- [En-us-arm.ogg](https://commons.wikimedia.org/wiki/File:En-us-arm.ogg) â word "arm" +- [En-us-art.ogg](https://commons.wikimedia.org/wiki/File:En-us-art.ogg) â word "art" +- [En-us-bag.ogg](https://commons.wikimedia.org/wiki/File:En-us-bag.ogg) â word "bag" +- [En-us-balloon.ogg](https://commons.wikimedia.org/wiki/File:En-us-balloon.ogg) â word "balloon" +- [En-us-basket.ogg](https://commons.wikimedia.org/wiki/File:En-us-basket.ogg) â word "basket" +- [En-us-bat.ogg](https://commons.wikimedia.org/wiki/File:En-us-bat.ogg) â word "bat" +- [En-us-bath.ogg](https://commons.wikimedia.org/wiki/File:En-us-bath.ogg) â word "bath" +- [En-us-bed.ogg](https://commons.wikimedia.org/wiki/File:En-us-bed.ogg) â word "bed" +- [En-us-bee.ogg](https://commons.wikimedia.org/wiki/File:En-us-bee.ogg) â word "bee" +- [En-us-bell.ogg](https://commons.wikimedia.org/wiki/File:En-us-bell.ogg) â word "bell" +- [En-us-big.ogg](https://commons.wikimedia.org/wiki/File:En-us-big.ogg) â word "big" +- [En-us-boat.ogg](https://commons.wikimedia.org/wiki/File:En-us-boat.ogg) â word "boat" +- [En-us-book.ogg](https://commons.wikimedia.org/wiki/File:En-us-book.ogg) â word "book" +- [En-us-boot.ogg](https://commons.wikimedia.org/wiki/File:En-us-boot.ogg) â word "boot" +- [En-us-box.ogg](https://commons.wikimedia.org/wiki/File:En-us-box.ogg) â word "box" +- [En-us-boy.ogg](https://commons.wikimedia.org/wiki/File:En-us-boy.ogg) â phoneme /oi/ â cut 0.12â0.44s of the original; word "boy" +- [En-us-brain.ogg](https://commons.wikimedia.org/wiki/File:En-us-brain.ogg) â word "brain" +- [En-us-brush.ogg](https://commons.wikimedia.org/wiki/File:En-us-brush.ogg) â word "brush" +- [En-us-bucket.ogg](https://commons.wikimedia.org/wiki/File:En-us-bucket.ogg) â word "bucket" +- [En-us-bug.ogg](https://commons.wikimedia.org/wiki/File:En-us-bug.ogg) â phoneme /b/ â cut 0.09â0.16s of the original; word "bug" +- [En-us-bus.ogg](https://commons.wikimedia.org/wiki/File:En-us-bus.ogg) â word "bus" +- [En-us-butter.ogg](https://commons.wikimedia.org/wiki/File:En-us-butter.ogg) â word "butter" +- [En-us-cab.ogg](https://commons.wikimedia.org/wiki/File:En-us-cab.ogg) â word "cab" +- [En-us-cactus.ogg](https://commons.wikimedia.org/wiki/File:En-us-cactus.ogg) â word "cactus" +- [En-us-camel.ogg](https://commons.wikimedia.org/wiki/File:En-us-camel.ogg) â word "camel" +- [En-us-camp.ogg](https://commons.wikimedia.org/wiki/File:En-us-camp.ogg) â word "camp" +- [En-us-can.ogg](https://commons.wikimedia.org/wiki/File:En-us-can.ogg) â word "can" +- [En-us-cap.ogg](https://commons.wikimedia.org/wiki/File:En-us-cap.ogg) â word "cap" +- [En-us-car.ogg](https://commons.wikimedia.org/wiki/File:En-us-car.ogg) â word "car" +- [En-us-cart.ogg](https://commons.wikimedia.org/wiki/File:En-us-cart.ogg) â word "cart" +- [En-us-cat.ogg](https://commons.wikimedia.org/wiki/File:En-us-cat.ogg) â phoneme /k/ â cut 0.02â0.14s of the original; word "cat" +- [En-us-chain.ogg](https://commons.wikimedia.org/wiki/File:En-us-chain.ogg) â word "chain" +- [En-us-chest.ogg](https://commons.wikimedia.org/wiki/File:En-us-chest.ogg) â word "chest" +- [En-us-chick.ogg](https://commons.wikimedia.org/wiki/File:En-us-chick.ogg) â phoneme /ch/ â cut 0.09â0.24s of the original; word "chick" +- [En-us-chicken.ogg](https://commons.wikimedia.org/wiki/File:En-us-chicken.ogg) â word "chicken" +- [En-us-clock.ogg](https://commons.wikimedia.org/wiki/File:En-us-clock.ogg) â word "clock" +- [En-us-cloud.ogg](https://commons.wikimedia.org/wiki/File:En-us-cloud.ogg) â word "cloud" +- [En-us-coat.ogg](https://commons.wikimedia.org/wiki/File:En-us-coat.ogg) â word "coat" +- [En-us-coin.ogg](https://commons.wikimedia.org/wiki/File:En-us-coin.ogg) â word "coin" +- [En-us-comet.ogg](https://commons.wikimedia.org/wiki/File:En-us-comet.ogg) â word "comet" +- [En-us-cook.ogg](https://commons.wikimedia.org/wiki/File:En-us-cook.ogg) â word "cook" +- [En-us-corn.ogg](https://commons.wikimedia.org/wiki/File:En-us-corn.ogg) â word "corn" +- [En-us-cow.ogg](https://commons.wikimedia.org/wiki/File:En-us-cow.ogg) â word "cow" +- [En-us-crab.ogg](https://commons.wikimedia.org/wiki/File:En-us-crab.ogg) â word "crab" +- [En-us-cub.ogg](https://commons.wikimedia.org/wiki/File:En-us-cub.ogg) â word "cub" +- [En-us-cup.ogg](https://commons.wikimedia.org/wiki/File:En-us-cup.ogg) â word "cup" +- [En-us-cut.ogg](https://commons.wikimedia.org/wiki/File:En-us-cut.ogg) â word "cut" +- [En-us-dad.ogg](https://commons.wikimedia.org/wiki/File:En-us-dad.ogg) â word "dad" +- [En-us-deer.ogg](https://commons.wikimedia.org/wiki/File:En-us-deer.ogg) â word "deer" +- [En-us-dig.ogg](https://commons.wikimedia.org/wiki/File:En-us-dig.ogg) â word "dig" +- [En-us-dish.ogg](https://commons.wikimedia.org/wiki/File:En-us-dish.ogg) â word "dish" +- [En-us-doctor.ogg](https://commons.wikimedia.org/wiki/File:En-us-doctor.ogg) â word "doctor" +- [En-us-dog.ogg](https://commons.wikimedia.org/wiki/File:En-us-dog.ogg) â phoneme /d/ â cut 0.12â0.19s of the original; word "dog" +- [En-us-doll.ogg](https://commons.wikimedia.org/wiki/File:En-us-doll.ogg) â word "doll" +- [En-us-dress.ogg](https://commons.wikimedia.org/wiki/File:En-us-dress.ogg) â word "dress" +- [En-us-drum.ogg](https://commons.wikimedia.org/wiki/File:En-us-drum.ogg) â word "drum" +- [En-us-duck.ogg](https://commons.wikimedia.org/wiki/File:En-us-duck.ogg) â word "duck" +- [En-us-egg.ogg](https://commons.wikimedia.org/wiki/File:En-us-egg.ogg) â word "egg" +- [En-us-er.ogg](https://commons.wikimedia.org/wiki/File:En-us-er.ogg) â phoneme /er/ â cut 0.00â0.45s of the original +- [En-us-feet.ogg](https://commons.wikimedia.org/wiki/File:En-us-feet.ogg) â word "feet" +- [En-us-fog.ogg](https://commons.wikimedia.org/wiki/File:En-us-fog.ogg) â word "fog" +- [En-us-foot.ogg](https://commons.wikimedia.org/wiki/File:En-us-foot.ogg) â word "foot" +- [En-us-fork.ogg](https://commons.wikimedia.org/wiki/File:En-us-fork.ogg) â word "fork" +- [En-us-fossil.ogg](https://commons.wikimedia.org/wiki/File:En-us-fossil.ogg) â word "fossil" +- [En-us-fox.ogg](https://commons.wikimedia.org/wiki/File:En-us-fox.ogg) â word "fox" +- [En-us-frog.ogg](https://commons.wikimedia.org/wiki/File:En-us-frog.ogg) â word "frog" +- [En-us-gem.ogg](https://commons.wikimedia.org/wiki/File:En-us-gem.ogg) â word "gem" +- [En-us-gift.ogg](https://commons.wikimedia.org/wiki/File:En-us-gift.ogg) â word "gift" +- [En-us-goat.ogg](https://commons.wikimedia.org/wiki/File:En-us-goat.ogg) â phoneme /g/ â cut 0.07â0.14s of the original; word "goat" +- [En-us-goblin.ogg](https://commons.wikimedia.org/wiki/File:En-us-goblin.ogg) â word "goblin" +- [En-us-ham.ogg](https://commons.wikimedia.org/wiki/File:En-us-ham.ogg) â word "ham" +- [En-us-hammer.ogg](https://commons.wikimedia.org/wiki/File:En-us-hammer.ogg) â word "hammer" +- [En-us-hand.ogg](https://commons.wikimedia.org/wiki/File:En-us-hand.ogg) â word "hand" +- [En-us-hat.ogg](https://commons.wikimedia.org/wiki/File:En-us-hat.ogg) â word "hat" +- [En-us-hen.ogg](https://commons.wikimedia.org/wiki/File:En-us-hen.ogg) â word "hen" +- [En-us-hiss.ogg](https://commons.wikimedia.org/wiki/File:En-us-hiss.ogg) â word "hiss" +- [En-us-hook.ogg](https://commons.wikimedia.org/wiki/File:En-us-hook.ogg) â word "hook" +- [En-us-horn.ogg](https://commons.wikimedia.org/wiki/File:En-us-horn.ogg) â word "horn" +- [En-us-hot.ogg](https://commons.wikimedia.org/wiki/File:En-us-hot.ogg) â word "hot" +- [En-us-hug.ogg](https://commons.wikimedia.org/wiki/File:En-us-hug.ogg) â word "hug" +- [En-us-hut.ogg](https://commons.wikimedia.org/wiki/File:En-us-hut.ogg) â word "hut" +- [En-us-insect.ogg](https://commons.wikimedia.org/wiki/File:En-us-insect.ogg) â word "insect" +- [En-us-jam.ogg](https://commons.wikimedia.org/wiki/File:En-us-jam.ogg) â phoneme /j/ â cut 0.30â0.40s of the original; word "jam" +- [En-us-jar.ogg](https://commons.wikimedia.org/wiki/File:En-us-jar.ogg) â word "jar" +- [En-us-kid.ogg](https://commons.wikimedia.org/wiki/File:En-us-kid.ogg) â word "kid" +- [En-us-king.ogg](https://commons.wikimedia.org/wiki/File:En-us-king.ogg) â word "king" +- [En-us-kitten.ogg](https://commons.wikimedia.org/wiki/File:En-us-kitten.ogg) â word "kitten" +- [En-us-ladder.ogg](https://commons.wikimedia.org/wiki/File:En-us-ladder.ogg) â word "ladder" +- [En-us-lamp.ogg](https://commons.wikimedia.org/wiki/File:En-us-lamp.ogg) â word "lamp" +- [En-us-light.ogg](https://commons.wikimedia.org/wiki/File:En-us-light.ogg) â word "light" +- [En-us-lightning.ogg](https://commons.wikimedia.org/wiki/File:En-us-lightning.ogg) â word "lightning" +- [En-us-lips.ogg](https://commons.wikimedia.org/wiki/File:En-us-lips.ogg) â word "lips" +- [En-us-lizard.ogg](https://commons.wikimedia.org/wiki/File:En-us-lizard.ogg) â word "lizard" +- [En-us-lobster.ogg](https://commons.wikimedia.org/wiki/File:En-us-lobster.ogg) â word "lobster" +- [En-us-lock.ogg](https://commons.wikimedia.org/wiki/File:En-us-lock.ogg) â word "lock" +- [En-us-log.ogg](https://commons.wikimedia.org/wiki/File:En-us-log.ogg) â word "log" +- [En-us-lunch.ogg](https://commons.wikimedia.org/wiki/File:En-us-lunch.ogg) â word "lunch" +- [En-us-mad.ogg](https://commons.wikimedia.org/wiki/File:En-us-mad.ogg) â word "mad" +- [En-us-magic.ogg](https://commons.wikimedia.org/wiki/File:En-us-magic.ogg) â word "magic" +- [En-us-map.ogg](https://commons.wikimedia.org/wiki/File:En-us-map.ogg) â word "map" +- [En-us-melon.ogg](https://commons.wikimedia.org/wiki/File:En-us-melon.ogg) â word "melon" +- [En-us-milk.ogg](https://commons.wikimedia.org/wiki/File:En-us-milk.ogg) â word "milk" +- [En-us-mom.ogg](https://commons.wikimedia.org/wiki/File:En-us-mom.ogg) â word "mom" +- [En-us-moon.ogg](https://commons.wikimedia.org/wiki/File:En-us-moon.ogg) â word "moon" +- [En-us-moth.ogg](https://commons.wikimedia.org/wiki/File:En-us-moth.ogg) â word "moth" +- [En-us-mouth.ogg](https://commons.wikimedia.org/wiki/File:En-us-mouth.ogg) â word "mouth" +- [En-us-mug.ogg](https://commons.wikimedia.org/wiki/File:En-us-mug.ogg) â word "mug" +- [En-us-mushroom.ogg](https://commons.wikimedia.org/wiki/File:En-us-mushroom.ogg) â word "mushroom" +- [En-us-nest.ogg](https://commons.wikimedia.org/wiki/File:En-us-nest.ogg) â word "nest" +- [En-us-net.ogg](https://commons.wikimedia.org/wiki/File:En-us-net.ogg) â word "net" +- [En-us-night.ogg](https://commons.wikimedia.org/wiki/File:En-us-night.ogg) â word "night" +- [En-us-nut.ogg](https://commons.wikimedia.org/wiki/File:En-us-nut.ogg) â word "nut" +- [En-us-octopus.ogg](https://commons.wikimedia.org/wiki/File:En-us-octopus.ogg) â word "octopus" +- [En-us-or.ogg](https://commons.wikimedia.org/wiki/File:En-us-or.ogg) â phoneme /or/ â cut 0.00â0.40s of the original +- [En-us-ow.ogg](https://commons.wikimedia.org/wiki/File:En-us-ow.ogg) â phoneme /ow/ â cut 0.00â0.40s of the original +- [En-us-owl.ogg](https://commons.wikimedia.org/wiki/File:En-us-owl.ogg) â word "owl" +- [En-us-oyster.ogg](https://commons.wikimedia.org/wiki/File:En-us-oyster.ogg) â word "oyster" +- [En-us-paint.ogg](https://commons.wikimedia.org/wiki/File:En-us-paint.ogg) â word "paint" +- [En-us-pan.ogg](https://commons.wikimedia.org/wiki/File:En-us-pan.ogg) â word "pan" +- [En-us-pants.ogg](https://commons.wikimedia.org/wiki/File:En-us-pants.ogg) â word "pants" +- [En-us-park.ogg](https://commons.wikimedia.org/wiki/File:En-us-park.ogg) â word "park" +- [En-us-parrot.ogg](https://commons.wikimedia.org/wiki/File:En-us-parrot.ogg) â word "parrot" +- [En-us-pen.ogg](https://commons.wikimedia.org/wiki/File:En-us-pen.ogg) â word "pen" +- [En-us-pig.ogg](https://commons.wikimedia.org/wiki/File:En-us-pig.ogg) â phoneme /p/ â cut 0.09â0.18s of the original; word "pig" +- [En-us-planet.ogg](https://commons.wikimedia.org/wiki/File:En-us-planet.ogg) â word "planet" +- [En-us-plant.ogg](https://commons.wikimedia.org/wiki/File:En-us-plant.ogg) â word "plant" +- [En-us-plug.ogg](https://commons.wikimedia.org/wiki/File:En-us-plug.ogg) â word "plug" +- [En-us-pot.ogg](https://commons.wikimedia.org/wiki/File:En-us-pot.ogg) â word "pot" +- [En-us-quack.ogg](https://commons.wikimedia.org/wiki/File:En-us-quack.ogg) â word "quack" +- [En-us-queen.ogg](https://commons.wikimedia.org/wiki/File:En-us-queen.ogg) â word "queen" +- [En-us-rabbit.ogg](https://commons.wikimedia.org/wiki/File:En-us-rabbit.ogg) â word "rabbit" +- [En-us-rain.ogg](https://commons.wikimedia.org/wiki/File:En-us-rain.ogg) â word "rain" +- [En-us-rainbow.ogg](https://commons.wikimedia.org/wiki/File:En-us-rainbow.ogg) â word "rainbow" +- [En-us-ram.ogg](https://commons.wikimedia.org/wiki/File:En-us-ram.ogg) â word "ram" +- [En-us-rat.ogg](https://commons.wikimedia.org/wiki/File:En-us-rat.ogg) â word "rat" +- [En-us-ring.ogg](https://commons.wikimedia.org/wiki/File:En-us-ring.ogg) â word "ring" +- [En-us-road.ogg](https://commons.wikimedia.org/wiki/File:En-us-road.ogg) â word "road" +- [En-us-roar.ogg](https://commons.wikimedia.org/wiki/File:En-us-roar.ogg) â word "roar" +- [En-us-robot.ogg](https://commons.wikimedia.org/wiki/File:En-us-robot.ogg) â word "robot" +- [En-us-rock.ogg](https://commons.wikimedia.org/wiki/File:En-us-rock.ogg) â word "rock" +- [En-us-rocket.ogg](https://commons.wikimedia.org/wiki/File:En-us-rocket.ogg) â word "rocket" +- [En-us-rooster.ogg](https://commons.wikimedia.org/wiki/File:En-us-rooster.ogg) â word "rooster" +- [En-us-run.ogg](https://commons.wikimedia.org/wiki/File:En-us-run.ogg) â word "run" +- [En-us-sad.ogg](https://commons.wikimedia.org/wiki/File:En-us-sad.ogg) â word "sad" +- [En-us-scooter.ogg](https://commons.wikimedia.org/wiki/File:En-us-scooter.ogg) â word "scooter" +- [En-us-seed.ogg](https://commons.wikimedia.org/wiki/File:En-us-seed.ogg) â word "seed" +- [En-us-shark.ogg](https://commons.wikimedia.org/wiki/File:En-us-shark.ogg) â word "shark" +- [En-us-sheep.ogg](https://commons.wikimedia.org/wiki/File:En-us-sheep.ogg) â word "sheep" +- [En-us-shell.ogg](https://commons.wikimedia.org/wiki/File:En-us-shell.ogg) â word "shell" +- [En-us-ship.ogg](https://commons.wikimedia.org/wiki/File:En-us-ship.ogg) â word "ship" +- [En-us-shrimp.ogg](https://commons.wikimedia.org/wiki/File:En-us-shrimp.ogg) â word "shrimp" +- [En-us-sing.ogg](https://commons.wikimedia.org/wiki/File:En-us-sing.ogg) â word "sing" +- [En-us-sleep.ogg](https://commons.wikimedia.org/wiki/File:En-us-sleep.ogg) â word "sleep" +- [En-us-snail.ogg](https://commons.wikimedia.org/wiki/File:En-us-snail.ogg) â word "snail" +- [En-us-snap.ogg](https://commons.wikimedia.org/wiki/File:En-us-snap.ogg) â word "snap" +- [En-us-snow.ogg](https://commons.wikimedia.org/wiki/File:En-us-snow.ogg) â word "snow" +- [En-us-sock.ogg](https://commons.wikimedia.org/wiki/File:En-us-sock.ogg) â word "sock" +- [En-us-speedboat.ogg](https://commons.wikimedia.org/wiki/File:En-us-speedboat.ogg) â word "speedboat" +- [En-us-spin.ogg](https://commons.wikimedia.org/wiki/File:En-us-spin.ogg) â word "spin" +- [En-us-spoon.ogg](https://commons.wikimedia.org/wiki/File:En-us-spoon.ogg) â word "spoon" +- [En-us-storm.ogg](https://commons.wikimedia.org/wiki/File:En-us-storm.ogg) â word "storm" +- [En-us-sun.ogg](https://commons.wikimedia.org/wiki/File:En-us-sun.ogg) â word "sun" +- [En-us-sunset.ogg](https://commons.wikimedia.org/wiki/File:En-us-sunset.ogg) â word "sunset" +- [En-us-swim.ogg](https://commons.wikimedia.org/wiki/File:En-us-swim.ogg) â word "swim" +- [En-us-target.ogg](https://commons.wikimedia.org/wiki/File:En-us-target.ogg) â word "target" +- [En-us-teeth.ogg](https://commons.wikimedia.org/wiki/File:En-us-teeth.ogg) â word "teeth" +- [En-us-tent.ogg](https://commons.wikimedia.org/wiki/File:En-us-tent.ogg) â word "tent" +- [En-us-thunder.ogg](https://commons.wikimedia.org/wiki/File:En-us-thunder.ogg) â word "thunder" +- [En-us-ticket.ogg](https://commons.wikimedia.org/wiki/File:En-us-ticket.ogg) â word "ticket" +- [En-us-toilet.ogg](https://commons.wikimedia.org/wiki/File:En-us-toilet.ogg) â word "toilet" +- [En-us-tractor.ogg](https://commons.wikimedia.org/wiki/File:En-us-tractor.ogg) â word "tractor" +- [En-us-train.ogg](https://commons.wikimedia.org/wiki/File:En-us-train.ogg) â word "train" +- [En-us-tree.ogg](https://commons.wikimedia.org/wiki/File:En-us-tree.ogg) â word "tree" +- [En-us-truck.ogg](https://commons.wikimedia.org/wiki/File:En-us-truck.ogg) â word "truck" +- [En-us-van.ogg](https://commons.wikimedia.org/wiki/File:En-us-van.ogg) â word "van" +- [En-us-vest.ogg](https://commons.wikimedia.org/wiki/File:En-us-vest.ogg) â word "vest" +- [En-us-web.ogg](https://commons.wikimedia.org/wiki/File:En-us-web.ogg) â word "web" +- [En-us-wheel.ogg](https://commons.wikimedia.org/wiki/File:En-us-wheel.ogg) â word "wheel" +- [En-us-wig.ogg](https://commons.wikimedia.org/wiki/File:En-us-wig.ogg) â word "wig" +- [En-us-window.ogg](https://commons.wikimedia.org/wiki/File:En-us-window.ogg) â word "window" +- [En-us-wing.ogg](https://commons.wikimedia.org/wiki/File:En-us-wing.ogg) â word "wing" +- [En-us-witch.ogg](https://commons.wikimedia.org/wiki/File:En-us-witch.ogg) â word "witch" +- [En-us-zap.ogg](https://commons.wikimedia.org/wiki/File:En-us-zap.ogg) â word "zap" +- [En-us-zip.ogg](https://commons.wikimedia.org/wiki/File:En-us-zip.ogg) â phoneme /z/ â cut 0.02â0.19s of the original (held by time-stretching); word "zip" + +### Denelson83 â [CC BY-SA 3.0](http://creativecommons.org/licenses/by-sa/3.0/) + +- [Close-mid_back_rounded_vowel.ogg](https://commons.wikimedia.org/wiki/File:Close-mid_back_rounded_vowel.ogg) â phoneme /oa/ â cut 0.02â0.47s of the original +- [Close_back_rounded_vowel.ogg](https://commons.wikimedia.org/wiki/File:Close_back_rounded_vowel.ogg) â phoneme /oo/ â cut 0.01â0.46s of the original (held by time-stretching) +- [Close_front_unrounded_vowel.ogg](https://commons.wikimedia.org/wiki/File:Close_front_unrounded_vowel.ogg) â phoneme /ee/ â cut 0.02â0.47s of the original (held by time-stretching) +- [Near-close_near-back_rounded_vowel.ogg](https://commons.wikimedia.org/wiki/File:Near-close_near-back_rounded_vowel.ogg) â phoneme /uu/ â cut 0.01â0.46s of the original (held by time-stretching) +- [Near-close_near-front_unrounded_vowel.ogg](https://commons.wikimedia.org/wiki/File:Near-close_near-front_unrounded_vowel.ogg) â phoneme /i/ â cut 0.02â0.47s of the original (held by time-stretching) +- [Near-open_front_unrounded_vowel.ogg](https://commons.wikimedia.org/wiki/File:Near-open_front_unrounded_vowel.ogg) â phoneme /a/ â cut 0.03â0.48s of the original (held by time-stretching) +- [Open-mid_back_unrounded_vowel.ogg](https://commons.wikimedia.org/wiki/File:Open-mid_back_unrounded_vowel.ogg) â phoneme /u/ â cut 0.03â0.48s of the original (held by time-stretching) +- [Open-mid_front_unrounded_vowel.ogg](https://commons.wikimedia.org/wiki/File:Open-mid_front_unrounded_vowel.ogg) â phoneme /e/ â cut 0.03â0.48s of the original (held by time-stretching) +- [Open_back_unrounded_vowel.ogg](https://commons.wikimedia.org/wiki/File:Open_back_unrounded_vowel.ogg) â phoneme /o/ â cut 0.01â0.46s of the original (held by time-stretching) + +### Karmosin~commonswiki â [CC BY-SA 3.0](http://creativecommons.org/licenses/by-sa/3.0/) + +- [Alveolar_lateral_approximant.ogg](https://commons.wikimedia.org/wiki/File:Alveolar_lateral_approximant.ogg) â phoneme /l/ â cut 0.22â0.50s of the original (held by time-stretching) +- [Bilabial_nasal.ogg](https://commons.wikimedia.org/wiki/File:Bilabial_nasal.ogg) â phoneme /m/ â cut 0.21â0.38s of the original (held by time-stretching) +- [Palatal_approximant.ogg](https://commons.wikimedia.org/wiki/File:Palatal_approximant.ogg) â phoneme /y/ â cut 0.26â0.43s of the original +- [Velar_nasal.ogg](https://commons.wikimedia.org/wiki/File:Velar_nasal.ogg) â phoneme /ng/ â cut 0.20â0.33s of the original (held by time-stretching) +- [Voiced_labio-velar_approximant.ogg](https://commons.wikimedia.org/wiki/File:Voiced_labio-velar_approximant.ogg) â phoneme /w/ â cut 0.02â0.23s of the original +- [Voiced_labiodental_fricative.ogg](https://commons.wikimedia.org/wiki/File:Voiced_labiodental_fricative.ogg) â phoneme /v/ â cut 0.28â0.46s of the original (held by time-stretching) +- [Voiceless_glottal_fricative.ogg](https://commons.wikimedia.org/wiki/File:Voiceless_glottal_fricative.ogg) â phoneme /h/ â cut 0.22â0.48s of the original +- [Voiceless_labiodental_fricative.ogg](https://commons.wikimedia.org/wiki/File:Voiceless_labiodental_fricative.ogg) â phoneme /f/ â cut 0.16â0.52s of the original (held by time-stretching) + +### Dvortygirl â Public domain + +- [En-us-brick.ogg](https://commons.wikimedia.org/wiki/File:En-us-brick.ogg) â word "brick" +- [En-us-fish.ogg](https://commons.wikimedia.org/wiki/File:En-us-fish.ogg) â word "fish" +- [En-us-flag.ogg](https://commons.wikimedia.org/wiki/File:En-us-flag.ogg) â word "flag" +- [En-us-squid.ogg](https://commons.wikimedia.org/wiki/File:En-us-squid.ogg) â word "squid" +- [En-us-tap.ogg](https://commons.wikimedia.org/wiki/File:En-us-tap.ogg) â phoneme /t/ â cut 0.07â0.16s of the original; word "tap" +- [En-us-thumb.ogg](https://commons.wikimedia.org/wiki/File:En-us-thumb.ogg) â word "thumb" +- [En-us-toy.ogg](https://commons.wikimedia.org/wiki/File:En-us-toy.ogg) â word "toy" + +### No machine-readable author provided. Dvortygirl assumed (based on copyright claims). â [CC BY-SA 3.0](http://creativecommons.org/licenses/by-sa/3.0/) + +- [En-us-carrot.ogg](https://commons.wikimedia.org/wiki/File:En-us-carrot.ogg) â word "carrot" +- [En-us-day.ogg](https://commons.wikimedia.org/wiki/File:En-us-day.ogg) â phoneme /ai/ â cut 0.16â0.46s of the original +- [En-us-pie.ogg](https://commons.wikimedia.org/wiki/File:En-us-pie.ogg) â word "pie" +- [En-us-star.ogg](https://commons.wikimedia.org/wiki/File:En-us-star.ogg) â word "star" +- [En-us-tin.ogg](https://commons.wikimedia.org/wiki/File:En-us-tin.ogg) â word "tin" + +### Sylvanmoon â [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0) + +- [En-us-bang.ogg](https://commons.wikimedia.org/wiki/File:En-us-bang.ogg) â word "bang" +- [En-us-buzz.ogg](https://commons.wikimedia.org/wiki/File:En-us-buzz.ogg) â word "buzz" +- [En-us-mango.ogg](https://commons.wikimedia.org/wiki/File:En-us-mango.ogg) â word "mango" +- [En-us-mask.ogg](https://commons.wikimedia.org/wiki/File:En-us-mask.ogg) â word "mask" +- [En-us-quiz.ogg](https://commons.wikimedia.org/wiki/File:En-us-quiz.ogg) â word "quiz" + +### Muke â Public domain + +- [En-us-bin.ogg](https://commons.wikimedia.org/wiki/File:En-us-bin.ogg) â word "bin" +- [En-us-dragon.ogg](https://commons.wikimedia.org/wiki/File:En-us-dragon.ogg) â word "dragon" +- [En-us-eye.ogg](https://commons.wikimedia.org/wiki/File:En-us-eye.ogg) â phoneme /igh/ â cut 0.00â0.72s of the original +- [En-us-pin.ogg](https://commons.wikimedia.org/wiki/File:En-us-pin.ogg) â word "pin" + +### Peter Isotalo â [CC BY-SA 3.0](http://creativecommons.org/licenses/by-sa/3.0/) + +- [Alveolar_nasal.ogg](https://commons.wikimedia.org/wiki/File:Alveolar_nasal.ogg) â phoneme /n/ â cut 0.40â0.50s of the original (held by time-stretching) +- [Voiceless_alveolar_sibilant.ogg](https://commons.wikimedia.org/wiki/File:Voiceless_alveolar_sibilant.ogg) â phoneme /s/ â cut 0.24â0.45s of the original (held by time-stretching) +- [Voiceless_dental_fricative.ogg](https://commons.wikimedia.org/wiki/File:Voiceless_dental_fricative.ogg) â phoneme /th/ â cut 0.08â0.29s of the original (held by time-stretching) +- [Voiceless_palato-alveolar_sibilant.ogg](https://commons.wikimedia.org/wiki/File:Voiceless_palato-alveolar_sibilant.ogg) â phoneme /sh/ â cut 0.18â0.45s of the original (held by time-stretching) + +### EncycloPetey â [CC BY-SA 3.0](https://creativecommons.org/licenses/by-sa/3.0) + +- [En-us-lollipop.ogg](https://commons.wikimedia.org/wiki/File:En-us-lollipop.ogg) â word "lollipop" +- [En-us-muffin.ogg](https://commons.wikimedia.org/wiki/File:En-us-muffin.ogg) â word "muffin" +- [En-us-raccoon.ogg](https://commons.wikimedia.org/wiki/File:En-us-raccoon.ogg) â word "raccoon" + +### EncycloPetey â [CC BY-SA 3.0](http://creativecommons.org/licenses/by-sa/3.0/) + +- [En-us-monster.ogg](https://commons.wikimedia.org/wiki/File:En-us-monster.ogg) â word "monster" +- [En-us-pumpkin.ogg](https://commons.wikimedia.org/wiki/File:En-us-pumpkin.ogg) â word "pumpkin" + +### K6ka â [CC0](http://creativecommons.org/publicdomain/zero/1.0/deed.en) + +- [En-us-jeep.ogg](https://commons.wikimedia.org/wiki/File:En-us-jeep.ogg) â word "jeep" +- [En-us-ox.ogg](https://commons.wikimedia.org/wiki/File:En-us-ox.ogg) â word "ox" + +### Mx. Granger â [CC0](http://creativecommons.org/publicdomain/zero/1.0/deed.en) + +- [En-us-blossom.ogg](https://commons.wikimedia.org/wiki/File:En-us-blossom.ogg) â word "blossom" +- [En-us-moo.ogg](https://commons.wikimedia.org/wiki/File:En-us-moo.ogg) â word "moo" + +### Neskaya â [CC BY-SA 3.0](http://creativecommons.org/licenses/by-sa/3.0/) + +- [En-us-lantern.ogg](https://commons.wikimedia.org/wiki/File:En-us-lantern.ogg) â word "lantern" +- [En-us-toast.ogg](https://commons.wikimedia.org/wiki/File:En-us-toast.ogg) â word "toast" + +### Paul2520 â [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0) + +- [En-us-crayon.ogg](https://commons.wikimedia.org/wiki/File:En-us-crayon.ogg) â word "crayon" +- [En-us-popcorn.ogg](https://commons.wikimedia.org/wiki/File:En-us-popcorn.ogg) â word "popcorn" + +### TheDaveRoss â [CC BY-SA 3.0](http://creativecommons.org/licenses/by-sa/3.0/) + +- [En-us-six.ogg](https://commons.wikimedia.org/wiki/File:En-us-six.ogg) â word "six" +- [En-us-ten.ogg](https://commons.wikimedia.org/wiki/File:En-us-ten.ogg) â word "ten" + +### BirdHopper â [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0) + +- [En-us-chips.ogg](https://commons.wikimedia.org/wiki/File:En-us-chips.ogg) â word "chips" + +### Dinoroki â [CC0](http://creativecommons.org/publicdomain/zero/1.0/deed.en) + +- [En-us-boo.ogg](https://commons.wikimedia.org/wiki/File:En-us-boo.ogg) â word "boo" + +### Erutuon â [CC BY-SA 3.0](http://creativecommons.org/licenses/by-sa/3.0/) + +- [Alveolar_approximant.ogg](https://commons.wikimedia.org/wiki/File:Alveolar_approximant.ogg) â phoneme /r/ â cut 0.29â0.49s of the original (held by time-stretching) + +### G. Allen Morris III â [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0) + +- [En-us-laptop.ogg](https://commons.wikimedia.org/wiki/File:En-us-laptop.ogg) â word "laptop" + +### No machine-readable author provided. Muke assumed (based on copyright claims). â Public domain + +- [En-us-elf.ogg](https://commons.wikimedia.org/wiki/File:En-us-elf.ogg) â word "elf" + +### Rappo â [CC BY-SA 3.0](http://creativecommons.org/licenses/by-sa/3.0/) + +- [En-us-sandwich.ogg](https://commons.wikimedia.org/wiki/File:En-us-sandwich.ogg) â word "sandwich" + +### TreeMama â [CC BY-SA 3.0](http://creativecommons.org/licenses/by-sa/3.0/) + +- [En-us-jump.ogg](https://commons.wikimedia.org/wiki/File:En-us-jump.ogg) â word "jump" + +### user:Dvortygirl â [CC BY-SA 3.0](http://creativecommons.org/licenses/by-sa/3.0/) + +- [En-us-soap.ogg](https://commons.wikimedia.org/wiki/File:En-us-soap.ogg) â word "soap" + +## Speech + +Instructions, patron chatter, and any word without a recording are voiced by the browser's built-in speech synthesis (Web Speech API); nothing is sent to a server. diff --git a/games/story-stamp/assets/audio/phonemes/a.mp3 b/games/story-stamp/assets/audio/phonemes/a.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/phonemes/ai.mp3 b/games/story-stamp/assets/audio/phonemes/ai.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/phonemes/ar.mp3 b/games/story-stamp/assets/audio/phonemes/ar.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/phonemes/b.mp3 b/games/story-stamp/assets/audio/phonemes/b.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/phonemes/ch.mp3 b/games/story-stamp/assets/audio/phonemes/ch.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/phonemes/d.mp3 b/games/story-stamp/assets/audio/phonemes/d.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/phonemes/e.mp3 b/games/story-stamp/assets/audio/phonemes/e.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/phonemes/ee.mp3 b/games/story-stamp/assets/audio/phonemes/ee.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/phonemes/er.mp3 b/games/story-stamp/assets/audio/phonemes/er.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/phonemes/f.mp3 b/games/story-stamp/assets/audio/phonemes/f.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/phonemes/g.mp3 b/games/story-stamp/assets/audio/phonemes/g.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/phonemes/h.mp3 b/games/story-stamp/assets/audio/phonemes/h.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/phonemes/i.mp3 b/games/story-stamp/assets/audio/phonemes/i.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/phonemes/igh.mp3 b/games/story-stamp/assets/audio/phonemes/igh.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/phonemes/j.mp3 b/games/story-stamp/assets/audio/phonemes/j.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/phonemes/k.mp3 b/games/story-stamp/assets/audio/phonemes/k.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/phonemes/l.mp3 b/games/story-stamp/assets/audio/phonemes/l.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/phonemes/m.mp3 b/games/story-stamp/assets/audio/phonemes/m.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/phonemes/n.mp3 b/games/story-stamp/assets/audio/phonemes/n.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/phonemes/ng.mp3 b/games/story-stamp/assets/audio/phonemes/ng.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/phonemes/o.mp3 b/games/story-stamp/assets/audio/phonemes/o.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/phonemes/oa.mp3 b/games/story-stamp/assets/audio/phonemes/oa.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/phonemes/oi.mp3 b/games/story-stamp/assets/audio/phonemes/oi.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/phonemes/oo.mp3 b/games/story-stamp/assets/audio/phonemes/oo.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/phonemes/or.mp3 b/games/story-stamp/assets/audio/phonemes/or.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/phonemes/ow.mp3 b/games/story-stamp/assets/audio/phonemes/ow.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/phonemes/p.mp3 b/games/story-stamp/assets/audio/phonemes/p.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/phonemes/r.mp3 b/games/story-stamp/assets/audio/phonemes/r.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/phonemes/s.mp3 b/games/story-stamp/assets/audio/phonemes/s.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/phonemes/sh.mp3 b/games/story-stamp/assets/audio/phonemes/sh.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/phonemes/t.mp3 b/games/story-stamp/assets/audio/phonemes/t.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/phonemes/th.mp3 b/games/story-stamp/assets/audio/phonemes/th.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/phonemes/u.mp3 b/games/story-stamp/assets/audio/phonemes/u.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/phonemes/uu.mp3 b/games/story-stamp/assets/audio/phonemes/uu.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/phonemes/v.mp3 b/games/story-stamp/assets/audio/phonemes/v.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/phonemes/w.mp3 b/games/story-stamp/assets/audio/phonemes/w.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/phonemes/y.mp3 b/games/story-stamp/assets/audio/phonemes/y.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/phonemes/z.mp3 b/games/story-stamp/assets/audio/phonemes/z.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/ant.mp3 b/games/story-stamp/assets/audio/words/ant.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/arm.mp3 b/games/story-stamp/assets/audio/words/arm.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/art.mp3 b/games/story-stamp/assets/audio/words/art.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/bag.mp3 b/games/story-stamp/assets/audio/words/bag.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/balloon.mp3 b/games/story-stamp/assets/audio/words/balloon.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/bang.mp3 b/games/story-stamp/assets/audio/words/bang.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/basket.mp3 b/games/story-stamp/assets/audio/words/basket.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/bat.mp3 b/games/story-stamp/assets/audio/words/bat.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/bath.mp3 b/games/story-stamp/assets/audio/words/bath.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/bed.mp3 b/games/story-stamp/assets/audio/words/bed.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/bee.mp3 b/games/story-stamp/assets/audio/words/bee.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/bell.mp3 b/games/story-stamp/assets/audio/words/bell.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/big.mp3 b/games/story-stamp/assets/audio/words/big.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/bin.mp3 b/games/story-stamp/assets/audio/words/bin.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/blossom.mp3 b/games/story-stamp/assets/audio/words/blossom.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/boat.mp3 b/games/story-stamp/assets/audio/words/boat.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/boo.mp3 b/games/story-stamp/assets/audio/words/boo.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/book.mp3 b/games/story-stamp/assets/audio/words/book.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/boot.mp3 b/games/story-stamp/assets/audio/words/boot.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/box.mp3 b/games/story-stamp/assets/audio/words/box.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/boy.mp3 b/games/story-stamp/assets/audio/words/boy.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/brain.mp3 b/games/story-stamp/assets/audio/words/brain.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/brick.mp3 b/games/story-stamp/assets/audio/words/brick.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/brush.mp3 b/games/story-stamp/assets/audio/words/brush.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/bucket.mp3 b/games/story-stamp/assets/audio/words/bucket.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/bug.mp3 b/games/story-stamp/assets/audio/words/bug.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/bus.mp3 b/games/story-stamp/assets/audio/words/bus.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/butter.mp3 b/games/story-stamp/assets/audio/words/butter.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/buzz.mp3 b/games/story-stamp/assets/audio/words/buzz.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/cab.mp3 b/games/story-stamp/assets/audio/words/cab.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/cactus.mp3 b/games/story-stamp/assets/audio/words/cactus.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/camel.mp3 b/games/story-stamp/assets/audio/words/camel.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/camp.mp3 b/games/story-stamp/assets/audio/words/camp.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/can.mp3 b/games/story-stamp/assets/audio/words/can.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/cap.mp3 b/games/story-stamp/assets/audio/words/cap.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/car.mp3 b/games/story-stamp/assets/audio/words/car.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/carrot.mp3 b/games/story-stamp/assets/audio/words/carrot.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/cart.mp3 b/games/story-stamp/assets/audio/words/cart.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/cat.mp3 b/games/story-stamp/assets/audio/words/cat.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/chain.mp3 b/games/story-stamp/assets/audio/words/chain.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/chest.mp3 b/games/story-stamp/assets/audio/words/chest.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/chick.mp3 b/games/story-stamp/assets/audio/words/chick.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/chicken.mp3 b/games/story-stamp/assets/audio/words/chicken.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/chips.mp3 b/games/story-stamp/assets/audio/words/chips.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/clock.mp3 b/games/story-stamp/assets/audio/words/clock.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/cloud.mp3 b/games/story-stamp/assets/audio/words/cloud.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/coat.mp3 b/games/story-stamp/assets/audio/words/coat.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/coin.mp3 b/games/story-stamp/assets/audio/words/coin.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/comet.mp3 b/games/story-stamp/assets/audio/words/comet.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/cook.mp3 b/games/story-stamp/assets/audio/words/cook.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/corn.mp3 b/games/story-stamp/assets/audio/words/corn.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/cow.mp3 b/games/story-stamp/assets/audio/words/cow.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/crab.mp3 b/games/story-stamp/assets/audio/words/crab.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/crayon.mp3 b/games/story-stamp/assets/audio/words/crayon.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/cub.mp3 b/games/story-stamp/assets/audio/words/cub.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/cup.mp3 b/games/story-stamp/assets/audio/words/cup.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/cut.mp3 b/games/story-stamp/assets/audio/words/cut.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/dad.mp3 b/games/story-stamp/assets/audio/words/dad.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/deer.mp3 b/games/story-stamp/assets/audio/words/deer.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/dig.mp3 b/games/story-stamp/assets/audio/words/dig.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/dish.mp3 b/games/story-stamp/assets/audio/words/dish.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/doctor.mp3 b/games/story-stamp/assets/audio/words/doctor.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/dog.mp3 b/games/story-stamp/assets/audio/words/dog.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/doll.mp3 b/games/story-stamp/assets/audio/words/doll.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/dragon.mp3 b/games/story-stamp/assets/audio/words/dragon.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/dress.mp3 b/games/story-stamp/assets/audio/words/dress.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/drum.mp3 b/games/story-stamp/assets/audio/words/drum.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/duck.mp3 b/games/story-stamp/assets/audio/words/duck.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/egg.mp3 b/games/story-stamp/assets/audio/words/egg.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/elf.mp3 b/games/story-stamp/assets/audio/words/elf.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/feet.mp3 b/games/story-stamp/assets/audio/words/feet.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/fish.mp3 b/games/story-stamp/assets/audio/words/fish.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/flag.mp3 b/games/story-stamp/assets/audio/words/flag.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/fog.mp3 b/games/story-stamp/assets/audio/words/fog.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/foot.mp3 b/games/story-stamp/assets/audio/words/foot.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/fork.mp3 b/games/story-stamp/assets/audio/words/fork.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/fossil.mp3 b/games/story-stamp/assets/audio/words/fossil.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/fox.mp3 b/games/story-stamp/assets/audio/words/fox.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/frog.mp3 b/games/story-stamp/assets/audio/words/frog.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/gem.mp3 b/games/story-stamp/assets/audio/words/gem.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/gift.mp3 b/games/story-stamp/assets/audio/words/gift.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/goat.mp3 b/games/story-stamp/assets/audio/words/goat.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/goblin.mp3 b/games/story-stamp/assets/audio/words/goblin.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/ham.mp3 b/games/story-stamp/assets/audio/words/ham.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/hammer.mp3 b/games/story-stamp/assets/audio/words/hammer.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/hand.mp3 b/games/story-stamp/assets/audio/words/hand.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/hat.mp3 b/games/story-stamp/assets/audio/words/hat.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/hen.mp3 b/games/story-stamp/assets/audio/words/hen.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/hiss.mp3 b/games/story-stamp/assets/audio/words/hiss.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/hook.mp3 b/games/story-stamp/assets/audio/words/hook.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/horn.mp3 b/games/story-stamp/assets/audio/words/horn.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/hot.mp3 b/games/story-stamp/assets/audio/words/hot.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/hug.mp3 b/games/story-stamp/assets/audio/words/hug.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/hut.mp3 b/games/story-stamp/assets/audio/words/hut.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/insect.mp3 b/games/story-stamp/assets/audio/words/insect.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/jam.mp3 b/games/story-stamp/assets/audio/words/jam.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/jar.mp3 b/games/story-stamp/assets/audio/words/jar.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/jeep.mp3 b/games/story-stamp/assets/audio/words/jeep.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/jump.mp3 b/games/story-stamp/assets/audio/words/jump.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/kid.mp3 b/games/story-stamp/assets/audio/words/kid.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/king.mp3 b/games/story-stamp/assets/audio/words/king.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/kitten.mp3 b/games/story-stamp/assets/audio/words/kitten.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/ladder.mp3 b/games/story-stamp/assets/audio/words/ladder.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/lamp.mp3 b/games/story-stamp/assets/audio/words/lamp.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/lantern.mp3 b/games/story-stamp/assets/audio/words/lantern.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/laptop.mp3 b/games/story-stamp/assets/audio/words/laptop.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/light.mp3 b/games/story-stamp/assets/audio/words/light.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/lightning.mp3 b/games/story-stamp/assets/audio/words/lightning.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/lips.mp3 b/games/story-stamp/assets/audio/words/lips.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/lizard.mp3 b/games/story-stamp/assets/audio/words/lizard.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/lobster.mp3 b/games/story-stamp/assets/audio/words/lobster.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/lock.mp3 b/games/story-stamp/assets/audio/words/lock.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/log.mp3 b/games/story-stamp/assets/audio/words/log.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/lollipop.mp3 b/games/story-stamp/assets/audio/words/lollipop.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/lunch.mp3 b/games/story-stamp/assets/audio/words/lunch.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/mad.mp3 b/games/story-stamp/assets/audio/words/mad.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/magic.mp3 b/games/story-stamp/assets/audio/words/magic.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/mango.mp3 b/games/story-stamp/assets/audio/words/mango.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/map.mp3 b/games/story-stamp/assets/audio/words/map.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/mask.mp3 b/games/story-stamp/assets/audio/words/mask.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/melon.mp3 b/games/story-stamp/assets/audio/words/melon.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/milk.mp3 b/games/story-stamp/assets/audio/words/milk.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/mom.mp3 b/games/story-stamp/assets/audio/words/mom.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/monster.mp3 b/games/story-stamp/assets/audio/words/monster.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/moo.mp3 b/games/story-stamp/assets/audio/words/moo.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/moon.mp3 b/games/story-stamp/assets/audio/words/moon.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/moth.mp3 b/games/story-stamp/assets/audio/words/moth.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/mouth.mp3 b/games/story-stamp/assets/audio/words/mouth.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/muffin.mp3 b/games/story-stamp/assets/audio/words/muffin.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/mug.mp3 b/games/story-stamp/assets/audio/words/mug.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/mushroom.mp3 b/games/story-stamp/assets/audio/words/mushroom.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/nest.mp3 b/games/story-stamp/assets/audio/words/nest.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/net.mp3 b/games/story-stamp/assets/audio/words/net.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/night.mp3 b/games/story-stamp/assets/audio/words/night.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/nut.mp3 b/games/story-stamp/assets/audio/words/nut.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/octopus.mp3 b/games/story-stamp/assets/audio/words/octopus.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/owl.mp3 b/games/story-stamp/assets/audio/words/owl.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/ox.mp3 b/games/story-stamp/assets/audio/words/ox.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/oyster.mp3 b/games/story-stamp/assets/audio/words/oyster.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/paint.mp3 b/games/story-stamp/assets/audio/words/paint.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/pan.mp3 b/games/story-stamp/assets/audio/words/pan.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/pants.mp3 b/games/story-stamp/assets/audio/words/pants.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/park.mp3 b/games/story-stamp/assets/audio/words/park.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/parrot.mp3 b/games/story-stamp/assets/audio/words/parrot.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/pen.mp3 b/games/story-stamp/assets/audio/words/pen.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/pie.mp3 b/games/story-stamp/assets/audio/words/pie.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/pig.mp3 b/games/story-stamp/assets/audio/words/pig.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/pin.mp3 b/games/story-stamp/assets/audio/words/pin.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/planet.mp3 b/games/story-stamp/assets/audio/words/planet.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/plant.mp3 b/games/story-stamp/assets/audio/words/plant.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/plug.mp3 b/games/story-stamp/assets/audio/words/plug.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/popcorn.mp3 b/games/story-stamp/assets/audio/words/popcorn.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/pot.mp3 b/games/story-stamp/assets/audio/words/pot.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/pumpkin.mp3 b/games/story-stamp/assets/audio/words/pumpkin.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/quack.mp3 b/games/story-stamp/assets/audio/words/quack.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/queen.mp3 b/games/story-stamp/assets/audio/words/queen.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/quiz.mp3 b/games/story-stamp/assets/audio/words/quiz.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/rabbit.mp3 b/games/story-stamp/assets/audio/words/rabbit.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/raccoon.mp3 b/games/story-stamp/assets/audio/words/raccoon.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/rain.mp3 b/games/story-stamp/assets/audio/words/rain.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/rainbow.mp3 b/games/story-stamp/assets/audio/words/rainbow.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/ram.mp3 b/games/story-stamp/assets/audio/words/ram.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/rat.mp3 b/games/story-stamp/assets/audio/words/rat.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/ring.mp3 b/games/story-stamp/assets/audio/words/ring.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/road.mp3 b/games/story-stamp/assets/audio/words/road.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/roar.mp3 b/games/story-stamp/assets/audio/words/roar.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/robot.mp3 b/games/story-stamp/assets/audio/words/robot.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/rock.mp3 b/games/story-stamp/assets/audio/words/rock.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/rocket.mp3 b/games/story-stamp/assets/audio/words/rocket.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/rooster.mp3 b/games/story-stamp/assets/audio/words/rooster.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/run.mp3 b/games/story-stamp/assets/audio/words/run.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/sad.mp3 b/games/story-stamp/assets/audio/words/sad.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/sandwich.mp3 b/games/story-stamp/assets/audio/words/sandwich.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/scooter.mp3 b/games/story-stamp/assets/audio/words/scooter.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/seed.mp3 b/games/story-stamp/assets/audio/words/seed.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/shark.mp3 b/games/story-stamp/assets/audio/words/shark.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/sheep.mp3 b/games/story-stamp/assets/audio/words/sheep.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/shell.mp3 b/games/story-stamp/assets/audio/words/shell.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/ship.mp3 b/games/story-stamp/assets/audio/words/ship.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/shrimp.mp3 b/games/story-stamp/assets/audio/words/shrimp.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/sing.mp3 b/games/story-stamp/assets/audio/words/sing.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/six.mp3 b/games/story-stamp/assets/audio/words/six.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/sleep.mp3 b/games/story-stamp/assets/audio/words/sleep.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/snail.mp3 b/games/story-stamp/assets/audio/words/snail.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/snap.mp3 b/games/story-stamp/assets/audio/words/snap.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/snow.mp3 b/games/story-stamp/assets/audio/words/snow.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/soap.mp3 b/games/story-stamp/assets/audio/words/soap.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/sock.mp3 b/games/story-stamp/assets/audio/words/sock.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/speedboat.mp3 b/games/story-stamp/assets/audio/words/speedboat.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/spin.mp3 b/games/story-stamp/assets/audio/words/spin.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/spoon.mp3 b/games/story-stamp/assets/audio/words/spoon.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/squid.mp3 b/games/story-stamp/assets/audio/words/squid.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/star.mp3 b/games/story-stamp/assets/audio/words/star.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/storm.mp3 b/games/story-stamp/assets/audio/words/storm.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/sun.mp3 b/games/story-stamp/assets/audio/words/sun.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/sunset.mp3 b/games/story-stamp/assets/audio/words/sunset.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/swim.mp3 b/games/story-stamp/assets/audio/words/swim.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/tap.mp3 b/games/story-stamp/assets/audio/words/tap.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/target.mp3 b/games/story-stamp/assets/audio/words/target.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/teeth.mp3 b/games/story-stamp/assets/audio/words/teeth.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/ten.mp3 b/games/story-stamp/assets/audio/words/ten.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/tent.mp3 b/games/story-stamp/assets/audio/words/tent.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/thumb.mp3 b/games/story-stamp/assets/audio/words/thumb.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/thunder.mp3 b/games/story-stamp/assets/audio/words/thunder.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/ticket.mp3 b/games/story-stamp/assets/audio/words/ticket.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/tin.mp3 b/games/story-stamp/assets/audio/words/tin.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/toast.mp3 b/games/story-stamp/assets/audio/words/toast.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/toilet.mp3 b/games/story-stamp/assets/audio/words/toilet.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/toy.mp3 b/games/story-stamp/assets/audio/words/toy.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/tractor.mp3 b/games/story-stamp/assets/audio/words/tractor.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/train.mp3 b/games/story-stamp/assets/audio/words/train.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/tree.mp3 b/games/story-stamp/assets/audio/words/tree.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/truck.mp3 b/games/story-stamp/assets/audio/words/truck.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/van.mp3 b/games/story-stamp/assets/audio/words/van.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/vest.mp3 b/games/story-stamp/assets/audio/words/vest.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/web.mp3 b/games/story-stamp/assets/audio/words/web.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/wheel.mp3 b/games/story-stamp/assets/audio/words/wheel.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/wig.mp3 b/games/story-stamp/assets/audio/words/wig.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/window.mp3 b/games/story-stamp/assets/audio/words/window.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/wing.mp3 b/games/story-stamp/assets/audio/words/wing.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/witch.mp3 b/games/story-stamp/assets/audio/words/witch.mp3 Binary files differ. diff --git a/games/sound-soup/assets/audio/words/zap.mp3 b/games/story-stamp/assets/audio/words/zap.mp3 Binary files differ. diff --git a/games/story-stamp/assets/audio/words/zip.mp3 b/games/story-stamp/assets/audio/words/zip.mp3 Binary files differ. diff --git a/games/story-stamp/assets/img/alarm_clock.webp b/games/story-stamp/assets/img/alarm_clock.webp Binary files differ. diff --git a/games/story-stamp/assets/img/alien.webp b/games/story-stamp/assets/img/alien.webp Binary files differ. diff --git a/games/story-stamp/assets/img/alien_monster.webp b/games/story-stamp/assets/img/alien_monster.webp Binary files differ. diff --git a/games/story-stamp/assets/img/angry_face.webp b/games/story-stamp/assets/img/angry_face.webp Binary files differ. diff --git a/games/story-stamp/assets/img/ant.webp b/games/story-stamp/assets/img/ant.webp Binary files differ. diff --git a/games/story-stamp/assets/img/artist_palette.webp b/games/story-stamp/assets/img/artist_palette.webp Binary files differ. diff --git a/games/story-stamp/assets/img/automobile.webp b/games/story-stamp/assets/img/automobile.webp Binary files differ. diff --git a/games/story-stamp/assets/img/axe.webp b/games/story-stamp/assets/img/axe.webp Binary files differ. diff --git a/games/story-stamp/assets/img/baby_chick.webp b/games/story-stamp/assets/img/baby_chick.webp Binary files differ. diff --git a/games/story-stamp/assets/img/backhand_index_pointing_up_default.webp b/games/story-stamp/assets/img/backhand_index_pointing_up_default.webp Binary files differ. diff --git a/games/story-stamp/assets/img/balloon.webp b/games/story-stamp/assets/img/balloon.webp Binary files differ. diff --git a/games/story-stamp/assets/img/basket.webp b/games/story-stamp/assets/img/basket.webp Binary files differ. diff --git a/games/story-stamp/assets/img/bat.webp b/games/story-stamp/assets/img/bat.webp Binary files differ. diff --git a/games/story-stamp/assets/img/bathtub.webp b/games/story-stamp/assets/img/bathtub.webp Binary files differ. diff --git a/games/story-stamp/assets/img/bear.webp b/games/story-stamp/assets/img/bear.webp Binary files differ. diff --git a/games/story-stamp/assets/img/bed.webp b/games/story-stamp/assets/img/bed.webp Binary files differ. diff --git a/games/story-stamp/assets/img/beetle.webp b/games/story-stamp/assets/img/beetle.webp Binary files differ. diff --git a/games/story-stamp/assets/img/bell.webp b/games/story-stamp/assets/img/bell.webp Binary files differ. diff --git a/games/story-stamp/assets/img/bento_box.webp b/games/story-stamp/assets/img/bento_box.webp Binary files differ. diff --git a/games/story-stamp/assets/img/billed_cap.webp b/games/story-stamp/assets/img/billed_cap.webp Binary files differ. diff --git a/games/story-stamp/assets/img/bird.webp b/games/story-stamp/assets/img/bird.webp Binary files differ. diff --git a/games/story-stamp/assets/img/bone.webp b/games/story-stamp/assets/img/bone.webp Binary files differ. diff --git a/games/story-stamp/assets/img/books.webp b/games/story-stamp/assets/img/books.webp Binary files differ. diff --git a/games/story-stamp/assets/img/bowling.webp b/games/story-stamp/assets/img/bowling.webp Binary files differ. diff --git a/games/story-stamp/assets/img/boy_default.webp b/games/story-stamp/assets/img/boy_default.webp Binary files differ. diff --git a/games/story-stamp/assets/img/brain.webp b/games/story-stamp/assets/img/brain.webp Binary files differ. diff --git a/games/story-stamp/assets/img/bread.webp b/games/story-stamp/assets/img/bread.webp Binary files differ. diff --git a/games/story-stamp/assets/img/brick.webp b/games/story-stamp/assets/img/brick.webp Binary files differ. diff --git a/games/story-stamp/assets/img/bucket.webp b/games/story-stamp/assets/img/bucket.webp Binary files differ. diff --git a/games/story-stamp/assets/img/bug.webp b/games/story-stamp/assets/img/bug.webp Binary files differ. diff --git a/games/story-stamp/assets/img/bullseye.webp b/games/story-stamp/assets/img/bullseye.webp Binary files differ. diff --git a/games/story-stamp/assets/img/bus.webp b/games/story-stamp/assets/img/bus.webp Binary files differ. diff --git a/games/story-stamp/assets/img/butter.webp b/games/story-stamp/assets/img/butter.webp Binary files differ. diff --git a/games/story-stamp/assets/img/butterfly.webp b/games/story-stamp/assets/img/butterfly.webp Binary files differ. diff --git a/games/story-stamp/assets/img/cactus.webp b/games/story-stamp/assets/img/cactus.webp Binary files differ. diff --git a/games/story-stamp/assets/img/camel.webp b/games/story-stamp/assets/img/camel.webp Binary files differ. diff --git a/games/story-stamp/assets/img/camping.webp b/games/story-stamp/assets/img/camping.webp Binary files differ. diff --git a/games/story-stamp/assets/img/canned_food.webp b/games/story-stamp/assets/img/canned_food.webp Binary files differ. diff --git a/games/story-stamp/assets/img/card_file_box.webp b/games/story-stamp/assets/img/card_file_box.webp Binary files differ. diff --git a/games/story-stamp/assets/img/carrot.webp b/games/story-stamp/assets/img/carrot.webp Binary files differ. diff --git a/games/story-stamp/assets/img/cat.webp b/games/story-stamp/assets/img/cat.webp Binary files differ. diff --git a/games/story-stamp/assets/img/cat_face.webp b/games/story-stamp/assets/img/cat_face.webp Binary files differ. diff --git a/games/story-stamp/assets/img/chains.webp b/games/story-stamp/assets/img/chains.webp Binary files differ. diff --git a/games/story-stamp/assets/img/cherry_blossom.webp b/games/story-stamp/assets/img/cherry_blossom.webp Binary files differ. diff --git a/games/story-stamp/assets/img/chicken.webp b/games/story-stamp/assets/img/chicken.webp Binary files differ. diff --git a/games/story-stamp/assets/img/child_default.webp b/games/story-stamp/assets/img/child_default.webp Binary files differ. diff --git a/games/story-stamp/assets/img/cloud.webp b/games/story-stamp/assets/img/cloud.webp Binary files differ. diff --git a/games/story-stamp/assets/img/cloud_with_lightning.webp b/games/story-stamp/assets/img/cloud_with_lightning.webp Binary files differ. diff --git a/games/story-stamp/assets/img/cloud_with_lightning_and_rain.webp b/games/story-stamp/assets/img/cloud_with_lightning_and_rain.webp Binary files differ. diff --git a/games/story-stamp/assets/img/cloud_with_rain.webp b/games/story-stamp/assets/img/cloud_with_rain.webp Binary files differ. diff --git a/games/story-stamp/assets/img/clown_face.webp b/games/story-stamp/assets/img/clown_face.webp Binary files differ. diff --git a/games/story-stamp/assets/img/coat.webp b/games/story-stamp/assets/img/coat.webp Binary files differ. diff --git a/games/story-stamp/assets/img/coin.webp b/games/story-stamp/assets/img/coin.webp Binary files differ. diff --git a/games/story-stamp/assets/img/collision.webp b/games/story-stamp/assets/img/collision.webp Binary files differ. diff --git a/games/story-stamp/assets/img/comet.webp b/games/story-stamp/assets/img/comet.webp Binary files differ. diff --git a/games/story-stamp/assets/img/cook_default.webp b/games/story-stamp/assets/img/cook_default.webp Binary files differ. diff --git a/games/story-stamp/assets/img/cooking.webp b/games/story-stamp/assets/img/cooking.webp Binary files differ. diff --git a/games/story-stamp/assets/img/cow.webp b/games/story-stamp/assets/img/cow.webp Binary files differ. diff --git a/games/story-stamp/assets/img/cow_face.webp b/games/story-stamp/assets/img/cow_face.webp Binary files differ. diff --git a/games/story-stamp/assets/img/cowboy_hat_face.webp b/games/story-stamp/assets/img/cowboy_hat_face.webp Binary files differ. diff --git a/games/story-stamp/assets/img/crab.webp b/games/story-stamp/assets/img/crab.webp Binary files differ. diff --git a/games/story-stamp/assets/img/crayon.webp b/games/story-stamp/assets/img/crayon.webp Binary files differ. diff --git a/games/story-stamp/assets/img/crescent_moon.webp b/games/story-stamp/assets/img/crescent_moon.webp Binary files differ. diff --git a/games/story-stamp/assets/img/crocodile.webp b/games/story-stamp/assets/img/crocodile.webp Binary files differ. diff --git a/games/story-stamp/assets/img/crown.webp b/games/story-stamp/assets/img/crown.webp Binary files differ. diff --git a/games/story-stamp/assets/img/crying_face.webp b/games/story-stamp/assets/img/crying_face.webp Binary files differ. diff --git a/games/story-stamp/assets/img/crystal_ball.webp b/games/story-stamp/assets/img/crystal_ball.webp Binary files differ. diff --git a/games/story-stamp/assets/img/cup_with_straw.webp b/games/story-stamp/assets/img/cup_with_straw.webp Binary files differ. diff --git a/games/story-stamp/assets/img/cupcake.webp b/games/story-stamp/assets/img/cupcake.webp Binary files differ. diff --git a/games/story-stamp/assets/img/cut_of_meat.webp b/games/story-stamp/assets/img/cut_of_meat.webp Binary files differ. diff --git a/games/story-stamp/assets/img/cyclone.webp b/games/story-stamp/assets/img/cyclone.webp Binary files differ. diff --git a/games/story-stamp/assets/img/deciduous_tree.webp b/games/story-stamp/assets/img/deciduous_tree.webp Binary files differ. diff --git a/games/story-stamp/assets/img/deer.webp b/games/story-stamp/assets/img/deer.webp Binary files differ. diff --git a/games/story-stamp/assets/img/delivery_truck.webp b/games/story-stamp/assets/img/delivery_truck.webp Binary files differ. diff --git a/games/story-stamp/assets/img/dog.webp b/games/story-stamp/assets/img/dog.webp Binary files differ. diff --git a/games/story-stamp/assets/img/dog_face.webp b/games/story-stamp/assets/img/dog_face.webp Binary files differ. diff --git a/games/story-stamp/assets/img/dolphin.webp b/games/story-stamp/assets/img/dolphin.webp Binary files differ. diff --git a/games/story-stamp/assets/img/dragon.webp b/games/story-stamp/assets/img/dragon.webp Binary files differ. diff --git a/games/story-stamp/assets/img/dragon_face.webp b/games/story-stamp/assets/img/dragon_face.webp Binary files differ. diff --git a/games/story-stamp/assets/img/dress.webp b/games/story-stamp/assets/img/dress.webp Binary files differ. diff --git a/games/story-stamp/assets/img/drum.webp b/games/story-stamp/assets/img/drum.webp Binary files differ. diff --git a/games/story-stamp/assets/img/duck.webp b/games/story-stamp/assets/img/duck.webp Binary files differ. diff --git a/games/story-stamp/assets/img/ear_of_corn.webp b/games/story-stamp/assets/img/ear_of_corn.webp Binary files differ. diff --git a/games/story-stamp/assets/img/egg.webp b/games/story-stamp/assets/img/egg.webp Binary files differ. diff --git a/games/story-stamp/assets/img/electric_plug.webp b/games/story-stamp/assets/img/electric_plug.webp Binary files differ. diff --git a/games/story-stamp/assets/img/ewe.webp b/games/story-stamp/assets/img/ewe.webp Binary files differ. diff --git a/games/story-stamp/assets/img/face_savoring_food.webp b/games/story-stamp/assets/img/face_savoring_food.webp Binary files differ. diff --git a/games/story-stamp/assets/img/fire_engine.webp b/games/story-stamp/assets/img/fire_engine.webp Binary files differ. diff --git a/games/story-stamp/assets/img/fish.webp b/games/story-stamp/assets/img/fish.webp Binary files differ. diff --git a/games/story-stamp/assets/img/flamingo.webp b/games/story-stamp/assets/img/flamingo.webp Binary files differ. diff --git a/games/story-stamp/assets/img/flashlight.webp b/games/story-stamp/assets/img/flashlight.webp Binary files differ. diff --git a/games/story-stamp/assets/img/flexed_biceps_default.webp b/games/story-stamp/assets/img/flexed_biceps_default.webp Binary files differ. diff --git a/games/story-stamp/assets/img/fog.webp b/games/story-stamp/assets/img/fog.webp Binary files differ. diff --git a/games/story-stamp/assets/img/foot_default.webp b/games/story-stamp/assets/img/foot_default.webp Binary files differ. diff --git a/games/story-stamp/assets/img/fork_and_knife.webp b/games/story-stamp/assets/img/fork_and_knife.webp Binary files differ. diff --git a/games/story-stamp/assets/img/fork_and_knife_with_plate.webp b/games/story-stamp/assets/img/fork_and_knife_with_plate.webp Binary files differ. diff --git a/games/story-stamp/assets/img/fox.webp b/games/story-stamp/assets/img/fox.webp Binary files differ. diff --git a/games/story-stamp/assets/img/french_fries.webp b/games/story-stamp/assets/img/french_fries.webp Binary files differ. diff --git a/games/story-stamp/assets/img/frog.webp b/games/story-stamp/assets/img/frog.webp Binary files differ. diff --git a/games/story-stamp/assets/img/garlic.webp b/games/story-stamp/assets/img/garlic.webp Binary files differ. diff --git a/games/story-stamp/assets/img/gem_stone.webp b/games/story-stamp/assets/img/gem_stone.webp Binary files differ. diff --git a/games/story-stamp/assets/img/ghost.webp b/games/story-stamp/assets/img/ghost.webp Binary files differ. diff --git a/games/story-stamp/assets/img/girl_default.webp b/games/story-stamp/assets/img/girl_default.webp Binary files differ. diff --git a/games/story-stamp/assets/img/glass_of_milk.webp b/games/story-stamp/assets/img/glass_of_milk.webp Binary files differ. diff --git a/games/story-stamp/assets/img/globe_showing_americas.webp b/games/story-stamp/assets/img/globe_showing_americas.webp Binary files differ. diff --git a/games/story-stamp/assets/img/goal_net.webp b/games/story-stamp/assets/img/goal_net.webp Binary files differ. diff --git a/games/story-stamp/assets/img/goat.webp b/games/story-stamp/assets/img/goat.webp Binary files differ. diff --git a/games/story-stamp/assets/img/goblin.webp b/games/story-stamp/assets/img/goblin.webp Binary files differ. diff --git a/games/story-stamp/assets/img/hammer.webp b/games/story-stamp/assets/img/hammer.webp Binary files differ. diff --git a/games/story-stamp/assets/img/hamster.webp b/games/story-stamp/assets/img/hamster.webp Binary files differ. diff --git a/games/story-stamp/assets/img/handbag.webp b/games/story-stamp/assets/img/handbag.webp Binary files differ. diff --git a/games/story-stamp/assets/img/hatching_chick.webp b/games/story-stamp/assets/img/hatching_chick.webp Binary files differ. diff --git a/games/story-stamp/assets/img/health_worker_default.webp b/games/story-stamp/assets/img/health_worker_default.webp Binary files differ. diff --git a/games/story-stamp/assets/img/helicopter.webp b/games/story-stamp/assets/img/helicopter.webp Binary files differ. diff --git a/games/story-stamp/assets/img/high_voltage.webp b/games/story-stamp/assets/img/high_voltage.webp Binary files differ. diff --git a/games/story-stamp/assets/img/hiking_boot.webp b/games/story-stamp/assets/img/hiking_boot.webp Binary files differ. diff --git a/games/story-stamp/assets/img/hippopotamus.webp b/games/story-stamp/assets/img/hippopotamus.webp Binary files differ. diff --git a/games/story-stamp/assets/img/honeybee.webp b/games/story-stamp/assets/img/honeybee.webp Binary files differ. diff --git a/games/story-stamp/assets/img/hook.webp b/games/story-stamp/assets/img/hook.webp Binary files differ. diff --git a/games/story-stamp/assets/img/hot_beverage.webp b/games/story-stamp/assets/img/hot_beverage.webp Binary files differ. diff --git a/games/story-stamp/assets/img/hot_dog.webp b/games/story-stamp/assets/img/hot_dog.webp Binary files differ. diff --git a/games/story-stamp/assets/img/hot_face.webp b/games/story-stamp/assets/img/hot_face.webp Binary files differ. diff --git a/games/story-stamp/assets/img/hot_pepper.webp b/games/story-stamp/assets/img/hot_pepper.webp Binary files differ. diff --git a/games/story-stamp/assets/img/house.webp b/games/story-stamp/assets/img/house.webp Binary files differ. diff --git a/games/story-stamp/assets/img/hut.webp b/games/story-stamp/assets/img/hut.webp Binary files differ. diff --git a/games/story-stamp/assets/img/jack-o-lantern.webp b/games/story-stamp/assets/img/jack-o-lantern.webp Binary files differ. diff --git a/games/story-stamp/assets/img/jar.webp b/games/story-stamp/assets/img/jar.webp Binary files differ. diff --git a/games/story-stamp/assets/img/jeans.webp b/games/story-stamp/assets/img/jeans.webp Binary files differ. diff --git a/games/story-stamp/assets/img/keycap_10.webp b/games/story-stamp/assets/img/keycap_10.webp Binary files differ. diff --git a/games/story-stamp/assets/img/keycap_6.webp b/games/story-stamp/assets/img/keycap_6.webp Binary files differ. diff --git a/games/story-stamp/assets/img/kick_scooter.webp b/games/story-stamp/assets/img/kick_scooter.webp Binary files differ. diff --git a/games/story-stamp/assets/img/koala.webp b/games/story-stamp/assets/img/koala.webp Binary files differ. diff --git a/games/story-stamp/assets/img/ladder.webp b/games/story-stamp/assets/img/ladder.webp Binary files differ. diff --git a/games/story-stamp/assets/img/laptop.webp b/games/story-stamp/assets/img/laptop.webp Binary files differ. diff --git a/games/story-stamp/assets/img/lemon.webp b/games/story-stamp/assets/img/lemon.webp Binary files differ. diff --git a/games/story-stamp/assets/img/light_bulb.webp b/games/story-stamp/assets/img/light_bulb.webp Binary files differ. diff --git a/games/story-stamp/assets/img/lion.webp b/games/story-stamp/assets/img/lion.webp Binary files differ. diff --git a/games/story-stamp/assets/img/lizard.webp b/games/story-stamp/assets/img/lizard.webp Binary files differ. diff --git a/games/story-stamp/assets/img/lobster.webp b/games/story-stamp/assets/img/lobster.webp Binary files differ. diff --git a/games/story-stamp/assets/img/locked.webp b/games/story-stamp/assets/img/locked.webp Binary files differ. diff --git a/games/story-stamp/assets/img/locomotive.webp b/games/story-stamp/assets/img/locomotive.webp Binary files differ. diff --git a/games/story-stamp/assets/img/lollipop.webp b/games/story-stamp/assets/img/lollipop.webp Binary files differ. diff --git a/games/story-stamp/assets/img/magic_wand.webp b/games/story-stamp/assets/img/magic_wand.webp Binary files differ. diff --git a/games/story-stamp/assets/img/magnet.webp b/games/story-stamp/assets/img/magnet.webp Binary files differ. diff --git a/games/story-stamp/assets/img/mammoth.webp b/games/story-stamp/assets/img/mammoth.webp Binary files differ. diff --git a/games/story-stamp/assets/img/man_default.webp b/games/story-stamp/assets/img/man_default.webp Binary files differ. diff --git a/games/story-stamp/assets/img/mango.webp b/games/story-stamp/assets/img/mango.webp Binary files differ. diff --git a/games/story-stamp/assets/img/melon.webp b/games/story-stamp/assets/img/melon.webp Binary files differ. diff --git a/games/story-stamp/assets/img/microphone.webp b/games/story-stamp/assets/img/microphone.webp Binary files differ. diff --git a/games/story-stamp/assets/img/minibus.webp b/games/story-stamp/assets/img/minibus.webp Binary files differ. diff --git a/games/story-stamp/assets/img/motor_boat.webp b/games/story-stamp/assets/img/motor_boat.webp Binary files differ. diff --git a/games/story-stamp/assets/img/motorway.webp b/games/story-stamp/assets/img/motorway.webp Binary files differ. diff --git a/games/story-stamp/assets/img/mouth.webp b/games/story-stamp/assets/img/mouth.webp Binary files differ. diff --git a/games/story-stamp/assets/img/mushroom.webp b/games/story-stamp/assets/img/mushroom.webp Binary files differ. diff --git a/games/story-stamp/assets/img/national_park.webp b/games/story-stamp/assets/img/national_park.webp Binary files differ. diff --git a/games/story-stamp/assets/img/nauseated_face.webp b/games/story-stamp/assets/img/nauseated_face.webp Binary files differ. diff --git a/games/story-stamp/assets/img/nest_with_eggs.webp b/games/story-stamp/assets/img/nest_with_eggs.webp Binary files differ. diff --git a/games/story-stamp/assets/img/nesting_dolls.webp b/games/story-stamp/assets/img/nesting_dolls.webp Binary files differ. diff --git a/games/story-stamp/assets/img/night_with_stars.webp b/games/story-stamp/assets/img/night_with_stars.webp Binary files differ. diff --git a/games/story-stamp/assets/img/octopus.webp b/games/story-stamp/assets/img/octopus.webp Binary files differ. diff --git a/games/story-stamp/assets/img/open_book.webp b/games/story-stamp/assets/img/open_book.webp Binary files differ. diff --git a/games/story-stamp/assets/img/otter.webp b/games/story-stamp/assets/img/otter.webp Binary files differ. diff --git a/games/story-stamp/assets/img/owl.webp b/games/story-stamp/assets/img/owl.webp Binary files differ. diff --git a/games/story-stamp/assets/img/ox.webp b/games/story-stamp/assets/img/ox.webp Binary files differ. diff --git a/games/story-stamp/assets/img/oyster.webp b/games/story-stamp/assets/img/oyster.webp Binary files differ. diff --git a/games/story-stamp/assets/img/package.webp b/games/story-stamp/assets/img/package.webp Binary files differ. diff --git a/games/story-stamp/assets/img/paintbrush.webp b/games/story-stamp/assets/img/paintbrush.webp Binary files differ. diff --git a/games/story-stamp/assets/img/palm_tree.webp b/games/story-stamp/assets/img/palm_tree.webp Binary files differ. diff --git a/games/story-stamp/assets/img/panda.webp b/games/story-stamp/assets/img/panda.webp Binary files differ. diff --git a/games/story-stamp/assets/img/parrot.webp b/games/story-stamp/assets/img/parrot.webp Binary files differ. diff --git a/games/story-stamp/assets/img/paw_prints.webp b/games/story-stamp/assets/img/paw_prints.webp Binary files differ. diff --git a/games/story-stamp/assets/img/peanuts.webp b/games/story-stamp/assets/img/peanuts.webp Binary files differ. diff --git a/games/story-stamp/assets/img/pen.webp b/games/story-stamp/assets/img/pen.webp Binary files differ. diff --git a/games/story-stamp/assets/img/penguin.webp b/games/story-stamp/assets/img/penguin.webp Binary files differ. diff --git a/games/story-stamp/assets/img/people_hugging.webp b/games/story-stamp/assets/img/people_hugging.webp Binary files differ. diff --git a/games/story-stamp/assets/img/performing_arts.webp b/games/story-stamp/assets/img/performing_arts.webp Binary files differ. diff --git a/games/story-stamp/assets/img/person_cartwheeling_default.webp b/games/story-stamp/assets/img/person_cartwheeling_default.webp Binary files differ. diff --git a/games/story-stamp/assets/img/person_elf_default.webp b/games/story-stamp/assets/img/person_elf_default.webp Binary files differ. diff --git a/games/story-stamp/assets/img/person_genie.webp b/games/story-stamp/assets/img/person_genie.webp Binary files differ. diff --git a/games/story-stamp/assets/img/person_merpeople_default.webp b/games/story-stamp/assets/img/person_merpeople_default.webp Binary files differ. diff --git a/games/story-stamp/assets/img/person_running_default.webp b/games/story-stamp/assets/img/person_running_default.webp Binary files differ. diff --git a/games/story-stamp/assets/img/person_swimming_default.webp b/games/story-stamp/assets/img/person_swimming_default.webp Binary files differ. diff --git a/games/story-stamp/assets/img/pick.webp b/games/story-stamp/assets/img/pick.webp Binary files differ. diff --git a/games/story-stamp/assets/img/pickup_truck.webp b/games/story-stamp/assets/img/pickup_truck.webp Binary files differ. diff --git a/games/story-stamp/assets/img/pie.webp b/games/story-stamp/assets/img/pie.webp Binary files differ. diff --git a/games/story-stamp/assets/img/pig.webp b/games/story-stamp/assets/img/pig.webp Binary files differ. diff --git a/games/story-stamp/assets/img/pig_face.webp b/games/story-stamp/assets/img/pig_face.webp Binary files differ. diff --git a/games/story-stamp/assets/img/pile_of_poo.webp b/games/story-stamp/assets/img/pile_of_poo.webp Binary files differ. diff --git a/games/story-stamp/assets/img/pizza.webp b/games/story-stamp/assets/img/pizza.webp Binary files differ. diff --git a/games/story-stamp/assets/img/popcorn.webp b/games/story-stamp/assets/img/popcorn.webp Binary files differ. diff --git a/games/story-stamp/assets/img/postal_horn.webp b/games/story-stamp/assets/img/postal_horn.webp Binary files differ. diff --git a/games/story-stamp/assets/img/pot_of_food.webp b/games/story-stamp/assets/img/pot_of_food.webp Binary files differ. diff --git a/games/story-stamp/assets/img/potable_water.webp b/games/story-stamp/assets/img/potable_water.webp Binary files differ. diff --git a/games/story-stamp/assets/img/potted_plant.webp b/games/story-stamp/assets/img/potted_plant.webp Binary files differ. diff --git a/games/story-stamp/assets/img/poultry_leg.webp b/games/story-stamp/assets/img/poultry_leg.webp Binary files differ. diff --git a/games/story-stamp/assets/img/prince_default.webp b/games/story-stamp/assets/img/prince_default.webp Binary files differ. diff --git a/games/story-stamp/assets/img/princess_default.webp b/games/story-stamp/assets/img/princess_default.webp Binary files differ. diff --git a/games/story-stamp/assets/img/printer.webp b/games/story-stamp/assets/img/printer.webp Binary files differ. diff --git a/games/story-stamp/assets/img/rabbit.webp b/games/story-stamp/assets/img/rabbit.webp Binary files differ. diff --git a/games/story-stamp/assets/img/rabbit_face.webp b/games/story-stamp/assets/img/rabbit_face.webp Binary files differ. diff --git a/games/story-stamp/assets/img/raccoon.webp b/games/story-stamp/assets/img/raccoon.webp Binary files differ. diff --git a/games/story-stamp/assets/img/rainbow.webp b/games/story-stamp/assets/img/rainbow.webp Binary files differ. diff --git a/games/story-stamp/assets/img/raised_hand_default.webp b/games/story-stamp/assets/img/raised_hand_default.webp Binary files differ. diff --git a/games/story-stamp/assets/img/ram.webp b/games/story-stamp/assets/img/ram.webp Binary files differ. diff --git a/games/story-stamp/assets/img/rat.webp b/games/story-stamp/assets/img/rat.webp Binary files differ. diff --git a/games/story-stamp/assets/img/red_paper_lantern.webp b/games/story-stamp/assets/img/red_paper_lantern.webp Binary files differ. diff --git a/games/story-stamp/assets/img/red_question_mark.webp b/games/story-stamp/assets/img/red_question_mark.webp Binary files differ. diff --git a/games/story-stamp/assets/img/rescue_workers_helmet.webp b/games/story-stamp/assets/img/rescue_workers_helmet.webp Binary files differ. diff --git a/games/story-stamp/assets/img/ring.webp b/games/story-stamp/assets/img/ring.webp Binary files differ. diff --git a/games/story-stamp/assets/img/ringed_planet.webp b/games/story-stamp/assets/img/ringed_planet.webp Binary files differ. diff --git a/games/story-stamp/assets/img/robot.webp b/games/story-stamp/assets/img/robot.webp Binary files differ. diff --git a/games/story-stamp/assets/img/rock.webp b/games/story-stamp/assets/img/rock.webp Binary files differ. diff --git a/games/story-stamp/assets/img/rocket.webp b/games/story-stamp/assets/img/rocket.webp Binary files differ. diff --git a/games/story-stamp/assets/img/rooster.webp b/games/story-stamp/assets/img/rooster.webp Binary files differ. diff --git a/games/story-stamp/assets/img/safety_vest.webp b/games/story-stamp/assets/img/safety_vest.webp Binary files differ. diff --git a/games/story-stamp/assets/img/sailboat.webp b/games/story-stamp/assets/img/sailboat.webp Binary files differ. diff --git a/games/story-stamp/assets/img/sandwich.webp b/games/story-stamp/assets/img/sandwich.webp Binary files differ. diff --git a/games/story-stamp/assets/img/sauropod.webp b/games/story-stamp/assets/img/sauropod.webp Binary files differ. diff --git a/games/story-stamp/assets/img/scarf.webp b/games/story-stamp/assets/img/scarf.webp Binary files differ. diff --git a/games/story-stamp/assets/img/scissors.webp b/games/story-stamp/assets/img/scissors.webp Binary files differ. diff --git a/games/story-stamp/assets/img/seedling.webp b/games/story-stamp/assets/img/seedling.webp Binary files differ. diff --git a/games/story-stamp/assets/img/shark.webp b/games/story-stamp/assets/img/shark.webp Binary files differ. diff --git a/games/story-stamp/assets/img/ship.webp b/games/story-stamp/assets/img/ship.webp Binary files differ. diff --git a/games/story-stamp/assets/img/shopping_cart.webp b/games/story-stamp/assets/img/shopping_cart.webp Binary files differ. diff --git a/games/story-stamp/assets/img/shrimp.webp b/games/story-stamp/assets/img/shrimp.webp Binary files differ. diff --git a/games/story-stamp/assets/img/skunk.webp b/games/story-stamp/assets/img/skunk.webp Binary files differ. diff --git a/games/story-stamp/assets/img/sled.webp b/games/story-stamp/assets/img/sled.webp Binary files differ. diff --git a/games/story-stamp/assets/img/sleeping_face.webp b/games/story-stamp/assets/img/sleeping_face.webp Binary files differ. diff --git a/games/story-stamp/assets/img/sloth.webp b/games/story-stamp/assets/img/sloth.webp Binary files differ. diff --git a/games/story-stamp/assets/img/small_airplane.webp b/games/story-stamp/assets/img/small_airplane.webp Binary files differ. diff --git a/games/story-stamp/assets/img/snail.webp b/games/story-stamp/assets/img/snail.webp Binary files differ. diff --git a/games/story-stamp/assets/img/snake.webp b/games/story-stamp/assets/img/snake.webp Binary files differ. diff --git a/games/story-stamp/assets/img/snowflake.webp b/games/story-stamp/assets/img/snowflake.webp Binary files differ. diff --git a/games/story-stamp/assets/img/soap.webp b/games/story-stamp/assets/img/soap.webp Binary files differ. diff --git a/games/story-stamp/assets/img/soccer_ball.webp b/games/story-stamp/assets/img/soccer_ball.webp Binary files differ. diff --git a/games/story-stamp/assets/img/socks.webp b/games/story-stamp/assets/img/socks.webp Binary files differ. diff --git a/games/story-stamp/assets/img/speedboat.webp b/games/story-stamp/assets/img/speedboat.webp Binary files differ. diff --git a/games/story-stamp/assets/img/spider_web.webp b/games/story-stamp/assets/img/spider_web.webp Binary files differ. diff --git a/games/story-stamp/assets/img/spiral_shell.webp b/games/story-stamp/assets/img/spiral_shell.webp Binary files differ. diff --git a/games/story-stamp/assets/img/spoon.webp b/games/story-stamp/assets/img/spoon.webp Binary files differ. diff --git a/games/story-stamp/assets/img/sport_utility_vehicle.webp b/games/story-stamp/assets/img/sport_utility_vehicle.webp Binary files differ. diff --git a/games/story-stamp/assets/img/squid.webp b/games/story-stamp/assets/img/squid.webp Binary files differ. diff --git a/games/story-stamp/assets/img/star.webp b/games/story-stamp/assets/img/star.webp Binary files differ. diff --git a/games/story-stamp/assets/img/sun.webp b/games/story-stamp/assets/img/sun.webp Binary files differ. diff --git a/games/story-stamp/assets/img/sunset.webp b/games/story-stamp/assets/img/sunset.webp Binary files differ. diff --git a/games/story-stamp/assets/img/t-rex.webp b/games/story-stamp/assets/img/t-rex.webp Binary files differ. diff --git a/games/story-stamp/assets/img/taxi.webp b/games/story-stamp/assets/img/taxi.webp Binary files differ. diff --git a/games/story-stamp/assets/img/teddy_bear.webp b/games/story-stamp/assets/img/teddy_bear.webp Binary files differ. diff --git a/games/story-stamp/assets/img/telescope.webp b/games/story-stamp/assets/img/telescope.webp Binary files differ. diff --git a/games/story-stamp/assets/img/tennis.webp b/games/story-stamp/assets/img/tennis.webp Binary files differ. diff --git a/games/story-stamp/assets/img/tent.webp b/games/story-stamp/assets/img/tent.webp Binary files differ. diff --git a/games/story-stamp/assets/img/thumbs_up_default.webp b/games/story-stamp/assets/img/thumbs_up_default.webp Binary files differ. diff --git a/games/story-stamp/assets/img/ticket.webp b/games/story-stamp/assets/img/ticket.webp Binary files differ. diff --git a/games/story-stamp/assets/img/tiger_face.webp b/games/story-stamp/assets/img/tiger_face.webp Binary files differ. diff --git a/games/story-stamp/assets/img/toilet.webp b/games/story-stamp/assets/img/toilet.webp Binary files differ. diff --git a/games/story-stamp/assets/img/tooth.webp b/games/story-stamp/assets/img/tooth.webp Binary files differ. diff --git a/games/story-stamp/assets/img/toothbrush.webp b/games/story-stamp/assets/img/toothbrush.webp Binary files differ. diff --git a/games/story-stamp/assets/img/top_hat.webp b/games/story-stamp/assets/img/top_hat.webp Binary files differ. diff --git a/games/story-stamp/assets/img/tractor.webp b/games/story-stamp/assets/img/tractor.webp Binary files differ. diff --git a/games/story-stamp/assets/img/tram.webp b/games/story-stamp/assets/img/tram.webp Binary files differ. diff --git a/games/story-stamp/assets/img/triangular_flag.webp b/games/story-stamp/assets/img/triangular_flag.webp Binary files differ. diff --git a/games/story-stamp/assets/img/tropical_fish.webp b/games/story-stamp/assets/img/tropical_fish.webp Binary files differ. diff --git a/games/story-stamp/assets/img/turtle.webp b/games/story-stamp/assets/img/turtle.webp Binary files differ. diff --git a/games/story-stamp/assets/img/unicorn.webp b/games/story-stamp/assets/img/unicorn.webp Binary files differ. diff --git a/games/story-stamp/assets/img/wastebasket.webp b/games/story-stamp/assets/img/wastebasket.webp Binary files differ. diff --git a/games/story-stamp/assets/img/water_wave.webp b/games/story-stamp/assets/img/water_wave.webp Binary files differ. diff --git a/games/story-stamp/assets/img/wheel.webp b/games/story-stamp/assets/img/wheel.webp Binary files differ. diff --git a/games/story-stamp/assets/img/window.webp b/games/story-stamp/assets/img/window.webp Binary files differ. diff --git a/games/story-stamp/assets/img/wing.webp b/games/story-stamp/assets/img/wing.webp Binary files differ. diff --git a/games/story-stamp/assets/img/woman_default.webp b/games/story-stamp/assets/img/woman_default.webp Binary files differ. diff --git a/games/story-stamp/assets/img/woman_mage_default.webp b/games/story-stamp/assets/img/woman_mage_default.webp Binary files differ. diff --git a/games/story-stamp/assets/img/womans_hat.webp b/games/story-stamp/assets/img/womans_hat.webp Binary files differ. diff --git a/games/story-stamp/assets/img/wood.webp b/games/story-stamp/assets/img/wood.webp Binary files differ. diff --git a/games/story-stamp/assets/img/world_map.webp b/games/story-stamp/assets/img/world_map.webp Binary files differ. diff --git a/games/story-stamp/assets/img/wrapped_gift.webp b/games/story-stamp/assets/img/wrapped_gift.webp Binary files differ. diff --git a/games/story-stamp/assets/img/yo-yo.webp b/games/story-stamp/assets/img/yo-yo.webp Binary files differ. diff --git a/games/story-stamp/assets/img/zany_face.webp b/games/story-stamp/assets/img/zany_face.webp Binary files differ. diff --git a/games/story-stamp/assets/img/zipper-mouth_face.webp b/games/story-stamp/assets/img/zipper-mouth_face.webp Binary files differ. diff --git a/games/story-stamp/assets/manifest.js b/games/story-stamp/assets/manifest.js @@ -0,0 +1,1331 @@ +// Generated by tools/story-stamp-assets.py â do not edit by hand. +window.SS_ASSETS = { + "images": { + "Alarm clock": "img/alarm_clock.webp", + "Alien": "img/alien.webp", + "Alien monster": "img/alien_monster.webp", + "Angry face": "img/angry_face.webp", + "Ant": "img/ant.webp", + "Artist palette": "img/artist_palette.webp", + "Automobile": "img/automobile.webp", + "Axe": "img/axe.webp", + "Baby chick": "img/baby_chick.webp", + "Backhand index pointing up/Default": "img/backhand_index_pointing_up_default.webp", + "Balloon": "img/balloon.webp", + "Basket": "img/basket.webp", + "Bat": "img/bat.webp", + "Bathtub": "img/bathtub.webp", + "Bear": "img/bear.webp", + "Bed": "img/bed.webp", + "Beetle": "img/beetle.webp", + "Bell": "img/bell.webp", + "Bento box": "img/bento_box.webp", + "Billed cap": "img/billed_cap.webp", + "Bird": "img/bird.webp", + "Bone": "img/bone.webp", + "Books": "img/books.webp", + "Bowling": "img/bowling.webp", + "Boy/Default": "img/boy_default.webp", + "Brain": "img/brain.webp", + "Bread": "img/bread.webp", + "Brick": "img/brick.webp", + "Bucket": "img/bucket.webp", + "Bug": "img/bug.webp", + "Bullseye": "img/bullseye.webp", + "Bus": "img/bus.webp", + "Butter": "img/butter.webp", + "Butterfly": "img/butterfly.webp", + "Cactus": "img/cactus.webp", + "Camel": "img/camel.webp", + "Camping": "img/camping.webp", + "Canned food": "img/canned_food.webp", + "Card file box": "img/card_file_box.webp", + "Carrot": "img/carrot.webp", + "Cat": "img/cat.webp", + "Cat face": "img/cat_face.webp", + "Chains": "img/chains.webp", + "Cherry blossom": "img/cherry_blossom.webp", + "Chicken": "img/chicken.webp", + "Child/Default": "img/child_default.webp", + "Cloud": "img/cloud.webp", + "Cloud with lightning": "img/cloud_with_lightning.webp", + "Cloud with lightning and rain": "img/cloud_with_lightning_and_rain.webp", + "Cloud with rain": "img/cloud_with_rain.webp", + "Clown face": "img/clown_face.webp", + "Coat": "img/coat.webp", + "Coin": "img/coin.webp", + "Collision": "img/collision.webp", + "Comet": "img/comet.webp", + "Cook/Default": "img/cook_default.webp", + "Cooking": "img/cooking.webp", + "Cow": "img/cow.webp", + "Cow face": "img/cow_face.webp", + "Cowboy hat face": "img/cowboy_hat_face.webp", + "Crab": "img/crab.webp", + "Crayon": "img/crayon.webp", + "Crescent moon": "img/crescent_moon.webp", + "Crocodile": "img/crocodile.webp", + "Crown": "img/crown.webp", + "Crying face": "img/crying_face.webp", + "Crystal ball": "img/crystal_ball.webp", + "Cup with straw": "img/cup_with_straw.webp", + "Cupcake": "img/cupcake.webp", + "Cut of meat": "img/cut_of_meat.webp", + "Cyclone": "img/cyclone.webp", + "Deciduous tree": "img/deciduous_tree.webp", + "Deer": "img/deer.webp", + "Delivery truck": "img/delivery_truck.webp", + "Dog": "img/dog.webp", + "Dog face": "img/dog_face.webp", + "Dolphin": "img/dolphin.webp", + "Dragon": "img/dragon.webp", + "Dragon face": "img/dragon_face.webp", + "Dress": "img/dress.webp", + "Drum": "img/drum.webp", + "Duck": "img/duck.webp", + "Ear of corn": "img/ear_of_corn.webp", + "Egg": "img/egg.webp", + "Electric plug": "img/electric_plug.webp", + "Ewe": "img/ewe.webp", + "Face savoring food": "img/face_savoring_food.webp", + "Fire engine": "img/fire_engine.webp", + "Fish": "img/fish.webp", + "Flamingo": "img/flamingo.webp", + "Flashlight": "img/flashlight.webp", + "Flexed biceps/Default": "img/flexed_biceps_default.webp", + "Fog": "img/fog.webp", + "Foot/Default": "img/foot_default.webp", + "Fork and knife": "img/fork_and_knife.webp", + "Fork and knife with plate": "img/fork_and_knife_with_plate.webp", + "Fox": "img/fox.webp", + "French fries": "img/french_fries.webp", + "Frog": "img/frog.webp", + "Garlic": "img/garlic.webp", + "Gem stone": "img/gem_stone.webp", + "Ghost": "img/ghost.webp", + "Girl/Default": "img/girl_default.webp", + "Glass of milk": "img/glass_of_milk.webp", + "Globe showing americas": "img/globe_showing_americas.webp", + "Goal net": "img/goal_net.webp", + "Goat": "img/goat.webp", + "Goblin": "img/goblin.webp", + "Hammer": "img/hammer.webp", + "Hamster": "img/hamster.webp", + "Handbag": "img/handbag.webp", + "Hatching chick": "img/hatching_chick.webp", + "Health worker/Default": "img/health_worker_default.webp", + "Helicopter": "img/helicopter.webp", + "High voltage": "img/high_voltage.webp", + "Hiking boot": "img/hiking_boot.webp", + "Hippopotamus": "img/hippopotamus.webp", + "Honeybee": "img/honeybee.webp", + "Hook": "img/hook.webp", + "Hot beverage": "img/hot_beverage.webp", + "Hot dog": "img/hot_dog.webp", + "Hot face": "img/hot_face.webp", + "Hot pepper": "img/hot_pepper.webp", + "House": "img/house.webp", + "Hut": "img/hut.webp", + "Jack-o-lantern": "img/jack-o-lantern.webp", + "Jar": "img/jar.webp", + "Jeans": "img/jeans.webp", + "Keycap 10": "img/keycap_10.webp", + "Keycap 6": "img/keycap_6.webp", + "Kick scooter": "img/kick_scooter.webp", + "Koala": "img/koala.webp", + "Ladder": "img/ladder.webp", + "Laptop": "img/laptop.webp", + "Lemon": "img/lemon.webp", + "Light bulb": "img/light_bulb.webp", + "Lion": "img/lion.webp", + "Lizard": "img/lizard.webp", + "Lobster": "img/lobster.webp", + "Locked": "img/locked.webp", + "Locomotive": "img/locomotive.webp", + "Lollipop": "img/lollipop.webp", + "Magic wand": "img/magic_wand.webp", + "Magnet": "img/magnet.webp", + "Mammoth": "img/mammoth.webp", + "Man/Default": "img/man_default.webp", + "Mango": "img/mango.webp", + "Melon": "img/melon.webp", + "Microphone": "img/microphone.webp", + "Minibus": "img/minibus.webp", + "Motor boat": "img/motor_boat.webp", + "Motorway": "img/motorway.webp", + "Mouth": "img/mouth.webp", + "Mushroom": "img/mushroom.webp", + "National park": "img/national_park.webp", + "Nauseated face": "img/nauseated_face.webp", + "Nest with eggs": "img/nest_with_eggs.webp", + "Nesting dolls": "img/nesting_dolls.webp", + "Night with stars": "img/night_with_stars.webp", + "Octopus": "img/octopus.webp", + "Open book": "img/open_book.webp", + "Otter": "img/otter.webp", + "Owl": "img/owl.webp", + "Ox": "img/ox.webp", + "Oyster": "img/oyster.webp", + "Package": "img/package.webp", + "Paintbrush": "img/paintbrush.webp", + "Palm tree": "img/palm_tree.webp", + "Panda": "img/panda.webp", + "Parrot": "img/parrot.webp", + "Paw prints": "img/paw_prints.webp", + "Peanuts": "img/peanuts.webp", + "Pen": "img/pen.webp", + "Penguin": "img/penguin.webp", + "People hugging": "img/people_hugging.webp", + "Performing arts": "img/performing_arts.webp", + "Person cartwheeling/Default": "img/person_cartwheeling_default.webp", + "Person elf/Default": "img/person_elf_default.webp", + "Person genie": "img/person_genie.webp", + "Person merpeople/Default": "img/person_merpeople_default.webp", + "Person running/Default": "img/person_running_default.webp", + "Person swimming/Default": "img/person_swimming_default.webp", + "Pick": "img/pick.webp", + "Pickup truck": "img/pickup_truck.webp", + "Pie": "img/pie.webp", + "Pig": "img/pig.webp", + "Pig face": "img/pig_face.webp", + "Pile of poo": "img/pile_of_poo.webp", + "Pizza": "img/pizza.webp", + "Popcorn": "img/popcorn.webp", + "Postal horn": "img/postal_horn.webp", + "Pot of food": "img/pot_of_food.webp", + "Potable water": "img/potable_water.webp", + "Potted plant": "img/potted_plant.webp", + "Poultry leg": "img/poultry_leg.webp", + "Prince/Default": "img/prince_default.webp", + "Princess/Default": "img/princess_default.webp", + "Printer": "img/printer.webp", + "Rabbit": "img/rabbit.webp", + "Rabbit face": "img/rabbit_face.webp", + "Raccoon": "img/raccoon.webp", + "Rainbow": "img/rainbow.webp", + "Raised hand/Default": "img/raised_hand_default.webp", + "Ram": "img/ram.webp", + "Rat": "img/rat.webp", + "Red paper lantern": "img/red_paper_lantern.webp", + "Red question mark": "img/red_question_mark.webp", + "Rescue workers helmet": "img/rescue_workers_helmet.webp", + "Ring": "img/ring.webp", + "Ringed planet": "img/ringed_planet.webp", + "Robot": "img/robot.webp", + "Rock": "img/rock.webp", + "Rocket": "img/rocket.webp", + "Rooster": "img/rooster.webp", + "Safety vest": "img/safety_vest.webp", + "Sailboat": "img/sailboat.webp", + "Sandwich": "img/sandwich.webp", + "Sauropod": "img/sauropod.webp", + "Scarf": "img/scarf.webp", + "Scissors": "img/scissors.webp", + "Seedling": "img/seedling.webp", + "Shark": "img/shark.webp", + "Ship": "img/ship.webp", + "Shopping cart": "img/shopping_cart.webp", + "Shrimp": "img/shrimp.webp", + "Skunk": "img/skunk.webp", + "Sled": "img/sled.webp", + "Sleeping face": "img/sleeping_face.webp", + "Sloth": "img/sloth.webp", + "Small airplane": "img/small_airplane.webp", + "Snail": "img/snail.webp", + "Snake": "img/snake.webp", + "Snowflake": "img/snowflake.webp", + "Soap": "img/soap.webp", + "Soccer ball": "img/soccer_ball.webp", + "Socks": "img/socks.webp", + "Speedboat": "img/speedboat.webp", + "Spider web": "img/spider_web.webp", + "Spiral shell": "img/spiral_shell.webp", + "Spoon": "img/spoon.webp", + "Sport utility vehicle": "img/sport_utility_vehicle.webp", + "Squid": "img/squid.webp", + "Star": "img/star.webp", + "Sun": "img/sun.webp", + "Sunset": "img/sunset.webp", + "T-rex": "img/t-rex.webp", + "Taxi": "img/taxi.webp", + "Teddy bear": "img/teddy_bear.webp", + "Telescope": "img/telescope.webp", + "Tennis": "img/tennis.webp", + "Tent": "img/tent.webp", + "Thumbs up/Default": "img/thumbs_up_default.webp", + "Ticket": "img/ticket.webp", + "Tiger face": "img/tiger_face.webp", + "Toilet": "img/toilet.webp", + "Tooth": "img/tooth.webp", + "Toothbrush": "img/toothbrush.webp", + "Top hat": "img/top_hat.webp", + "Tractor": "img/tractor.webp", + "Tram": "img/tram.webp", + "Triangular flag": "img/triangular_flag.webp", + "Tropical fish": "img/tropical_fish.webp", + "Turtle": "img/turtle.webp", + "Unicorn": "img/unicorn.webp", + "Wastebasket": "img/wastebasket.webp", + "Water wave": "img/water_wave.webp", + "Wheel": "img/wheel.webp", + "Window": "img/window.webp", + "Wing": "img/wing.webp", + "Woman mage/Default": "img/woman_mage_default.webp", + "Woman/Default": "img/woman_default.webp", + "Womans hat": "img/womans_hat.webp", + "Wood": "img/wood.webp", + "World map": "img/world_map.webp", + "Wrapped gift": "img/wrapped_gift.webp", + "Yo-yo": "img/yo-yo.webp", + "Zany face": "img/zany_face.webp", + "Zipper-mouth face": "img/zipper-mouth_face.webp" + }, + "phonemes": { + "a": { + "dur": 0.873, + "file": "phonemes/a.mp3" + }, + "ai": { + "dur": 0.3, + "file": "phonemes/ai.mp3" + }, + "ar": { + "dur": 0.38, + "file": "phonemes/ar.mp3" + }, + "b": { + "dur": 0.075, + "file": "phonemes/b.mp3" + }, + "ch": { + "dur": 0.152, + "file": "phonemes/ch.mp3" + }, + "d": { + "dur": 0.075, + "file": "phonemes/d.mp3" + }, + "e": { + "dur": 0.873, + "file": "phonemes/e.mp3" + }, + "ee": { + "dur": 0.873, + "file": "phonemes/ee.mp3" + }, + "er": { + "dur": 0.87, + "file": "phonemes/er.mp3" + }, + "f": { + "dur": 0.976, + "file": "phonemes/f.mp3" + }, + "g": { + "dur": 0.075, + "file": "phonemes/g.mp3" + }, + "h": { + "dur": 0.26, + "file": "phonemes/h.mp3" + }, + "i": { + "dur": 0.873, + "file": "phonemes/i.mp3" + }, + "igh": { + "dur": 0.72, + "file": "phonemes/igh.mp3" + }, + "j": { + "dur": 0.1, + "file": "phonemes/j.mp3" + }, + "k": { + "dur": 0.122, + "file": "phonemes/k.mp3" + }, + "l": { + "dur": 0.976, + "file": "phonemes/l.mp3" + }, + "m": { + "dur": 0.97, + "file": "phonemes/m.mp3" + }, + "n": { + "dur": 0.98, + "file": "phonemes/n.mp3" + }, + "ng": { + "dur": 0.98, + "file": "phonemes/ng.mp3" + }, + "o": { + "dur": 0.873, + "file": "phonemes/o.mp3" + }, + "oa": { + "dur": 0.45, + "file": "phonemes/oa.mp3" + }, + "oi": { + "dur": 0.32, + "file": "phonemes/oi.mp3" + }, + "oo": { + "dur": 0.873, + "file": "phonemes/oo.mp3" + }, + "or": { + "dur": 0.4, + "file": "phonemes/or.mp3" + }, + "ow": { + "dur": 0.4, + "file": "phonemes/ow.mp3" + }, + "p": { + "dur": 0.092, + "file": "phonemes/p.mp3" + }, + "r": { + "dur": 0.976, + "file": "phonemes/r.mp3" + }, + "s": { + "dur": 0.976, + "file": "phonemes/s.mp3" + }, + "sh": { + "dur": 0.976, + "file": "phonemes/sh.mp3" + }, + "t": { + "dur": 0.097, + "file": "phonemes/t.mp3" + }, + "th": { + "dur": 0.976, + "file": "phonemes/th.mp3" + }, + "u": { + "dur": 0.873, + "file": "phonemes/u.mp3" + }, + "uu": { + "dur": 0.873, + "file": "phonemes/uu.mp3" + }, + "v": { + "dur": 0.976, + "file": "phonemes/v.mp3" + }, + "w": { + "dur": 0.21, + "file": "phonemes/w.mp3" + }, + "y": { + "dur": 0.17, + "file": "phonemes/y.mp3" + }, + "z": { + "dur": 0.97, + "file": "phonemes/z.mp3" + } + }, + "words": { + "ant": { + "dur": 0.62, + "file": "words/ant.mp3" + }, + "arm": { + "dur": 0.55, + "file": "words/arm.mp3" + }, + "art": { + "dur": 0.55, + "file": "words/art.mp3" + }, + "bag": { + "dur": 0.47, + "file": "words/bag.mp3" + }, + "balloon": { + "dur": 0.53, + "file": "words/balloon.mp3" + }, + "bang": { + "dur": 0.79, + "file": "words/bang.mp3" + }, + "basket": { + "dur": 0.69, + "file": "words/basket.mp3" + }, + "bat": { + "dur": 0.4, + "file": "words/bat.mp3" + }, + "bath": { + "dur": 0.5, + "file": "words/bath.mp3" + }, + "bed": { + "dur": 0.4, + "file": "words/bed.mp3" + }, + "bee": { + "dur": 0.51, + "file": "words/bee.mp3" + }, + "bell": { + "dur": 0.36, + "file": "words/bell.mp3" + }, + "big": { + "dur": 0.35, + "file": "words/big.mp3" + }, + "bin": { + "dur": 1.01, + "file": "words/bin.mp3" + }, + "blossom": { + "dur": 0.6, + "file": "words/blossom.mp3" + }, + "boat": { + "dur": 0.38, + "file": "words/boat.mp3" + }, + "boo": { + "dur": 0.37, + "file": "words/boo.mp3" + }, + "book": { + "dur": 0.39, + "file": "words/book.mp3" + }, + "boot": { + "dur": 0.4, + "file": "words/boot.mp3" + }, + "box": { + "dur": 0.5, + "file": "words/box.mp3" + }, + "boy": { + "dur": 0.39, + "file": "words/boy.mp3" + }, + "brain": { + "dur": 0.47, + "file": "words/brain.mp3" + }, + "brick": { + "dur": 0.44, + "file": "words/brick.mp3" + }, + "brush": { + "dur": 0.46, + "file": "words/brush.mp3" + }, + "bucket": { + "dur": 0.42, + "file": "words/bucket.mp3" + }, + "bug": { + "dur": 0.35, + "file": "words/bug.mp3" + }, + "bus": { + "dur": 0.77, + "file": "words/bus.mp3" + }, + "butter": { + "dur": 0.32, + "file": "words/butter.mp3" + }, + "buzz": { + "dur": 0.63, + "file": "words/buzz.mp3" + }, + "cab": { + "dur": 0.55, + "file": "words/cab.mp3" + }, + "cactus": { + "dur": 0.66, + "file": "words/cactus.mp3" + }, + "camel": { + "dur": 0.45, + "file": "words/camel.mp3" + }, + "camp": { + "dur": 0.49, + "file": "words/camp.mp3" + }, + "can": { + "dur": 0.5, + "file": "words/can.mp3" + }, + "cap": { + "dur": 0.47, + "file": "words/cap.mp3" + }, + "car": { + "dur": 0.44, + "file": "words/car.mp3" + }, + "carrot": { + "dur": 0.45, + "file": "words/carrot.mp3" + }, + "cart": { + "dur": 0.41, + "file": "words/cart.mp3" + }, + "cat": { + "dur": 0.52, + "file": "words/cat.mp3" + }, + "chain": { + "dur": 0.65, + "file": "words/chain.mp3" + }, + "chest": { + "dur": 0.66, + "file": "words/chest.mp3" + }, + "chick": { + "dur": 0.53, + "file": "words/chick.mp3" + }, + "chicken": { + "dur": 0.47, + "file": "words/chicken.mp3" + }, + "chips": { + "dur": 0.59, + "file": "words/chips.mp3" + }, + "clock": { + "dur": 0.48, + "file": "words/clock.mp3" + }, + "cloud": { + "dur": 0.55, + "file": "words/cloud.mp3" + }, + "coat": { + "dur": 0.44, + "file": "words/coat.mp3" + }, + "coin": { + "dur": 0.6, + "file": "words/coin.mp3" + }, + "comet": { + "dur": 0.5, + "file": "words/comet.mp3" + }, + "cook": { + "dur": 0.35, + "file": "words/cook.mp3" + }, + "corn": { + "dur": 0.49, + "file": "words/corn.mp3" + }, + "cow": { + "dur": 0.45, + "file": "words/cow.mp3" + }, + "crab": { + "dur": 0.53, + "file": "words/crab.mp3" + }, + "crayon": { + "dur": 0.42, + "file": "words/crayon.mp3" + }, + "cub": { + "dur": 0.42, + "file": "words/cub.mp3" + }, + "cup": { + "dur": 0.43, + "file": "words/cup.mp3" + }, + "cut": { + "dur": 0.43, + "file": "words/cut.mp3" + }, + "dad": { + "dur": 0.49, + "file": "words/dad.mp3" + }, + "deer": { + "dur": 0.37, + "file": "words/deer.mp3" + }, + "dig": { + "dur": 0.42, + "file": "words/dig.mp3" + }, + "dish": { + "dur": 0.45, + "file": "words/dish.mp3" + }, + "doctor": { + "dur": 0.53, + "file": "words/doctor.mp3" + }, + "dog": { + "dur": 0.45, + "file": "words/dog.mp3" + }, + "doll": { + "dur": 0.45, + "file": "words/doll.mp3" + }, + "dragon": { + "dur": 0.88, + "file": "words/dragon.mp3" + }, + "dress": { + "dur": 0.62, + "file": "words/dress.mp3" + }, + "drum": { + "dur": 0.44, + "file": "words/drum.mp3" + }, + "duck": { + "dur": 0.55, + "file": "words/duck.mp3" + }, + "egg": { + "dur": 0.39, + "file": "words/egg.mp3" + }, + "elf": { + "dur": 0.78, + "file": "words/elf.mp3" + }, + "feet": { + "dur": 0.51, + "file": "words/feet.mp3" + }, + "fish": { + "dur": 0.57, + "file": "words/fish.mp3" + }, + "flag": { + "dur": 0.71, + "file": "words/flag.mp3" + }, + "fog": { + "dur": 0.49, + "file": "words/fog.mp3" + }, + "foot": { + "dur": 0.44, + "file": "words/foot.mp3" + }, + "fork": { + "dur": 0.47, + "file": "words/fork.mp3" + }, + "fossil": { + "dur": 0.45, + "file": "words/fossil.mp3" + }, + "fox": { + "dur": 0.57, + "file": "words/fox.mp3" + }, + "frog": { + "dur": 0.51, + "file": "words/frog.mp3" + }, + "gem": { + "dur": 0.47, + "file": "words/gem.mp3" + }, + "gift": { + "dur": 0.47, + "file": "words/gift.mp3" + }, + "goat": { + "dur": 0.4, + "file": "words/goat.mp3" + }, + "goblin": { + "dur": 0.5, + "file": "words/goblin.mp3" + }, + "ham": { + "dur": 0.64, + "file": "words/ham.mp3" + }, + "hammer": { + "dur": 0.42, + "file": "words/hammer.mp3" + }, + "hand": { + "dur": 0.54, + "file": "words/hand.mp3" + }, + "hat": { + "dur": 0.42, + "file": "words/hat.mp3" + }, + "hen": { + "dur": 0.4, + "file": "words/hen.mp3" + }, + "hiss": { + "dur": 0.45, + "file": "words/hiss.mp3" + }, + "hook": { + "dur": 0.42, + "file": "words/hook.mp3" + }, + "horn": { + "dur": 0.53, + "file": "words/horn.mp3" + }, + "hot": { + "dur": 0.44, + "file": "words/hot.mp3" + }, + "hug": { + "dur": 0.46, + "file": "words/hug.mp3" + }, + "hut": { + "dur": 0.36, + "file": "words/hut.mp3" + }, + "insect": { + "dur": 0.86, + "file": "words/insect.mp3" + }, + "jam": { + "dur": 0.48, + "file": "words/jam.mp3" + }, + "jar": { + "dur": 0.44, + "file": "words/jar.mp3" + }, + "jeep": { + "dur": 0.6, + "file": "words/jeep.mp3" + }, + "jump": { + "dur": 1.21, + "file": "words/jump.mp3" + }, + "kid": { + "dur": 0.46, + "file": "words/kid.mp3" + }, + "king": { + "dur": 0.68, + "file": "words/king.mp3" + }, + "kitten": { + "dur": 0.43, + "file": "words/kitten.mp3" + }, + "ladder": { + "dur": 0.55, + "file": "words/ladder.mp3" + }, + "lamp": { + "dur": 0.46, + "file": "words/lamp.mp3" + }, + "lantern": { + "dur": 0.84, + "file": "words/lantern.mp3" + }, + "laptop": { + "dur": 0.5, + "file": "words/laptop.mp3" + }, + "light": { + "dur": 0.41, + "file": "words/light.mp3" + }, + "lightning": { + "dur": 0.5, + "file": "words/lightning.mp3" + }, + "lips": { + "dur": 0.56, + "file": "words/lips.mp3" + }, + "lizard": { + "dur": 0.5, + "file": "words/lizard.mp3" + }, + "lobster": { + "dur": 0.62, + "file": "words/lobster.mp3" + }, + "lock": { + "dur": 0.46, + "file": "words/lock.mp3" + }, + "log": { + "dur": 0.53, + "file": "words/log.mp3" + }, + "lollipop": { + "dur": 0.71, + "file": "words/lollipop.mp3" + }, + "lunch": { + "dur": 0.59, + "file": "words/lunch.mp3" + }, + "mad": { + "dur": 0.51, + "file": "words/mad.mp3" + }, + "magic": { + "dur": 0.56, + "file": "words/magic.mp3" + }, + "mango": { + "dur": 0.96, + "file": "words/mango.mp3" + }, + "map": { + "dur": 0.48, + "file": "words/map.mp3" + }, + "mask": { + "dur": 0.75, + "file": "words/mask.mp3" + }, + "melon": { + "dur": 0.47, + "file": "words/melon.mp3" + }, + "milk": { + "dur": 0.78, + "file": "words/milk.mp3" + }, + "mom": { + "dur": 0.49, + "file": "words/mom.mp3" + }, + "monster": { + "dur": 0.66, + "file": "words/monster.mp3" + }, + "moo": { + "dur": 0.62, + "file": "words/moo.mp3" + }, + "moon": { + "dur": 0.53, + "file": "words/moon.mp3" + }, + "moth": { + "dur": 0.47, + "file": "words/moth.mp3" + }, + "mouth": { + "dur": 0.57, + "file": "words/mouth.mp3" + }, + "muffin": { + "dur": 0.47, + "file": "words/muffin.mp3" + }, + "mug": { + "dur": 0.47, + "file": "words/mug.mp3" + }, + "mushroom": { + "dur": 0.56, + "file": "words/mushroom.mp3" + }, + "nest": { + "dur": 1.1, + "file": "words/nest.mp3" + }, + "net": { + "dur": 0.43, + "file": "words/net.mp3" + }, + "night": { + "dur": 0.5, + "file": "words/night.mp3" + }, + "nut": { + "dur": 0.44, + "file": "words/nut.mp3" + }, + "octopus": { + "dur": 0.72, + "file": "words/octopus.mp3" + }, + "owl": { + "dur": 0.43, + "file": "words/owl.mp3" + }, + "ox": { + "dur": 0.6, + "file": "words/ox.mp3" + }, + "oyster": { + "dur": 0.52, + "file": "words/oyster.mp3" + }, + "paint": { + "dur": 0.55, + "file": "words/paint.mp3" + }, + "pan": { + "dur": 0.46, + "file": "words/pan.mp3" + }, + "pants": { + "dur": 0.54, + "file": "words/pants.mp3" + }, + "park": { + "dur": 0.41, + "file": "words/park.mp3" + }, + "parrot": { + "dur": 0.45, + "file": "words/parrot.mp3" + }, + "pen": { + "dur": 0.65, + "file": "words/pen.mp3" + }, + "pie": { + "dur": 0.41, + "file": "words/pie.mp3" + }, + "pig": { + "dur": 0.46, + "file": "words/pig.mp3" + }, + "pin": { + "dur": 0.55, + "file": "words/pin.mp3" + }, + "planet": { + "dur": 0.54, + "file": "words/planet.mp3" + }, + "plant": { + "dur": 0.61, + "file": "words/plant.mp3" + }, + "plug": { + "dur": 0.48, + "file": "words/plug.mp3" + }, + "popcorn": { + "dur": 0.54, + "file": "words/popcorn.mp3" + }, + "pot": { + "dur": 0.49, + "file": "words/pot.mp3" + }, + "pumpkin": { + "dur": 0.62, + "file": "words/pumpkin.mp3" + }, + "quack": { + "dur": 0.35, + "file": "words/quack.mp3" + }, + "queen": { + "dur": 0.58, + "file": "words/queen.mp3" + }, + "quiz": { + "dur": 0.57, + "file": "words/quiz.mp3" + }, + "rabbit": { + "dur": 0.47, + "file": "words/rabbit.mp3" + }, + "raccoon": { + "dur": 0.81, + "file": "words/raccoon.mp3" + }, + "rain": { + "dur": 0.49, + "file": "words/rain.mp3" + }, + "rainbow": { + "dur": 0.53, + "file": "words/rainbow.mp3" + }, + "ram": { + "dur": 0.48, + "file": "words/ram.mp3" + }, + "rat": { + "dur": 0.44, + "file": "words/rat.mp3" + }, + "ring": { + "dur": 0.49, + "file": "words/ring.mp3" + }, + "road": { + "dur": 0.51, + "file": "words/road.mp3" + }, + "roar": { + "dur": 0.41, + "file": "words/roar.mp3" + }, + "robot": { + "dur": 0.59, + "file": "words/robot.mp3" + }, + "rock": { + "dur": 0.45, + "file": "words/rock.mp3" + }, + "rocket": { + "dur": 0.53, + "file": "words/rocket.mp3" + }, + "rooster": { + "dur": 0.58, + "file": "words/rooster.mp3" + }, + "run": { + "dur": 0.46, + "file": "words/run.mp3" + }, + "sad": { + "dur": 0.56, + "file": "words/sad.mp3" + }, + "sandwich": { + "dur": 0.67, + "file": "words/sandwich.mp3" + }, + "scooter": { + "dur": 0.58, + "file": "words/scooter.mp3" + }, + "seed": { + "dur": 0.64, + "file": "words/seed.mp3" + }, + "shark": { + "dur": 0.48, + "file": "words/shark.mp3" + }, + "sheep": { + "dur": 0.63, + "file": "words/sheep.mp3" + }, + "shell": { + "dur": 0.49, + "file": "words/shell.mp3" + }, + "ship": { + "dur": 0.44, + "file": "words/ship.mp3" + }, + "shrimp": { + "dur": 0.52, + "file": "words/shrimp.mp3" + }, + "sing": { + "dur": 0.57, + "file": "words/sing.mp3" + }, + "six": { + "dur": 0.67, + "file": "words/six.mp3" + }, + "sleep": { + "dur": 0.59, + "file": "words/sleep.mp3" + }, + "snail": { + "dur": 0.53, + "file": "words/snail.mp3" + }, + "snap": { + "dur": 0.35, + "file": "words/snap.mp3" + }, + "snow": { + "dur": 0.57, + "file": "words/snow.mp3" + }, + "soap": { + "dur": 0.5, + "file": "words/soap.mp3" + }, + "sock": { + "dur": 0.56, + "file": "words/sock.mp3" + }, + "speedboat": { + "dur": 0.75, + "file": "words/speedboat.mp3" + }, + "spin": { + "dur": 0.61, + "file": "words/spin.mp3" + }, + "spoon": { + "dur": 0.73, + "file": "words/spoon.mp3" + }, + "squid": { + "dur": 0.57, + "file": "words/squid.mp3" + }, + "star": { + "dur": 0.56, + "file": "words/star.mp3" + }, + "storm": { + "dur": 0.59, + "file": "words/storm.mp3" + }, + "sun": { + "dur": 0.53, + "file": "words/sun.mp3" + }, + "sunset": { + "dur": 0.56, + "file": "words/sunset.mp3" + }, + "swim": { + "dur": 0.58, + "file": "words/swim.mp3" + }, + "tap": { + "dur": 0.4, + "file": "words/tap.mp3" + }, + "target": { + "dur": 0.53, + "file": "words/target.mp3" + }, + "teeth": { + "dur": 0.57, + "file": "words/teeth.mp3" + }, + "ten": { + "dur": 0.41, + "file": "words/ten.mp3" + }, + "tent": { + "dur": 0.52, + "file": "words/tent.mp3" + }, + "thumb": { + "dur": 0.38, + "file": "words/thumb.mp3" + }, + "thunder": { + "dur": 0.48, + "file": "words/thunder.mp3" + }, + "ticket": { + "dur": 0.34, + "file": "words/ticket.mp3" + }, + "tin": { + "dur": 0.48, + "file": "words/tin.mp3" + }, + "toast": { + "dur": 0.96, + "file": "words/toast.mp3" + }, + "toilet": { + "dur": 0.51, + "file": "words/toilet.mp3" + }, + "toy": { + "dur": 0.35, + "file": "words/toy.mp3" + }, + "tractor": { + "dur": 0.55, + "file": "words/tractor.mp3" + }, + "train": { + "dur": 0.53, + "file": "words/train.mp3" + }, + "tree": { + "dur": 0.46, + "file": "words/tree.mp3" + }, + "truck": { + "dur": 0.39, + "file": "words/truck.mp3" + }, + "van": { + "dur": 0.49, + "file": "words/van.mp3" + }, + "vest": { + "dur": 0.64, + "file": "words/vest.mp3" + }, + "web": { + "dur": 0.46, + "file": "words/web.mp3" + }, + "wheel": { + "dur": 0.46, + "file": "words/wheel.mp3" + }, + "wig": { + "dur": 0.58, + "file": "words/wig.mp3" + }, + "window": { + "dur": 0.5, + "file": "words/window.mp3" + }, + "wing": { + "dur": 0.48, + "file": "words/wing.mp3" + }, + "witch": { + "dur": 0.45, + "file": "words/witch.mp3" + }, + "zap": { + "dur": 0.78, + "file": "words/zap.mp3" + }, + "zip": { + "dur": 0.54, + "file": "words/zip.mp3" + } + } +}; diff --git a/games/story-stamp/audio.js b/games/story-stamp/audio.js @@ -0,0 +1,265 @@ +// Story Stamp â audio: recorded phonemes + words (Web Audio), speech synthesis +// for everything else, and a few oscillator SFX. +// +// Recorded clips are listed in assets/manifest.js (generated by +// tools/story-stamp-assets.py). Held sounds (/sss/, /mmm/, vowels) are long +// recordings; a blend pass asks for a length and the clip is cut there with a +// short fade â nothing is looped. A word with no recording is spoken by the +// browser instead; phonemes are never synthesised (a synthetic "suh" would +// break blending), so a phoneme with no clip is simply silent. +(function (root) { + 'use strict'; + + const SS = root.SS; + const TEST = !!root.__TEST_MODE__; + const scale = (ms) => (TEST ? ms * 0.1 : ms); // test mode runs every wait at 10% speed + + class StoryStampAudio { + constructor(assets, base) { + this.assets = assets || { phonemes: {}, words: {} }; + this.base = base || 'assets/audio/'; + this.ctx = null; + this.ready = false; + this.buffers = {}; // key -> { buffer, offset } | null (failed) + this.pending = {}; // key -> Promise + this.log = []; // every clip played, for tests: { kind, id, len, t, at } + this.voiceMuted = false; + this.sfxMuted = false; + this._voice = null; + this._unlock = null; + this._active = []; // scheduled sources, so a blend can be cancelled + this._timers = []; + } + + // Create the context and preload the phoneme set. Safe to call repeatedly. + init() { + if (this.ctx) return; + const AC = root.AudioContext || root.webkitAudioContext; + if (!AC) return; + try { this.ctx = new AC(); } catch (_) { return; } + this.master = this.ctx.createGain(); + this.master.connect(this.ctx.destination); + if (root.KGames && root.KGames.installAudioUnlock) { + this._unlock = root.KGames.installAudioUnlock(() => [this.ctx], () => { this.ready = true; }); + } else { + this.ready = true; + } + Object.keys(this.assets.phonemes || {}).forEach((id) => this.load('ph:' + id, this.assets.phonemes[id].file)); + } + + // ââ loading ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ + + load(key, file) { + if (this.buffers[key] !== undefined) return Promise.resolve(this.buffers[key]); + if (this.pending[key]) return this.pending[key]; + const url = this.base + file; + const p = fetch(url) + .then((r) => { if (!r.ok) throw new Error(r.status); return r.arrayBuffer(); }) + .then((ab) => new Promise((res, rej) => this.ctx.decodeAudioData(ab, res, rej))) + .then((buffer) => { + // Skip the encoder's leading silence so blends stay tight. + const d = buffer.getChannelData(0); + let i = 0; + while (i < d.length && Math.abs(d[i]) < 0.012) i++; + const entry = { buffer, offset: Math.max(0, i - 40) / buffer.sampleRate }; + this.buffers[key] = entry; + return entry; + }) + .catch(() => { this.buffers[key] = null; return null; }); + this.pending[key] = p; + return p; + } + + hasWord(w) { return !!(this.assets.words && this.assets.words[w]); } + hasPhoneme(p) { return !!(this.assets.phonemes && this.assets.phonemes[p]); } + + // Natural length of a clip in seconds (for timeline planning). + phonemeDuration(p) { + const e = this.buffers['ph:' + p]; + if (e) return e.buffer.duration - e.offset; + const m = this.assets.phonemes && this.assets.phonemes[p]; + return m ? m.dur : 0.25; + } + phonemeDurations() { + const out = {}; + Object.keys(SS.PHONEMES).forEach((p) => { out[p] = this.phonemeDuration(p); }); + return out; + } + + preloadWord(w) { + if (!this.ctx || !this.hasWord(w)) return Promise.resolve(null); + return this.load('w:' + w, this.assets.words[w].file); + } + + // ââ playback âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ + + _now() { return this.ctx ? this.ctx.currentTime : 0; } + + // Play one recorded phoneme. `len` (seconds) shortens a held sound; stops + // and unstretchable sounds always play whole. Returns the seconds it takes. + playPhoneme(id, opts) { + opts = opts || {}; + const delay = opts.delay || 0; + const nat = this.phonemeDuration(id); + const len = SS.phonemeLength(id, opts.len == null ? nat : opts.len, { [id]: nat }); + // `at` is when the clip will sound (log order is schedule order, not play order) + this.log.push({ kind: 'phoneme', id, len, t: Date.now(), at: Date.now() + scale(delay * 1000) }); + if (!this.ctx) return len; + const entry = this.buffers['ph:' + id]; + if (!entry) return len; + const at = this._now() + delay; + const src = this.ctx.createBufferSource(); + src.buffer = entry.buffer; + const g = this.ctx.createGain(); + src.connect(g); g.connect(this.master); + const fadeOut = len < nat - 0.01 ? 0.045 : 0.02; + g.gain.setValueAtTime(1, at); + g.gain.setValueAtTime(1, at + Math.max(0, len - fadeOut)); + g.gain.linearRampToValueAtTime(0.0001, at + len); + src.start(at, entry.offset); + src.stop(at + len + 0.01); + this._active.push(src); + return len; + } + + // Play the recorded word, or speak it. Resolves when it is (about) done. + playWord(w, opts) { + opts = opts || {}; + this.log.push({ kind: 'word', id: w, t: Date.now(), at: Date.now() + scale((opts.delay || 0) * 1000) }); + if (this.ctx && this.hasWord(w)) { + return this.preloadWord(w).then((entry) => { + if (!entry) return this.speak(w, opts.voice); + const at = this._now() + (opts.delay || 0); + const src = this.ctx.createBufferSource(); + src.buffer = entry.buffer; + src.connect(this.master); + src.start(at, entry.offset); + this._active.push(src); + const dur = entry.buffer.duration - entry.offset; + return this._sleep(((opts.delay || 0) + dur) * 1000); + }); + } + return this.speak(w, opts.voice); + } + + // All the sounds of a word with clear gaps (used to re-demonstrate). + playSegmented(units, opts) { + opts = opts || {}; + const gap = opts.gap == null ? 0.35 : opts.gap; + const len = opts.len == null ? 0.45 : opts.len; + let t = 0; + units.forEach((unit, ui) => { + if (opts.onUnit) this._timer(() => opts.onUnit(ui), t * 1000); + unit.forEach((p) => { t += this.playPhoneme(p, { delay: t, len }) + 0.02; }); + if (ui < units.length - 1) t += gap; + }); + return this._sleep(t * 1000 + 100); + } + + // Sounding out: run SS.blendTimeline and fire onEvent for each event at + // the right moment (visuals hang off these). The word itself is never + // played â the child says it. Resolves when the last pass has faded. + blend(units, onEvent) { + const durations = this.phonemeDurations(); + const events = SS.blendTimeline(units, durations); + let last = 0; + events.forEach((ev) => { + if (ev.kind === 'phoneme') this.playPhoneme(ev.id, { delay: ev.t, len: ev.len }); + if (onEvent) this._timer(() => onEvent(ev), ev.t * 1000); + last = Math.max(last, ev.t); + }); + return this._sleep(last * 1000).then(() => events); + } + + // Stop every scheduled clip and pending callback (e.g. leaving a screen). + cancel() { + this._active.forEach((s) => { try { s.stop(); } catch (_) {} }); + this._active = []; + this._timers.forEach(clearTimeout); + this._timers = []; + try { if (root.speechSynthesis) root.speechSynthesis.cancel(); } catch (_) {} + } + + // ââ speech synthesis âââââââââââââââââââââââââââââââââââââââââââââââââââ + + _pickVoice() { + if (this._voice || !root.speechSynthesis) return this._voice; + let voices = []; + try { voices = root.speechSynthesis.getVoices() || []; } catch (_) {} + const en = voices.filter((v) => /^en[-_]/i.test(v.lang)); + const prefer = en.find((v) => /child|kid|samantha|karen|moira|google us english/i.test(v.name)); + this._voice = prefer || en.find((v) => /^en[-_]US/i.test(v.lang)) || en[0] || null; + return this._voice; + } + + // Speak text. Resolves when finished, or after an estimate if the browser + // never reports the end (headless / no voices installed). + speak(text, voice) { + this.log.push({ kind: 'speak', id: text, t: Date.now(), at: Date.now() }); + const est = Math.min(6000, 350 + 65 * text.length); + if (TEST) return this._sleep(0); + if (this.voiceMuted) return this._sleep(150); // muted: keep the flow moving + if (!root.speechSynthesis) return this._sleep(est); // no TTS at all: leave time to read + return new Promise((resolve) => { + let done = false; + const finish = () => { if (!done) { done = true; resolve(); } }; + try { + const u = new SpeechSynthesisUtterance(text); + const v = this._pickVoice(); + if (v) u.voice = v; + u.lang = (v && v.lang) || 'en-US'; + u.pitch = voice && voice.pitch != null ? voice.pitch : 1.05; + u.rate = voice && voice.rate != null ? voice.rate : 0.95; + u.onend = finish; u.onerror = finish; + root.speechSynthesis.cancel(); + root.speechSynthesis.speak(u); + setTimeout(finish, est + 2500); + } catch (_) { finish(); } + }); + } + + // ââ SFX (oscillators; incidental, not the taught audio) âââââââââââââââ + + _tone(freq, dur, type, gain, when, slideTo) { + if (!this.ctx || this.sfxMuted) return; + const at = this._now() + (when || 0); + const o = this.ctx.createOscillator(); + const g = this.ctx.createGain(); + o.type = type || 'sine'; + o.frequency.setValueAtTime(freq, at); + if (slideTo) o.frequency.exponentialRampToValueAtTime(slideTo, at + dur); + g.gain.setValueAtTime(0.0001, at); + g.gain.exponentialRampToValueAtTime(gain || 0.15, at + 0.01); + g.gain.exponentialRampToValueAtTime(0.0001, at + dur); + o.connect(g); g.connect(this.master); + o.start(at); o.stop(at + dur + 0.02); + } + _noise(dur, gain, when) { + if (!this.ctx || this.sfxMuted) return; + const at = this._now() + (when || 0); + const n = Math.floor(this.ctx.sampleRate * dur); + const buf = this.ctx.createBuffer(1, n, this.ctx.sampleRate); + const d = buf.getChannelData(0); + for (let i = 0; i < n; i++) d[i] = (Math.random() * 2 - 1) * (1 - i / n); + const src = this.ctx.createBufferSource(); src.buffer = buf; + const g = this.ctx.createGain(); g.gain.setValueAtTime(gain || 0.1, at); + src.connect(g); g.connect(this.master); src.start(at); + } + stampLetter() { this._tone(180, 0.09, 'square', 0.05, 0, 90); this._noise(0.05, 0.05); } + bigStamp() { this._tone(90, 0.22, 'triangle', 0.25, 0, 45); this._noise(0.12, 0.12); } + tick() { this._tone(900, 0.05, 'square', 0.04); } + wrong() { this._tone(200, 0.25, 'triangle', 0.12, 0, 150); } + ding() { this._tone(880, 0.35, 'sine', 0.12); this._tone(1320, 0.5, 'sine', 0.08, 0.08); } + sparkle() { [0, 0.07, 0.14, 0.21].forEach((d, i) => this._tone(1200 + i * 300, 0.25, 'sine', 0.07, d)); } + fanfare() { [523, 659, 784, 1046].forEach((f, i) => this._tone(f, 0.3, 'triangle', 0.1, i * 0.11)); } + whoosh() { this._noise(0.25, 0.06); } + pageFlip() { this._noise(0.08, 0.05); this._noise(0.06, 0.04, 0.09); } + slide() { this._tone(300, 0.3, 'sine', 0.05, 0, 600); } + + // ââ utils ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ + _timer(fn, ms) { const id = setTimeout(fn, scale(ms)); this._timers.push(id); return id; } + _sleep(ms) { return new Promise((r) => this._timer(r, ms)); } + } + + root.StoryStampAudio = StoryStampAudio; +}(window)); diff --git a/games/story-stamp/game.js b/games/story-stamp/game.js @@ -0,0 +1,907 @@ +// Story Stamp â the game. Plain DOM + CSS animations, keyboard only. +// +// The child runs the checkout desk of the island library. Kids queue up, each +// wanting a book about something (bugs, space, dinosaurs ...). The child +// picks who to help; that patron hands over a request slip with the book's +// title in sound boxes. The child stamps each letter (hearing its sound), +// then presses SPACE to sound the title out: the sounds play with shrinking +// gaps until they nearly touch â the word itself is never said, the child +// has to say it â and then picks the right book from the cart, stamps it, and +// the patron leaves happy. "Returns" (unlocked later) go the other way: the +// patron says the word and the child types its sounds to shelve the book. +// Pure logic lives in lib.js, audio in audio.js; this file is UI + flow. +// +// Wall-clock rule: every delay is a setTimeout so the flow keeps moving in +// throttled / headless browsers. window.__TEST_MODE__ shortens them all. +(function (root) { + 'use strict'; + + const SS = root.SS; + const ASSETS = root.SS_ASSETS || { phonemes: {}, words: {}, images: {} }; + const TEST = !!root.__TEST_MODE__; + + const T = (ms) => (TEST ? Math.min(ms, 15) : ms); + const sleep = (ms) => new Promise((r) => setTimeout(r, T(ms))); + const esc = (s) => String(s).replace(/[&<>"]/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"' }[c])); + const pick = (arr) => arr[Math.floor(Math.random() * arr.length)]; + const cap = (s) => s.charAt(0).toUpperCase() + s.slice(1); + // Picture by Fluent Emoji asset name (see manifest.js); unknown names draw nothing. + const img = (name, cls) => { + const f = ASSETS.images && ASSETS.images[name]; + return f ? '<img class="ss-img ' + (cls || '') + '" src="assets/' + f + '" alt="" draggable="false">' : ''; + }; + const BOX_COLORS = ['#E63946', '#1D7CF2', '#2EC4B6', '#9B5DE5', '#F4A261', '#06A77D', '#FF5DA2', '#8AC926']; + const WALKERS = ['Child/Default', 'Girl/Default', 'Boy/Default', 'Penguin', 'Koala', 'Panda', 'Lion', 'Tiger face', 'Bear', 'Alien']; + const DECOR = { + rug: '<div class="d-rug"></div>', + plant: '<span class="d-plant">' + img('Potted plant') + '</span>', + lamp: '<span class="d-lamp">' + img('Light bulb') + '</span>', + fishtank: '<div class="d-tank">' + img('Fish') + img('Tropical fish') + '</div>', + bunting: '<div class="d-bunting"></div>', + cat: '<span class="d-cat">' + img('Cat') + '</span>', + globe: '<span class="d-globe">' + img('Globe showing americas') + '</span>', + telescope: '<span class="d-telescope">' + img('Telescope') + '</span>', + dino: '<span class="d-dino">' + img('Sauropod') + '</span>', + rocket: '<span class="d-rocket">' + img('Rocket') + '</span>', + }; + + const LINES = { + hello: ['Hello!', 'Hi there!', 'Good morning!', 'Hi!', 'Yoo-hoo!', 'Hello, librarian!'], + again: ['It is me again!', 'I am back!', 'Remember me?', 'Back for another book!'], + want: ['I want a book about {t}!', 'Do you have a book about {t}?', 'A book about {t}, please!', 'I would love a book about {t}.'], + wantAny: ['I want a good book!', 'Can you pick me a book?', 'Any good book, please!'], + slip: ['Here is my request slip. Can you read it?', 'It is called this. Can you read it?', 'This is the one I want!'], + returning: ['I am bringing this book back.', 'I finished my book! Here it is.', 'Time to return this one.'], + happy: ['Thank you!', 'Just what I wanted!', 'I love it!', 'You are the best librarian!', 'Hooray!', 'My favourite!'], + wrongBook: ['Hmm, that is not it.', 'Oops! Not that one.', 'Not quite! Try again.'], + instrRead: 'Sound it out! Press each letter.', + instrHear: 'Which sounds do you hear? Press the letters.', + readIt: 'Now press space to read it out loud!', + shelveIt: 'Now press space to put it back!', + choose: 'What does it say? Say the word, then pick the right book.', + queue: 'Who is next? Use the arrows and press enter.', + }; + + class StoryStamp { + constructor(container) { + this.container = container; + this.storage = null; + try { this.storage = root.localStorage; } catch (_) {} + this.storage = this.storage || { getItem() { return null; }, setItem() {}, removeItem() {} }; + this.store = SS.loadStore(this.storage); + this.profile = null; + this.audio = new root.StoryStampAudio(ASSETS, 'assets/audio/'); + this.state = 'boot'; + this.busy = false; + this.order = null; + this.queue = []; + this.sel = 0; + this.booksToday = 0; + this.todayWords = []; + this.todaySounds = []; + this.applied = {}; + this._build(); + this._bindKeys(); + this._watchSize(); + this.showProfiles(); + if (!TEST) this._ambient(); + } + + now() { return root.__SS_NOW__ || Date.now(); } + save() { SS.saveStore(this.storage, this.store); } + + // ââ DOM ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ + + _build() { + const r = document.createElement('div'); + r.className = 'ss-root' + (TEST ? ' ss-test' : ''); + const spines = (n, seed) => { + let out = ''; + for (let i = 0; i < n; i++) { + const h = 55 + (SS.hashStr(seed + i) % 40); + out += '<i style="height:' + h + '%;background:' + BOX_COLORS[(SS.hashStr(seed + 'c' + i)) % BOX_COLORS.length] + '"></i>'; + } + return out; + }; + const shelf = (side) => '<div class="ss-shelf ' + side + '">' + + [0, 1, 2, 3].map((row) => '<div class="ss-row"><div class="ss-spines">' + spines(7, side + row) + '</div></div>').join('') + '</div>'; + r.innerHTML = + '<div class="ss-wall">' + + '<div class="ss-window"><div class="ss-sky"><div class="ss-sun"></div><div class="ss-cloud"></div><div class="ss-cloud c2"></div>' + + '<span class="ss-bird">' + img('Bird') + '</span></div><div class="ss-sea"><span class="ss-boat">' + img('Sailboat') + '</span></div>' + + '<span class="ss-palm">' + img('Palm tree') + '</span><div class="ss-walkers"></div></div>' + + shelf('left') + shelf('right') + + '<div class="ss-sign">Island Library</div>' + + '<div class="ss-decor"></div>' + + '</div>' + + '<div class="ss-floor"></div>' + + '<div class="ss-queue"></div>' + + '<div class="ss-customer"><div class="ss-bubble"></div><div class="ss-cust-body"></div><div class="ss-cust-name"></div></div>' + + '<div class="ss-desk"><div class="ss-desk-top"></div><div class="ss-desk-front"></div>' + + '<div class="ss-slip"><div class="ss-slip-title"></div><div class="ss-boxes"></div><div class="ss-finger">' + img('Backhand index pointing up/Default') + '</div></div>' + + '<div class="ss-stamp"><div class="ss-stamp-handle"></div><div class="ss-stamp-base"></div></div>' + + '<div class="ss-cart"></div>' + + '<div class="ss-book-out"></div>' + + '</div>' + + '<div class="ss-prompt"></div>' + + '<div class="ss-hud"><div class="ss-chip ss-who"></div><div class="ss-chip right ss-shelf-chip"></div></div>' + + '<div class="ss-day"></div>' + + '<div class="ss-overlay" hidden><div class="ss-panel"></div></div>'; + this.container.appendChild(r); + this.$ = (sel) => r.querySelector(sel); + this.root = r; + this.$queue = this.$('.ss-queue'); this.$boxes = this.$('.ss-boxes'); this.$prompt = this.$('.ss-prompt'); + this.$cart = this.$('.ss-cart'); this.$slip = this.$('.ss-slip'); this.$slipTitle = this.$('.ss-slip-title'); + this.$finger = this.$('.ss-finger'); this.$stamp = this.$('.ss-stamp'); this.$bookOut = this.$('.ss-book-out'); + this.$customer = this.$('.ss-customer'); this.$bubble = this.$('.ss-bubble'); this.$custBody = this.$('.ss-cust-body'); + this.$custName = this.$('.ss-cust-name'); this.$overlay = this.$('.ss-overlay'); this.$panel = this.$('.ss-panel'); + this.$who = this.$('.ss-who'); this.$shelfChip = this.$('.ss-shelf-chip'); this.$day = this.$('.ss-day'); + this.$decor = this.$('.ss-decor'); this.$walkers = this.$('.ss-walkers'); + + const btnV = document.getElementById('btn-voice'), btnS = document.getElementById('btn-sfx'); + if (btnV) btnV.addEventListener('click', () => { this.audio.voiceMuted = !this.audio.voiceMuted; btnV.classList.toggle('muted', this.audio.voiceMuted); btnV.textContent = 'Speech: ' + (this.audio.voiceMuted ? 'OFF' : 'ON'); if (this.audio.voiceMuted) this.audio.cancel(); }); + if (btnS) btnS.addEventListener('click', () => { this.audio.sfxMuted = !this.audio.sfxMuted; btnS.classList.toggle('muted', this.audio.sfxMuted); btnS.textContent = 'SFX: ' + (this.audio.sfxMuted ? 'OFF' : 'ON'); }); + } + + _watchSize() { + const apply = () => { + const w = this.container.clientWidth || 800, h = this.container.clientHeight || 600; + this.root.style.setProperty('--u', (Math.min(w, h * 1.25) / 100) + 'px'); + this.root.classList.toggle('ss-narrow', w < h * 0.9); + }; + apply(); + if (root.ResizeObserver) { try { new ResizeObserver(apply).observe(this.container); } catch (_) {} } + root.addEventListener('resize', apply); + } + + // Passers-by outside the window. + _ambient() { + const spawn = () => { + if (document.visibilityState === 'hidden') return; + const el = document.createElement('span'); + el.className = 'ss-walker'; + const rev = Math.random() < 0.5; + el.style.left = rev ? '100%' : '-15%'; + el.style.transform = 'translateX(0)'; + el.innerHTML = img(pick(WALKERS)); + this.$walkers.appendChild(el); + requestAnimationFrame(() => requestAnimationFrame(() => { el.style.transform = 'translateX(' + (rev ? '-' : '') + '130%)'; })); + setTimeout(() => el.remove(), 16000); + }; + setTimeout(spawn, 3000); + setInterval(spawn, 12000 + Math.random() * 6000); + } + + setPrompt(html) { this.$prompt.innerHTML = html || ''; } + + toast(text) { + const t = document.createElement('div'); + t.className = 'ss-toast'; t.innerHTML = text; + this.root.appendChild(t); + setTimeout(() => t.remove(), T(2600)); + } + + fx(emoji, x, y) { + const e = document.createElement('span'); + e.className = 'ss-fx'; e.textContent = emoji; + e.style.left = x + '%'; e.style.top = y + '%'; + this.root.appendChild(e); + setTimeout(() => e.remove(), T(1400)); + } + + // ââ Keyboard âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ + + _bindKeys() { + root.addEventListener('keydown', (e) => { + if (e.ctrlKey || e.metaKey || e.altKey) return; + const k = e.key; + if ([' ', 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Backspace'].indexOf(k) >= 0) e.preventDefault(); + this.onKey(k); + }); + } + + onKey(k) { + const letter = k.length === 1 && /[a-z]/i.test(k) ? k.toLowerCase() : null; + const s = this.state; + if (s === 'profiles') return this._keyProfiles(k, letter); + if (s === 'avatar') return this._keyAvatar(k); + if (s === 'name') return this._keyName(k, letter); + if (s === 'confirmDelete') return this._keyConfirm(letter); + if (s === 'menu') return this._keyMenu(k); + if (s === 'shelf') return this._keyShelf(k); + if (s === 'dayend') { if (k === 'Enter' || k === ' ') this.newDay(); else if (k === 'Backspace') this.showProfiles(); return; } + if (s === 'newsound') { if ((k === 'Enter' || k === ' ') && this._continue) { const c = this._continue; this._continue = null; c(); } return; } + if (k === 'Backspace' || k === 'Escape') { if (!this.busy) this.showMenu(); return; } + if (s === 'queue') return this._keyQueue(k); + if (s === 'read') { if (letter) this.typeLetter(letter); else if (k === ' ' || k === 'Enter') this.hint(); return; } + if (s === 'blend') { if (k === ' ' || k === 'Enter') this.soundOut(); return; } + if (s === 'choose') return this._keyChoose(k); + } + + // ââ Profiles âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ + + showProfiles() { + this.audio.cancel(); + this.state = 'profiles'; this.busy = false; this.sel = 0; + const list = this.store.profiles; + const idx = list.findIndex((p) => p.id === this.store.last); + if (idx >= 0) this.sel = idx; + this._renderProfiles(); + this.audio.speak(list.length ? 'Who is the librarian today? Use the arrows and press enter.' : 'Welcome to the island library! Press enter to make a new librarian.'); + } + + _renderProfiles() { + const list = this.store.profiles; + const items = list.map((p, i) => '<div class="ss-item' + (i === this.sel ? ' sel' : '') + '">' + img(p.avatar) + esc(p.name) + + '<small>' + SS.masteredWords(p).length + ' books</small></div>'); + if (list.length < SS.MAX_PROFILES) items.push('<div class="ss-item new' + (this.sel === list.length ? ' sel' : '') + '">⨠New librarian</div>'); + this._panel('<h2>Who is the librarian today?</h2><div class="ss-list">' + items.join('') + '</div>' + + '<p><kbd>â</kbd> <kbd>â</kbd> choose ¡ <kbd>Enter</kbd> start' + (list.length ? ' ¡ <kbd>X</kbd> remove' : '') + '</p>'); + } + + _keyProfiles(k, letter) { + const list = this.store.profiles; + const n = list.length + (list.length < SS.MAX_PROFILES ? 1 : 0); + if (k === 'ArrowUp') { this.sel = (this.sel + n - 1) % n; this._renderProfiles(); this.audio.init(); this.audio.tick(); return; } + if (k === 'ArrowDown') { this.sel = (this.sel + 1) % n; this._renderProfiles(); this.audio.init(); this.audio.tick(); return; } + if (/^[1-9]$/.test(k) && +k <= n) { this.sel = +k - 1; this._renderProfiles(); return; } + if (k === 'Enter' || k === ' ') { + this.audio.init(); + if (this.sel < list.length) this.start(list[this.sel]); + else this.showAvatar(); + return; + } + if ((letter === 'x' || k === 'Delete') && this.sel < list.length) { + this.state = 'confirmDelete'; + const p = list[this.sel]; + this._panel('<h2>Remove ' + esc(p.name) + '?</h2><p>' + img(p.avatar) + '</p><p>All of ' + esc(p.name) + '\'s books will be forgotten.</p><p><kbd>Y</kbd> yes, remove ¡ any other key: keep</p>'); + this.audio.speak('Remove ' + p.name + '? Press Y to remove, or any other key to keep.'); + } + } + + _keyConfirm(letter) { + if (letter === 'y') { + const p = this.store.profiles[this.sel]; + this.store.profiles = this.store.profiles.filter((x) => x !== p); + if (this.store.last === p.id) this.store.last = null; + this.save(); + } + this.showProfiles(); + } + + showAvatar() { + this.state = 'avatar'; this.avatarSel = Math.floor(Math.random() * SS.AVATARS.length); + this._renderAvatar(); + this.audio.speak('Pick a picture for yourself. Use the arrows, then press enter.'); + } + _renderAvatar() { + this._panel('<h2>Pick your picture</h2><div class="ss-avatars">' + + SS.AVATARS.map((a, i) => img(a, i === this.avatarSel ? 'sel' : '')).join('') + + '</div><p><kbd>â</kbd> <kbd>â</kbd> choose ¡ <kbd>Enter</kbd> next</p>'); + } + _keyAvatar(k) { + const n = SS.AVATARS.length; + if (k === 'ArrowLeft' || k === 'ArrowUp') { this.avatarSel = (this.avatarSel + n - 1) % n; this._renderAvatar(); this.audio.tick(); } + if (k === 'ArrowRight' || k === 'ArrowDown') { this.avatarSel = (this.avatarSel + 1) % n; this._renderAvatar(); this.audio.tick(); } + if (k === 'Enter' || k === ' ') { this.state = 'name'; this.nameBuf = ''; this._renderName(); this.audio.speak('Now type your name and press enter.'); } + if (k === 'Backspace') this.showProfiles(); + } + _renderName() { + this._panel('<h2>What is your name?</h2><p>' + img(SS.AVATARS[this.avatarSel]) + '</p><div class="ss-namefield">' + esc(this.nameBuf) + '</div>' + + '<p>Type your name ¡ <kbd>Enter</kbd> when done</p>'); + } + _keyName(k, letter) { + if (letter && this.nameBuf.length < 12) { this.nameBuf += letter; this._renderName(); this.audio.tick(); return; } + if (k === 'Backspace') { this.nameBuf = this.nameBuf.slice(0, -1); this._renderName(); return; } + if (k === 'Enter') { + const p = SS.newProfile(cap(this.nameBuf || 'librarian'), SS.AVATARS[this.avatarSel], this.now()); + this.store.profiles.push(p); + this.save(); + this.start(p); + } + } + + // Test / debug helper: create (or reuse) a profile and open the desk. + quickStart(name) { + let p = this.store.profiles.find((x) => x.name === name); + if (!p) { p = SS.newProfile(name, SS.AVATARS[0], this.now()); this.store.profiles.push(p); this.save(); } + this.audio.init(); + return this.start(p); + } + + // ââ Menu / my shelf ââââââââââââââââââââââââââââââââââââââââââââââââââââ + + showMenu() { + this.audio.cancel(); + this._resumeState = this.state; this.state = 'menu'; this.sel = 0; + this._renderMenu(); + this.audio.speak('Menu. My shelf, change librarian, or back to the desk.'); + } + _menuItems() { return ['đ My shelf', 'đ Change librarian', 'đˇď¸ Back to the desk']; } + _renderMenu() { + this._panel('<h2>Menu</h2><div class="ss-list">' + this._menuItems().map((t, i) => '<div class="ss-item' + (i === this.sel ? ' sel' : '') + '">' + t + '</div>').join('') + + '</div><p><kbd>â</kbd> <kbd>â</kbd> ¡ <kbd>Enter</kbd> ¡ <kbd>âŤ</kbd> back</p>'); + } + _keyMenu(k) { + const n = 3; + if (k === 'ArrowUp') { this.sel = (this.sel + n - 1) % n; this._renderMenu(); this.audio.tick(); } + if (k === 'ArrowDown') { this.sel = (this.sel + 1) % n; this._renderMenu(); this.audio.tick(); } + if (k === 'Backspace' || k === 'Escape') this._closeOverlay(); + if (k === 'Enter' || k === ' ') { + if (this.sel === 0) this.showShelf(); + else if (this.sel === 1) { this.$overlay.hidden = true; this.showProfiles(); } + else this._closeOverlay(); + } + } + _closeOverlay() { + this.$overlay.hidden = true; + this.state = this._resumeState || 'queue'; + this.audio.speak(this.state === 'queue' ? LINES.queue : 'Back to the desk!'); + } + + showShelf() { + this.state = 'shelf'; this.sel = 0; + this._renderShelf(); + const n = SS.masteredWords(this.profile).length; + this.audio.speak(n ? 'Your shelf has ' + n + ' books. Press enter to hear one.' : 'Your shelf is empty. Check out some books!'); + } + _shelfEntries() { + const p = this.profile; + const solid = SS.masteredWords(p), shaky = SS.shakyWords(p); // both skip words no longer in WORDS + const all = solid.map((w) => ({ w, solid: true })).concat(shaky.map((w) => ({ w, solid: false }))); + // Group by first topic so the shelf reads like the library's sections. + const order = SS.TOPICS.map((t) => t.id); + all.forEach((e) => { e.topic = SS.WORD_BY_NAME[e.w].t[0]; }); + all.sort((a, b) => order.indexOf(a.topic) - order.indexOf(b.topic) || a.w.localeCompare(b.w)); + return all; + } + _renderShelf() { + const entries = this._shelfEntries(); + let html = '', lastTopic = null; + entries.forEach((e, i) => { + if (e.topic !== lastTopic) { html += '<div class="ss-shelf-label">' + img(SS.TOPIC_BY_ID[e.topic].art) + ' ' + esc(SS.TOPIC_BY_ID[e.topic].name) + '</div>'; lastTopic = e.topic; } + html += '<div class="ss-book-mini' + (e.solid ? '' : ' shaky') + (i === this.sel ? ' sel' : '') + '">' + img(SS.WORD_BY_NAME[e.w].art) + '<span>' + e.w + '</span></div>'; + }); + this._panel('<h2>My shelf</h2>' + (entries.length ? '<div class="ss-books">' + html + '</div>' : '<p>No books yet â help someone at the desk!</p>') + + '<p><kbd>â</kbd> <kbd>â</kbd> choose ¡ <kbd>Enter</kbd> hear it ¡ <kbd>âŤ</kbd> back</p>'); + const sel = this.$panel.querySelector('.ss-book-mini.sel'); + if (sel && sel.scrollIntoView) { try { sel.scrollIntoView({ block: 'nearest' }); } catch (_) {} } + } + _keyShelf(k) { + const entries = this._shelfEntries(); const n = entries.length || 1; + if (k === 'ArrowLeft' || k === 'ArrowUp') { this.sel = (this.sel + n - 1) % n; this._renderShelf(); this.audio.tick(); } + if (k === 'ArrowRight' || k === 'ArrowDown') { this.sel = (this.sel + 1) % n; this._renderShelf(); this.audio.tick(); } + if (k === 'Backspace' || k === 'Escape') this._closeOverlay(); + if ((k === 'Enter' || k === ' ') && entries.length) { + const word = SS.WORD_BY_NAME[entries[this.sel].w]; + this.audio.cancel(); + this.audio.playSegmented(SS.phonemeUnits(word), { gap: 0.3 }).then(() => this.audio.playWord(word.w)); + } + } + + _panel(html) { this.$panel.innerHTML = html; this.$overlay.hidden = false; } + + // ââ Start / HUD / library ââââââââââââââââââââââââââââââââââââââââââââââ + + async start(profile) { + this.profile = profile; + this.store.last = profile.id; + this.save(); + this.$overlay.hidden = true; + this.booksToday = 0; this.todayWords = []; this.todaySounds = []; + this.root.classList.remove('ss-dusk'); + this.applied = {}; + this._applyLibrary(true); + this._hud(); + this.audio.init(); + this.busy = true; this.state = 'opening'; + root.__SS_READY__ = true; + await this.audio.speak('Hello ' + profile.name + '! Welcome to the island library. You are at the checkout desk.'); + this.showQueue(); + } + + _hud() { + const p = this.profile; if (!p) return; + this.$who.innerHTML = img(p.avatar) + esc(p.name); + this.$shelfChip.innerHTML = 'đ ' + SS.masteredWords(p).length + ' <small>⍠menu</small>'; + let dots = ''; + for (let i = 0; i < SS.BOOKS_PER_DAY; i++) dots += '<span class="' + (i < this.booksToday ? 'on' : '') + '"></span>'; + this.$day.innerHTML = dots; + } + + // Decorate the library for every upgrade earned; announce new ones if asked. + _applyLibrary(silent) { + const ups = SS.libraryUpgrades(SS.masteredWords(this.profile).length); + const fresh = []; + ups.forEach((u) => { + if (this.applied[u.id]) return; + this.applied[u.id] = true; + this.root.classList.add('has-' + u.id); + if (DECOR[u.id]) this.$decor.insertAdjacentHTML('beforeend', DECOR[u.id]); + if (!silent) fresh.push(u); + }); + return fresh; + } + + // ââ The queue ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ + + _resetDesk() { + this.order = null; + this.$boxes.innerHTML = ''; this.$boxes.className = 'ss-boxes'; + this.$slip.className = 'ss-slip'; this.$slipTitle.innerHTML = ''; + this.$cart.innerHTML = ''; this.$cart.className = 'ss-cart'; this.$cart.parentNode.classList.remove('choosing'); + this.$bookOut.innerHTML = ''; this.$bookOut.className = 'ss-book-out'; + this.$stamp.className = 'ss-stamp'; + this.$finger.className = 'ss-finger'; + this.setPrompt(''); + } + + async showQueue() { + this.busy = true; this.state = 'arrive'; + this._resetDesk(); + if (SS.shouldIntroduce(this.profile)) { + const g = SS.introduceNext(this.profile); + this.save(); + this.todaySounds.push(g); + await this.showNewSound(g); + } + const queue = SS.buildQueue(this.profile, this.now(), Math.random, 3); + if (root.__SS_FORCE_MODE__) queue.forEach((q) => { if (this.profile.words[q.word.w] && this.audio.hasWord(q.word.w)) q.mode = root.__SS_FORCE_MODE__; }); + // Returns need a recording of the word (the patron has to say it). + queue.forEach((q) => { if (q.mode === 'return' && !this.audio.hasWord(q.word.w)) q.mode = 'read'; }); + this.queue = queue; + this.sel = 0; + queue.forEach((q) => this.audio.preloadWord(q.word.w)); + this._renderQueue(true); + await sleep(900); + this.state = 'queue'; this.busy = false; + this.setPrompt('Who is next? <kbd>â</kbd> <kbd>â</kbd> then <kbd>Enter</kbd>'); + await this.audio.speak(LINES.queue); + if (this.state === 'queue') this._sayWish(); + } + + _renderQueue(fresh) { + this.$queue.innerHTML = this.queue.map((q, i) => { + const t = q.topic; + return '<div class="ss-patron' + (i === this.sel ? ' sel' : '') + (fresh ? ' in' : '') + '" style="--c:' + q.patron.color + ';animation-delay:' + (i * 0.15) + 's">' + + '<div class="ss-sign-card" style="border-color:' + t.color + '">' + img(t.art) + '<span>' + esc(t.name) + '</span></div>' + + (q.mode === 'return' ? '<div class="ss-holding">' + img(q.word.art) + '</div>' : '') + + '<div class="ss-patron-body">' + img(q.patron.art) + '</div>' + + '<div class="ss-patron-name">' + esc(q.patron.name) + '</div><span class="ss-num">' + (i + 1) + '</span></div>'; + }).join(''); + } + + _sayWish() { + const q = this.queue[this.sel]; if (!q) return; + const line = q.mode === 'return' ? pick(LINES.returning) + : q.topic.id === 'any' ? pick(LINES.wantAny) : pick(LINES.want).replace('{t}', q.topic.name); + this.audio.cancel(); + this.audio.speak(q.patron.name + ' says: ' + line, q.patron.voice); + } + + _keyQueue(k) { + if (this.busy) return; + const n = this.queue.length; + if (!n) { this.showQueue(); return; } + if (k === 'ArrowLeft' || k === 'ArrowUp') { this.sel = (this.sel + n - 1) % n; this._renderQueue(); this.audio.tick(); this._sayWish(); } + if (k === 'ArrowRight' || k === 'ArrowDown') { this.sel = (this.sel + 1) % n; this._renderQueue(); this.audio.tick(); this._sayWish(); } + if (/^[1-9]$/.test(k) && +k <= n) { this.sel = +k - 1; this._renderQueue(); this.callPatron(); } + if (k === 'Enter' || k === ' ') this.callPatron(); + } + + async callPatron() { + const q = this.queue[this.sel]; + if (!q) return; + this.busy = true; this.state = 'arrive'; + this.audio.cancel(); + SS.recordTopic(this.profile, q.topic.id); + this.save(); + this.order = { + word: q.word, mode: q.mode, review: q.review, topic: q.topic, patron: q.patron, + idx: 0, buffer: '', missed: false, boxMissed: false, wrongPicks: 0, + units: SS.phonemeUnits(q.word), graphemes: SS.graphemes(q.word), + }; + const els = Array.from(this.$queue.children); + els.forEach((el, i) => el.classList.add(i === this.sel ? 'called' : 'wait')); + this.setPrompt(''); + await sleep(500); + this.$queue.innerHTML = ''; + await this.arrive(); + } + + async showNewSound(g) { + this.state = 'newsound'; + const ps = SS.graphemePhonemes(g); + const example = SS.availableWords(this.profile.introduced).find((w) => SS.graphemes(w).map(SS.baseGrapheme).indexOf(g) >= 0 && this.audio.hasWord(w.w)) + || SS.availableWords(this.profile.introduced).find((w) => SS.graphemes(w).map(SS.baseGrapheme).indexOf(g) >= 0); + this._panel('<h2>A new sound!</h2><div class="ss-big-letter">' + g + '</div>' + + (example ? '<p>like in <b>' + example.w + '</b> ' + img(example.art) + '</p>' : '') + '<p><kbd>Enter</kbd> to keep going</p>'); + this.audio.sparkle(); + await this.audio.speak('A new sound! This one says'); + for (let rep = 0; rep < 2; rep++) { + let t = 0; + ps.forEach((p) => { t += this.audio.playPhoneme(p, { delay: t, len: 0.7 }) + 0.05; }); + await sleep(t * 1000 + 500); + } + if (example) { + await this.audio.speak('Like in'); + await this.audio.playSegmented(SS.phonemeUnits(example), { gap: 0.25, len: 0.35 }); + await this.audio.playWord(example.w); + } + await new Promise((resolve) => { + this._continue = resolve; + if (TEST) resolve(); + else setTimeout(() => { if (this._continue === resolve) { this._continue = null; resolve(); } }, 9000); + }); + this.$overlay.hidden = true; + } + + // ââ Serving one patron âââââââââââââââââââââââââââââââââââââââââââââââââ + + _renderCustomer() { + const c = this.order.patron; + this.$custBody.innerHTML = img(c.art); + this.$custName.textContent = c.name; + this.$custName.style.background = c.color; + this.$bubble.className = 'ss-bubble'; this.$bubble.innerHTML = ''; + } + + // Build the sound boxes once per book; _updateBoxes() keeps their classes in + // step so a stamped tile stays stamped (re-rendering would replay its drop). + _renderBoxes() { + const o = this.order; + this.$boxes.innerHTML = o.graphemes.map((g, i) => + '<div class="ss-box' + (g.length > 1 ? ' wide' : '') + '" data-i="' + i + '" style="--c:' + BOX_COLORS[i % BOX_COLORS.length] + '">' + esc(g) + '</div>').join(''); + this._updateBoxes(); + } + _updateBoxes() { + const o = this.order; + Array.from(this.$boxes.children).forEach((b, i) => { + const g = o.graphemes[i]; + b.classList.toggle('done', i < o.idx); + b.classList.toggle('next', i === o.idx); + b.classList.toggle('hidden-letter', o.mode === 'return' && i >= o.idx); + if (i === o.idx && o.buffer) b.innerHTML = '<span class="ss-partial">' + esc(o.buffer) + '</span>' + esc(g.slice(o.buffer.length)); + else b.textContent = g; + }); + } + + async arrive() { + const o = this.order, c = o.patron; + this._renderCustomer(); + this.$customer.classList.remove('in', 'out', 'happy', 'puzzled'); + void this.$customer.offsetWidth; // restart the walk-in transition + this.$customer.classList.add('in'); + this.$boxes.innerHTML = ''; + await sleep(900); + const hello = pick(LINES.hello) + (o.review ? ' ' + pick(LINES.again) : ''); + await this.audio.speak(hello, c.voice); + if (o.mode === 'read') { + this.$bubble.innerHTML = img(o.topic.art); + this.$bubble.classList.add('show'); + await this.audio.speak(o.topic.id === 'any' ? pick(LINES.wantAny) : pick(LINES.want).replace('{t}', o.topic.name), c.voice); + this.$slipTitle.innerHTML = 'Request'; + this._renderBoxes(); + this.$slip.classList.add('show'); + this.audio.pageFlip(); + await this.audio.speak(pick(LINES.slip), c.voice); + this.setPrompt(LINES.instrRead); + this.audio.speak(LINES.instrRead); + } else { + this.$bubble.innerHTML = img(o.word.art); + this.$bubble.classList.add('show'); + await this.audio.speak(pick(LINES.returning), c.voice); + await this.audio.playWord(o.word.w); + this.$slipTitle.innerHTML = 'Return card'; + this._renderBoxes(); + this.$slip.classList.add('show'); + this.audio.pageFlip(); + this.setPrompt(LINES.instrHear); + this.audio.speak(LINES.instrHear); + } + this.state = 'read'; this.busy = false; + } + + // Replay the demonstration for the current box. + hint() { + if (this.busy) return; + const o = this.order; + this.audio.cancel(); + if (o.mode === 'return') { + this.audio.playWord(o.word.w).then(() => this.audio.playSegmented(o.units, { gap: 0.3, onUnit: (i) => this._flashBox(i) })); + } else { + this._flashBox(o.idx); + let t = 0; + o.units[o.idx].forEach((p) => { t += this.audio.playPhoneme(p, { delay: t, len: 0.45 }) + 0.04; }); + } + } + + _flashBox(i, ms) { + const b = this.$boxes.querySelector('[data-i="' + i + '"]'); + if (!b) return; + b.classList.add('speaking'); + if (this.order.mode === 'return') b.classList.add('reveal'); + setTimeout(() => b.classList.remove('speaking', 'reveal'), T(ms || 650)); + } + + typeLetter(ch) { + if (this.busy) return; + const o = this.order; + const expected = o.graphemes[o.idx]; + const attempt = o.buffer + ch; + if (expected.indexOf(attempt) === 0) { + o.buffer = attempt; + if (attempt === expected) this._acceptGrapheme(); + else { this._updateBoxes(); this.audio.tick(); } + } else { + this._wrongKey(); + } + } + + _acceptGrapheme() { + const o = this.order; + SS.recordGrapheme(this.profile, o.graphemes[o.idx], !o.boxMissed); + o.boxMissed = false; + const unit = o.units[o.idx]; + let t = 0; + unit.forEach((p) => { t += this.audio.playPhoneme(p, { delay: t, len: 0.4 }) + 0.03; }); + this.audio.stampLetter(); + o.idx += 1; o.buffer = ''; + this._updateBoxes(); + const box = this.$boxes.querySelector('[data-i="' + (o.idx - 1) + '"]'); + if (box) { box.classList.add('stamped'); box.classList.add('reveal'); } + this.$stamp.classList.remove('press'); void this.$stamp.offsetWidth; this.$stamp.classList.add('press'); + if (o.idx >= o.graphemes.length) this._wordComplete(); + } + + async _wordComplete() { + const o = this.order; + this.state = 'blend'; this.busy = false; // SPACE works straight away; the prompt just follows + this.$slip.classList.add('ready'); + await sleep(450); + if (this.state !== 'blend') return; + this.setPrompt(o.mode === 'read' ? 'Press <kbd>space</kbd> to read it!' : 'Press <kbd>space</kbd> to shelve it!'); + this.audio.speak(o.mode === 'read' ? LINES.readIt : LINES.shelveIt); + } + + async _wrongKey() { + const o = this.order; + o.missed = true; + if (!o.boxMissed) { o.boxMissed = true; SS.recordGrapheme(this.profile, o.graphemes[o.idx], false); } + o.buffer = ''; + this._updateBoxes(); + this.busy = true; + this.audio.cancel(); + this.audio.wrong(); + this.$slip.classList.remove('wobble'); void this.$slip.offsetWidth; this.$slip.classList.add('wobble'); + const box = this.$boxes.querySelector('[data-i="' + o.idx + '"]'); + if (box) { box.classList.add('hint'); setTimeout(() => box.classList.remove('hint'), T(1000)); } + if (o.mode === 'read') { + await this.audio.speak('This one says'); + this._flashBox(o.idx, 900); + let t = 0; + o.units[o.idx].forEach((p) => { t += this.audio.playPhoneme(p, { delay: t, len: 0.6 }) + 0.04; }); + await sleep(t * 1000 + 200); + } else { + await this.audio.speak('Listen again.'); + await this.audio.playWord(o.word.w); + await this.audio.playSegmented(o.units, { gap: 0.35, onUnit: (i) => this._flashBox(i) }); + await this.audio.speak('Which sound comes next?'); + } + this.busy = false; + } + + // SPACE: sound the title out. The finger slides under each box as its + // sound plays; each pass is faster and closer until the boxes touch. + async soundOut() { + if (this.busy) return; + const o = this.order; + this.busy = true; this.state = 'sounding'; + this.setPrompt(''); + this.$slip.classList.remove('ready'); + this.$slip.classList.add('reading'); + this.$finger.classList.add('show'); + const boxes = Array.from(this.$boxes.children); + await this.audio.blend(o.units, (ev) => { + if (ev.kind === 'pass') { this.$boxes.className = 'ss-boxes pass' + ev.pass; this._moveFinger(boxes[0]); } + if (ev.kind === 'phoneme') { + const x = boxes[ev.unit]; + if (x) { x.classList.add('speaking', 'reveal'); setTimeout(() => x.classList.remove('speaking'), T(ev.len * 1000)); this._moveFinger(x); } + } + if (ev.kind === 'done') { + this.$boxes.classList.add('fused'); + boxes.forEach((x) => x.classList.add('done', 'reveal')); + this.$finger.classList.remove('show'); + this.audio.sparkle(); + this.fx('â¨', 46, 58); this.fx('â¨', 54, 56); + } + }); + this.$slip.classList.remove('reading'); + await sleep(400); + if (o.mode === 'read') this.choose(); else this.shelve(); + } + + _moveFinger(box) { + if (!box) return; + const r = box.getBoundingClientRect(), s = this.$slip.getBoundingClientRect(); + if (!s.width) return; + this.$finger.style.left = ((r.left + r.width / 2 - s.left) / s.width * 100) + '%'; + } + + // Which book on the cart matches what was just read? + choose() { + const o = this.order; + const cards = [o.word].concat(SS.distractors(o.word, this.profile, Math.random)); + for (let i = cards.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [cards[i], cards[j]] = [cards[j], cards[i]]; } + o.cards = cards; this.sel = 0; + this._renderCards(); + this.$cart.classList.add('show'); + this.$cart.parentNode.classList.add('choosing'); + this.audio.slide(); + this.setPrompt('Which book is it?'); + this.audio.speak(LINES.choose); + this.state = 'choose'; this.busy = false; + } + _renderCards() { + this.$cart.innerHTML = this.order.cards.map((w, i) => '<div class="ss-book' + (i === this.sel ? ' sel' : '') + '" data-w="' + w.w + '" style="--c:' + BOX_COLORS[SS.hashStr(w.w) % BOX_COLORS.length] + '">' + + '<div class="ss-cover">' + img(w.art) + '</div><span class="ss-num">' + (i + 1) + '</span></div>').join(''); + } + _keyChoose(k) { + if (this.busy) return; + const n = this.order.cards.length; + if (k === 'ArrowLeft' || k === 'ArrowUp') { this.sel = (this.sel + n - 1) % n; this._renderCards(); this.audio.tick(); } + if (k === 'ArrowRight' || k === 'ArrowDown') { this.sel = (this.sel + 1) % n; this._renderCards(); this.audio.tick(); } + if (/^[1-9]$/.test(k) && +k <= n) { this.sel = +k - 1; this._renderCards(); this._confirmChoice(); } + if (k === 'Enter' || k === ' ') this._confirmChoice(); + } + async _confirmChoice() { + const o = this.order, chosen = o.cards[this.sel]; + this.busy = true; + const cardEls = Array.from(this.$cart.children); + if (chosen.w === o.word.w) { + cardEls[this.sel].classList.add('yes'); + this.audio.ding(); + await sleep(450); + this.checkout(); + return; + } + o.missed = true; o.wrongPicks += 1; + cardEls[this.sel].classList.add('no'); + this.audio.wrong(); + this.$customer.classList.remove('puzzled'); void this.$customer.offsetWidth; this.$customer.classList.add('puzzled'); + await this.audio.speak(pick(LINES.wrongBook) + ' That one is about a ' + chosen.w + '.', o.patron.voice); + if (o.wrongPicks === 2 && o.cards.length > 2) { + // Take the wrong book away so the next try is between two. + o.cards = o.cards.filter((c) => c.w !== chosen.w); + this.sel = 0; + this._renderCards(); + await this.audio.speak('Let me take that one away.', o.patron.voice); + } else { + cardEls[this.sel].classList.remove('no'); + } + await this.audio.speak('Read it again.'); + this.$finger.classList.add('show'); + const boxes = Array.from(this.$boxes.children); + await this.audio.playSegmented(o.units, { gap: 0.1, len: 0.25, onUnit: (i) => { this._flashBox(i, 300); this._moveFinger(boxes[i]); } }); + this.$finger.classList.remove('show'); + if (o.wrongPicks >= 3) { + // No fail state: after three tries the word is given and the book pointed out. + await this.audio.speak('It says'); + await this.audio.playWord(o.word.w); + const right = o.cards.findIndex((c) => c.w === o.word.w); + this.sel = right; this._renderCards(); + this.$cart.children[right].classList.add('point'); + } + this.busy = false; + } + + // Read mode success: stamp the book and hand it over. + async checkout() { + const o = this.order; + this.state = 'stamp'; this.busy = true; + this.setPrompt(''); + const right = o.cards.findIndex((c) => c.w === o.word.w); + const book = this.$cart.children[right]; + this.$cart.classList.add('picked'); + Array.from(this.$cart.children).forEach((b, i) => { if (i !== right) b.classList.add('gone'); }); + this.$boxes.querySelectorAll('.ss-box').forEach((b) => b.classList.add('done', 'reveal')); + await sleep(350); + // The stamp comes down on the cover. + this.$stamp.classList.add('over'); + await sleep(350); + this.$stamp.classList.add('slam'); + this.audio.bigStamp(); + if (book) book.classList.add('stamped'); + this.root.classList.remove('shake'); void this.root.offsetWidth; this.root.classList.add('shake'); + await sleep(500); + this.$stamp.classList.remove('over', 'slam'); + await this.audio.speak('Checked out!'); + if (book) book.classList.add('handed'); + this.audio.whoosh(); + await sleep(600); + this.$bubble.innerHTML = img(o.word.art); + this.$customer.classList.add('happy'); + await this.audio.speak('Yes! ' + o.word.w + '! ' + pick(LINES.happy), o.patron.voice); + this.serve(); + } + + // Return mode success: the book flies back to its shelf. + async shelve() { + const o = this.order; + this.state = 'stamp'; this.busy = true; + this.$bookOut.innerHTML = '<div class="ss-book stamped" style="--c:' + BOX_COLORS[SS.hashStr(o.word.w) % BOX_COLORS.length] + '"><div class="ss-cover">' + img(o.word.art) + '</div></div>'; + this.$bookOut.classList.add('show'); + this.$stamp.classList.add('over'); + await sleep(350); + this.$stamp.classList.add('slam'); + this.audio.bigStamp(); + await sleep(450); + this.$stamp.classList.remove('over', 'slam'); + this.$bookOut.classList.add('fly'); + this.audio.whoosh(); + await sleep(700); + this.$customer.classList.add('happy'); + await this.audio.speak('Back on the shelf! ' + pick(LINES.happy), o.patron.voice); + this.serve(); + } + + async serve() { + const o = this.order, p = this.profile; + this.state = 'serve'; this.busy = true; + this.audio.fanfare(); + this.fx('â', 68, 40); this.fx('â¤ď¸', 74, 44); this.fx('â', 62, 42); + + const wasMastered = SS.isMastered(SS.wordRecord(p, o.word.w)); + const rec = SS.recordOrder(p, o.word.w, !o.missed, this.now()); + this.booksToday += 1; + this.todayWords.push(o.word.w); + this.save(); + this._hud(); + + if (!wasMastered && SS.isMastered(rec)) { + this.toast('đ ' + esc(o.word.w) + ' is on your shelf!'); + this.audio.sparkle(); + await this.audio.speak('A new book for your shelf!'); + } + const fresh = this._applyLibrary(false); + for (const u of fresh) { + this.toast('đ The library got ' + u.label + '!'); + this.audio.fanfare(); + await this.audio.speak('The library got ' + u.label + '!'); + await sleep(600); + } + this.$customer.classList.remove('in'); this.$customer.classList.add('out'); + this.$bubble.classList.remove('show'); + await sleep(800); + if (this.booksToday >= SS.BOOKS_PER_DAY) this.dayEnd(); + else this.showQueue(); + } + + async dayEnd() { + this.state = 'dayend'; this.busy = false; + this.root.classList.add('ss-dusk'); + this._resetDesk(); + const pics = this.todayWords.map((w) => '<span class="ss-day-book">' + img(SS.WORD_BY_NAME[w].art) + '<i>' + esc(w) + '</i></span>').join(''); + const sounds = this.todaySounds.length ? '<p>New sounds: <b>' + this.todaySounds.join(', ') + '</b></p>' : ''; + this._panel('<h2>Closing time!</h2><div class="ss-summary">' + pics + '</div>' + sounds + + '<p>' + SS.masteredWords(this.profile).length + ' books on your shelf</p>' + + '<p><kbd>Enter</kbd> open the library again ¡ <kbd>âŤ</kbd> change librarian</p>'); + this.audio.fanfare(); + await this.audio.speak('The library is closed for today. Great reading, ' + this.profile.name + '! Press enter to open again, or take a break.'); + } + + newDay() { + this.$overlay.hidden = true; + this.root.classList.remove('ss-dusk'); + this.booksToday = 0; this.todayWords = []; this.todaySounds = []; + this._hud(); + this.showQueue(); + } + } + + document.addEventListener('DOMContentLoaded', () => { + const container = document.getElementById('game-container'); + root.__SS_GAME__ = new StoryStamp(container); + }); + root.StoryStamp = StoryStamp; +}(window)); diff --git a/games/story-stamp/index.html b/games/story-stamp/index.html @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Story Stamp â KGames</title> + <link rel="preconnect" href="https://fonts.googleapis.com"> + <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> + <link href="https://fonts.googleapis.com/css2?family=Fredoka:wght@400;600&display=swap" rel="stylesheet"> + <link rel="stylesheet" href="style.css?v=2"> +</head> +<body> + <nav><a href="../../index.html">← Back to KGames</a></nav> + <div id="audio-controls"> + <button id="btn-voice">Speech: ON</button> + <button id="btn-sfx">SFX: ON</button> + </div> + <div id="game-container"></div> + <div id="attribution"> + Pictures: <a href="https://github.com/microsoft/fluentui-emoji" target="_blank" rel="noopener">Fluent Emoji</a> © Microsoft, MIT ¡ + Sounds: <a href="https://commons.wikimedia.org/" target="_blank" rel="noopener">Wikimedia Commons</a> recordings, CC BY-SA ¡ + <a href="CREDITS.md" target="_blank" rel="noopener">full credits</a> + </div> + <script src="../../js/shared/game-shared.js"></script> + <script src="lib.js?v=2"></script> + <script src="assets/manifest.js?v=2"></script> + <script src="audio.js?v=2"></script> + <script src="game.js?v=2"></script> + <script src="../../js/shared/touch-controls.js"></script> + <script>KGames.initTouchControls({ layout: 'text-nav' });</script> +</body> +</html> diff --git a/games/story-stamp/lib.js b/games/story-stamp/lib.js @@ -0,0 +1,764 @@ +// Story Stamp â pure game logic (no DOM, no audio). Dual-export for unit tests. +// +// Contents: +// PHONEMES / GRAPHEMES / SEQUENCE â the synthetic-phonics teaching content +// TOPICS / WORDS â decodable book titles with a picture +// PATRONS â the library regulars (one per word, by hash) +// segmentation helpers â graphemes / phonemes / keys for a word +// scheduler â mastery tracking + spaced repetition +// the queue â who is waiting at the desk, and for what +// blend timeline â timing plan for sounding a word out +// profile store â on-device, multi-child progress +(function (root, factory) { + if (typeof module === 'object' && module.exports) module.exports = factory(); + else root.SS = factory(); +}(typeof self !== 'undefined' ? self : this, function () { + 'use strict'; + + const DAY = 24 * 60 * 60 * 1000; + const MIN = 60 * 1000; + + // ââ Sounds âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ + // type: 'stop' = short burst (never stretched), 'cont' = consonant that can + // be held, 'vowel'. stretch: the clip is a long sustained recording and the + // player may cut it to the length a blend pass asks for; a sound without it + // (a stop, a glide like /w/, a diphthong like /aĘ/) always plays whole. + // home: the grapheme that teaches the sound, for words whose spelling + // overrides a sound (book: 'oo' says /Ę/, which 'oo' also teaches). + const PHONEMES = { + s: { ipa: 's', type: 'cont', stretch: true }, + a: { ipa: 'ĂŚ', type: 'vowel', stretch: true }, + t: { ipa: 't', type: 'stop' }, + p: { ipa: 'p', type: 'stop' }, + i: { ipa: 'ÉŞ', type: 'vowel', stretch: true }, + n: { ipa: 'n', type: 'cont', stretch: true }, + m: { ipa: 'm', type: 'cont', stretch: true }, + d: { ipa: 'd', type: 'stop' }, + g: { ipa: 'g', type: 'stop' }, + o: { ipa: 'É', type: 'vowel', stretch: true }, + k: { ipa: 'k', type: 'stop' }, + e: { ipa: 'É', type: 'vowel', stretch: true }, + u: { ipa: 'Ę', type: 'vowel', stretch: true }, + r: { ipa: 'Éš', type: 'cont', stretch: true }, + h: { ipa: 'h', type: 'cont' }, + b: { ipa: 'b', type: 'stop' }, + f: { ipa: 'f', type: 'cont', stretch: true }, + l: { ipa: 'l', type: 'cont', stretch: true }, + j: { ipa: 'dĘ', type: 'stop' }, + v: { ipa: 'v', type: 'cont', stretch: true }, + w: { ipa: 'w', type: 'cont' }, + y: { ipa: 'j', type: 'cont' }, + z: { ipa: 'z', type: 'cont', stretch: true }, + sh: { ipa: 'Ę', type: 'cont', stretch: true }, + ch: { ipa: 'tĘ', type: 'stop' }, + th: { ipa: 'θ', type: 'cont', stretch: true }, + ng: { ipa: 'Ĺ', type: 'cont', stretch: true }, + ee: { ipa: 'iË', type: 'vowel', stretch: true }, + oo: { ipa: 'uË', type: 'vowel', stretch: true }, + uu: { ipa: 'Ę', type: 'vowel', stretch: true, home: 'oo' }, + oa: { ipa: 'oĘ', type: 'vowel' }, + ar: { ipa: 'ÉÉš', type: 'vowel' }, + or: { ipa: 'ÉÉš', type: 'vowel' }, + er: { ipa: 'É', type: 'vowel', stretch: true }, + ai: { ipa: 'eÉŞ', type: 'vowel' }, + igh: { ipa: 'aÉŞ', type: 'vowel' }, + ow: { ipa: 'aĘ', type: 'vowel' }, + oi: { ipa: 'ÉÉŞ', type: 'vowel' }, + }; + + // Spelling unit -> phoneme ids. Doubled consonants (ll, ss, gg, ff, zz, cc, + // bb, dd, mm, nn, pp, rr, tt) are handled by graphemePhonemes(). + const GRAPHEMES = { + s: ['s'], a: ['a'], t: ['t'], p: ['p'], i: ['i'], n: ['n'], m: ['m'], d: ['d'], + g: ['g'], o: ['o'], c: ['k'], k: ['k'], ck: ['k'], e: ['e'], u: ['u'], r: ['r'], + h: ['h'], b: ['b'], f: ['f'], l: ['l'], j: ['j'], v: ['v'], w: ['w'], x: ['k', 's'], + y: ['y'], z: ['z'], qu: ['k', 'w'], sh: ['sh'], ch: ['ch'], tch: ['ch'], th: ['th'], + ng: ['ng'], wh: ['w'], ee: ['ee'], oo: ['oo'], oa: ['oa'], ar: ['ar'], or: ['or'], + oar: ['or'], er: ['er'], ai: ['ai'], ay: ['ai'], igh: ['igh'], ie: ['igh'], + ow: ['ow'], ou: ['ow'], oi: ['oi'], oy: ['oi'], + }; + // Alternative spellings map to the grapheme that teaches the sound. + const BASE = { tch: 'ch', wh: 'w', oar: 'or', ay: 'ai', ie: 'igh', ou: 'ow', oy: 'oi' }; + + // Teaching order (Letters and Sounds phase 2 -> 3, continuants early). + const SEQUENCE = [ + 's', 'a', 't', 'p', 'i', 'n', // the classic opening six + 'm', 'd', 'g', 'o', 'c', 'k', 'ck', 'e', 'u', 'r', 'h', 'b', 'f', 'l', + 'j', 'v', 'w', 'x', 'y', 'z', 'qu', + 'sh', 'ch', 'th', 'ng', 'ee', 'oo', 'oa', 'ar', 'or', 'er', 'ai', 'igh', 'ow', 'oi', + ]; + const STARTING_GRAPHEMES = 6; // s a t p i n are known from the first visit + + // ââ Topics âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ + // What the kids at the desk ask for. art: Fluent Emoji asset name. + const TOPICS = [ + { id: 'animals', name: 'animals', art: 'Paw prints', color: '#F4A261' }, + { id: 'bugs', name: 'bugs', art: 'Bug', color: '#8AC926' }, + { id: 'sea', name: 'the sea', art: 'Water wave', color: '#1D7CF2' }, + { id: 'space', name: 'space', art: 'Rocket', color: '#6A4C93' }, + { id: 'dinos', name: 'dinosaurs',art: 'T-rex', color: '#2A9D8F' }, + { id: 'wheels', name: 'things that go', art: 'Fire engine', color: '#E63946' }, + { id: 'food', name: 'food', art: 'Pizza', color: '#FF924C' }, + { id: 'magic', name: 'magic', art: 'Crystal ball', color: '#9B5DE5' }, + { id: 'play', name: 'playtime', art: 'Teddy bear', color: '#FFD23F' }, + { id: 'silly', name: 'silly stuff', art: 'Zany face', color: '#FF5DA2' }, + { id: 'outside', name: 'outdoors', art: 'Deciduous tree', color: '#06A77D' }, + { id: 'home', name: 'home', art: 'House', color: '#2EC4B6' }, + ]; + const TOPIC_BY_ID = {}; + TOPICS.forEach((t) => { TOPIC_BY_ID[t.id] = t; }); + const ANY_TOPIC = { id: 'any', name: 'a good book', art: 'Books', color: '#7A6C5D' }; + + // ââ Words ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ + // g: graphemes joined by '-'; art: Fluent Emoji asset (3D); t: topics; + // p: phoneme override (space-separated) when a grapheme is ambiguous + // (book: 'oo' -> /Ę/, robot: 'o' -> /oĘ/). + const WORDS = [ + // s a t p i n + { w: 'ant', g: 'a-n-t', art: 'Ant', t: ['bugs', 'animals'] }, + { w: 'pants', g: 'p-a-n-t-s', art: 'Jeans', t: ['play', 'home'] }, + { w: 'nap', g: 'n-a-p', art: 'Sleeping face', t: ['silly', 'home'] }, + { w: 'tap', g: 't-a-p', art: 'Potable water', t: ['home'] }, + { w: 'pin', g: 'p-i-n', art: 'Bowling', t: ['play'] }, + { w: 'snap', g: 's-n-a-p', art: 'Crocodile', t: ['animals', 'silly'] }, + { w: 'pan', g: 'p-a-n', art: 'Cooking', t: ['home', 'food'] }, + { w: 'tin', g: 't-i-n', art: 'Canned food', t: ['food', 'home'] }, + { w: 'spin', g: 's-p-i-n', art: 'Cyclone', t: ['silly', 'play'] }, + // + m d g o c k ck + { w: 'map', g: 'm-a-p', art: 'World map', t: ['outside', 'play'] }, + { w: 'mad', g: 'm-a-d', art: 'Angry face', t: ['silly'] }, + { w: 'sad', g: 's-a-d', art: 'Crying face', t: ['silly'] }, + { w: 'dad', g: 'd-a-d', art: 'Man/Default', t: ['home'] }, + { w: 'mom', g: 'm-o-m', art: 'Woman/Default', t: ['home'] }, + { w: 'dog', g: 'd-o-g', art: 'Dog', t: ['animals', 'home'] }, + { w: 'pig', g: 'p-i-g', art: 'Pig', t: ['animals'] }, + { w: 'pot', g: 'p-o-t', art: 'Pot of food', t: ['home', 'food'] }, + { w: 'cat', g: 'c-a-t', art: 'Cat', t: ['animals', 'home'] }, + { w: 'cap', g: 'c-a-p', art: 'Billed cap', t: ['play', 'home'] }, + { w: 'can', g: 'c-a-n', art: 'Canned food', t: ['food'] }, + { w: 'kid', g: 'k-i-d', art: 'Child/Default', t: ['home', 'play'] }, + { w: 'sock', g: 's-o-ck', art: 'Socks', t: ['home', 'silly'] }, + { w: 'camp', g: 'c-a-m-p', art: 'Camping', t: ['outside', 'play'] }, + { w: 'dig', g: 'd-i-g', art: 'Pick', t: ['dinos', 'outside'] }, + { w: 'mask', g: 'm-a-s-k', art: 'Performing arts', t: ['play', 'magic'] }, + { w: 'moo', g: 'm-oo', art: 'Cow face', t: ['silly', 'animals'] }, + // + e u + { w: 'pen', g: 'p-e-n', art: 'Pen', t: ['home'] }, + { w: 'ten', g: 't-e-n', art: 'Keycap 10', t: ['play'] }, + { w: 'net', g: 'n-e-t', art: 'Goal net', t: ['play', 'sea'] }, + { w: 'egg', g: 'e-gg', art: 'Egg', t: ['food', 'dinos', 'bugs'] }, + { w: 'gem', g: 'g-e-m', art: 'Gem stone', t: ['magic'] }, + { w: 'tent', g: 't-e-n-t', art: 'Tent', t: ['outside', 'play'] }, + { w: 'nest', g: 'n-e-s-t', art: 'Nest with eggs', t: ['dinos', 'animals', 'outside'] }, + { w: 'sun', g: 's-u-n', art: 'Sun', t: ['space', 'outside'] }, + { w: 'cup', g: 'c-u-p', art: 'Cup with straw', t: ['home', 'food'] }, + { w: 'nut', g: 'n-u-t', art: 'Peanuts', t: ['food'] }, + { w: 'cut', g: 'c-u-t', art: 'Scissors', t: ['home', 'play'] }, + { w: 'mug', g: 'm-u-g', art: 'Hot beverage', t: ['home', 'food'] }, + { w: 'duck', g: 'd-u-ck', art: 'Duck', t: ['animals', 'outside'] }, + { w: 'bug', g: 'b-u-g', art: 'Bug', t: ['bugs', 'animals'] }, + { w: 'sunset', g: 's-u-n-s-e-t', art: 'Sunset', t: ['outside', 'space'] }, + { w: 'pumpkin', g: 'p-u-m-p-k-i-n', art: 'Jack-o-lantern', t: ['food', 'magic'] }, + { w: 'insect', g: 'i-n-s-e-c-t', art: 'Beetle', t: ['bugs'] }, + { w: 'magnet', g: 'm-a-g-n-e-t', art: 'Magnet', t: ['play', 'magic'] }, + { w: 'cactus', g: 'c-a-c-t-u-s', art: 'Cactus', t: ['outside'] }, + { w: 'comet', g: 'c-o-m-e-t', art: 'Comet', t: ['space'] }, + { w: 'tennis', g: 't-e-nn-i-s', art: 'Tennis', t: ['play'] }, + { w: 'ticket', g: 't-i-ck-e-t', art: 'Ticket', t: ['play', 'wheels'] }, + { w: 'kitten', g: 'k-i-tt-e-n', art: 'Cat face', t: ['animals', 'home'] }, + { w: 'octopus', g: 'o-c-t-o-p-u-s', art: 'Octopus', t: ['sea', 'animals'] }, + // + r h b f l + { w: 'rat', g: 'r-a-t', art: 'Rat', t: ['animals'] }, + { w: 'ram', g: 'r-a-m', art: 'Ram', t: ['animals'] }, + { w: 'run', g: 'r-u-n', art: 'Person running/Default', t: ['play'] }, + { w: 'rock', g: 'r-o-ck', art: 'Rock', t: ['outside', 'space', 'dinos'] }, + { w: 'crab', g: 'c-r-a-b', art: 'Crab', t: ['sea', 'animals'] }, + { w: 'drum', g: 'd-r-u-m', art: 'Drum', t: ['play'] }, + { w: 'truck', g: 't-r-u-ck', art: 'Delivery truck', t: ['wheels'] }, + { w: 'dress', g: 'd-r-e-ss', art: 'Dress', t: ['play', 'home'] }, + { w: 'rex', g: 'r-e-x', art: 'T-rex', t: ['dinos'] }, + { w: 'rocket', g: 'r-o-ck-e-t', art: 'Rocket', t: ['space', 'wheels'] }, + { w: 'carrot', g: 'c-a-rr-o-t', art: 'Carrot', t: ['food'] }, + { w: 'rabbit', g: 'r-a-bb-i-t', art: 'Rabbit', t: ['animals'] }, + { w: 'parrot', g: 'p-a-rr-o-t', art: 'Parrot', t: ['animals'] }, + { w: 'hat', g: 'h-a-t', art: 'Top hat', t: ['play', 'magic', 'home'] }, + { w: 'hen', g: 'h-e-n', art: 'Chicken', t: ['animals'] }, + { w: 'hot', g: 'h-o-t', art: 'Hot face', t: ['silly'] }, + { w: 'hug', g: 'h-u-g', art: 'People hugging', t: ['silly', 'home'] }, + { w: 'hut', g: 'h-u-t', art: 'Hut', t: ['outside'] }, + { w: 'ham', g: 'h-a-m', art: 'Cut of meat', t: ['food'] }, + { w: 'hand', g: 'h-a-n-d', art: 'Raised hand/Default', t: ['home', 'silly'] }, + { w: 'helmet', g: 'h-e-l-m-e-t', art: 'Rescue workers helmet', t: ['wheels', 'play'] }, + { w: 'hotdog', g: 'h-o-t-d-o-g', art: 'Hot dog', t: ['food', 'silly'] }, + { w: 'bus', g: 'b-u-s', art: 'Bus', t: ['wheels'] }, + { w: 'bed', g: 'b-e-d', art: 'Bed', t: ['home'] }, + { w: 'bag', g: 'b-a-g', art: 'Handbag', t: ['home'] }, + { w: 'bat', g: 'b-a-t', art: 'Bat', t: ['animals', 'magic'] }, + { w: 'bin', g: 'b-i-n', art: 'Wastebasket', t: ['home'] }, + { w: 'cab', g: 'c-a-b', art: 'Taxi', t: ['wheels'] }, + { w: 'bell', g: 'b-e-ll', art: 'Bell', t: ['home', 'play'] }, + { w: 'brick', g: 'b-r-i-ck', art: 'Brick', t: ['play', 'home'] }, + { w: 'box', g: 'b-o-x', art: 'Package', t: ['home'] }, + { w: 'basket', g: 'b-a-s-k-e-t', art: 'Basket', t: ['home'] }, + { w: 'bucket', g: 'b-u-ck-e-t', art: 'Bucket', t: ['play', 'sea'] }, + { w: 'blossom', g: 'b-l-o-ss-o-m', art: 'Cherry blossom', t: ['outside'] }, + { w: 'big', g: 'b-i-g', art: 'Sauropod', t: ['dinos'] }, + { w: 'cub', g: 'c-u-b', art: 'Bear', t: ['animals'] }, + { w: 'gift', g: 'g-i-f-t', art: 'Wrapped gift', t: ['play', 'home'] }, + { w: 'frog', g: 'f-r-o-g', art: 'Frog', t: ['animals', 'outside'] }, + { w: 'fog', g: 'f-o-g', art: 'Fog', t: ['outside'] }, + { w: 'flag', g: 'f-l-a-g', art: 'Triangular flag', t: ['play'] }, + { w: 'fossil', g: 'f-o-ss-i-l', art: 'Bone', t: ['dinos'] }, + { w: 'muffin', g: 'm-u-ff-i-n', art: 'Cupcake', t: ['food'] }, + { w: 'log', g: 'l-o-g', art: 'Wood', t: ['outside'] }, + { w: 'lock', g: 'l-o-ck', art: 'Locked', t: ['home'] }, + { w: 'clock', g: 'c-l-o-ck', art: 'Alarm clock', t: ['home'] }, + { w: 'plug', g: 'p-l-u-g', art: 'Electric plug', t: ['home'] }, + { w: 'milk', g: 'm-i-l-k', art: 'Glass of milk', t: ['food', 'home'] }, + { w: 'plant', g: 'p-l-a-n-t', art: 'Potted plant', t: ['home', 'outside'] }, + { w: 'sled', g: 's-l-e-d', art: 'Sled', t: ['play', 'wheels'] }, + { w: 'lemon', g: 'l-e-m-o-n', art: 'Lemon', t: ['food'] }, + { w: 'melon', g: 'm-e-l-o-n', art: 'Melon', t: ['food'] }, + { w: 'lollipop', g: 'l-o-ll-i-p-o-p', art: 'Lollipop', t: ['food', 'silly'] }, + { w: 'doll', g: 'd-o-ll', art: 'Nesting dolls', t: ['play'] }, + { w: 'elf', g: 'e-l-f', art: 'Person elf/Default', t: ['magic'] }, + { w: 'lamp', g: 'l-a-m-p', art: 'Light bulb', t: ['home'] }, + { w: 'laptop', g: 'l-a-p-t-o-p', art: 'Laptop', t: ['home'] }, + { w: 'camel', g: 'c-a-m-e-l', art: 'Camel', t: ['animals'] }, + { w: 'lips', g: 'l-i-p-s', art: 'Mouth', t: ['silly', 'home'] }, + // + j v w x y z qu + { w: 'jet', g: 'j-e-t', art: 'Small airplane', t: ['wheels', 'space'] }, + { w: 'jam', g: 'j-a-m', art: 'Jar', t: ['food'] }, + { w: 'jump', g: 'j-u-m-p', art: 'Person cartwheeling/Default', t: ['play', 'silly'] }, + { w: 'van', g: 'v-a-n', art: 'Minibus', t: ['wheels'] }, + { w: 'vest', g: 'v-e-s-t', art: 'Safety vest', t: ['home', 'wheels'] }, + { w: 'web', g: 'w-e-b', art: 'Spider web', t: ['bugs', 'magic'] }, + { w: 'swim', g: 's-w-i-m', art: 'Person swimming/Default', t: ['sea', 'play'] }, + { w: 'wig', g: 'w-i-g', art: 'Womans hat', t: ['silly', 'magic'] }, + { w: 'window', g: 'w-i-n-d-ow', art: 'Window', t: ['home'], p: 'w i n d oa' }, + { w: 'witch', g: 'w-i-tch', art: 'Woman mage/Default', t: ['magic'] }, + { w: 'sandwich', g: 's-a-n-d-w-i-ch', art: 'Sandwich', t: ['food'] }, + { w: 'fox', g: 'f-o-x', art: 'Fox', t: ['animals'] }, + { w: 'six', g: 's-i-x', art: 'Keycap 6', t: ['play'] }, + { w: 'ax', g: 'a-x', art: 'Axe', t: ['outside'] }, + { w: 'ox', g: 'o-x', art: 'Ox', t: ['animals'] }, + { w: 'yum', g: 'y-u-m', art: 'Face savoring food', t: ['silly', 'food'] }, + { w: 'yuck', g: 'y-u-ck', art: 'Nauseated face', t: ['silly'] }, + { w: 'yoyo', g: 'y-o-y-o', art: 'Yo-yo', t: ['play'], p: 'y oa y oa' }, + { w: 'zap', g: 'z-a-p', art: 'High voltage', t: ['magic', 'silly'] }, + { w: 'zip', g: 'z-i-p', art: 'Zipper-mouth face', t: ['silly', 'home'] }, + { w: 'buzz', g: 'b-u-zz', art: 'Honeybee', t: ['bugs', 'silly'] }, + { w: 'quiz', g: 'qu-i-z', art: 'Red question mark', t: ['play', 'silly'] }, + { w: 'squid', g: 's-qu-i-d', art: 'Squid', t: ['sea', 'animals'] }, + { w: 'quack', g: 'qu-a-ck', art: 'Duck', t: ['silly', 'animals'] }, + // + sh ch th ng + { w: 'ship', g: 'sh-i-p', art: 'Ship', t: ['sea', 'wheels'] }, + { w: 'fish', g: 'f-i-sh', art: 'Fish', t: ['sea', 'animals', 'food'] }, + { w: 'shell', g: 'sh-e-ll', art: 'Spiral shell', t: ['sea'] }, + { w: 'dish', g: 'd-i-sh', art: 'Fork and knife with plate', t: ['home', 'food'] }, + { w: 'brush', g: 'b-r-u-sh', art: 'Toothbrush', t: ['home'] }, + { w: 'shrimp', g: 'sh-r-i-m-p', art: 'Shrimp', t: ['sea', 'food'] }, + { w: 'mushroom', g: 'm-u-sh-r-oo-m', art: 'Mushroom', t: ['outside', 'magic', 'food'] }, + { w: 'chick', g: 'ch-i-ck', art: 'Baby chick', t: ['animals'] }, + { w: 'hatch', g: 'h-a-tch', art: 'Hatching chick', t: ['dinos', 'animals'] }, + { w: 'chips', g: 'ch-i-p-s', art: 'French fries', t: ['food'] }, + { w: 'chicken', g: 'ch-i-ck-e-n', art: 'Poultry leg', t: ['food'] }, + { w: 'lunch', g: 'l-u-n-ch', art: 'Bento box', t: ['food'] }, + { w: 'chest', g: 'ch-e-s-t', art: 'Card file box', t: ['magic', 'home'] }, + { w: 'bath', g: 'b-a-th', art: 'Bathtub', t: ['home', 'silly'] }, + { w: 'moth', g: 'm-o-th', art: 'Butterfly', t: ['bugs'] }, + { w: 'thumb', g: 'th-u-m-b', art: 'Thumbs up/Default', t: ['silly', 'home'], p: 'th u m -' }, + { w: 'ring', g: 'r-i-ng', art: 'Ring', t: ['magic', 'home'] }, + { w: 'king', g: 'k-i-ng', art: 'Prince/Default', t: ['magic'] }, + { w: 'wing', g: 'w-i-ng', art: 'Wing', t: ['animals', 'wheels'] }, + { w: 'bang', g: 'b-a-ng', art: 'Collision', t: ['silly'] }, + { w: 'sing', g: 's-i-ng', art: 'Microphone', t: ['play', 'silly'] }, + { w: 'hiss', g: 'h-i-ss', art: 'Snake', t: ['animals', 'silly'] }, + { w: 'skunk', g: 's-k-u-n-k', art: 'Skunk', t: ['animals', 'silly'] }, + { w: 'mammoth', g: 'm-a-mm-o-th', art: 'Mammoth', t: ['dinos', 'animals'] }, + { w: 'dragon', g: 'd-r-a-g-o-n', art: 'Dragon', t: ['magic', 'dinos'] }, + { w: 'goblin', g: 'g-o-b-l-i-n', art: 'Goblin', t: ['magic'] }, + { w: 'magic', g: 'm-a-g-i-c', art: 'Magic wand', t: ['magic'] }, + { w: 'sloth', g: 's-l-o-th', art: 'Sloth', t: ['animals'], p: 's l oa th' }, + { w: 'hippo', g: 'h-i-pp-o', art: 'Hippopotamus', t: ['animals'], p: 'h i p oa' }, + { w: 'flamingo', g: 'f-l-a-m-i-ng-o', art: 'Flamingo', t: ['animals'], p: 'f l a m i ng oa' }, + { w: 'mango', g: 'm-a-ng-o', art: 'Mango', t: ['food'], p: 'm a ng oa' }, + { w: 'robot', g: 'r-o-b-o-t', art: 'Robot', t: ['space', 'magic', 'play'], p: 'r oa b o t' }, + { w: 'pickup', g: 'p-i-ck-u-p', art: 'Pickup truck', t: ['wheels'] }, + { w: 'tram', g: 't-r-a-m', art: 'Tram', t: ['wheels'] }, + { w: 'planet', g: 'p-l-a-n-e-t', art: 'Ringed planet', t: ['space'] }, + { w: 'lizard', g: 'l-i-z-ar-d', art: 'Lizard', t: ['animals', 'dinos'] }, + // + ee oo oa + { w: 'bee', g: 'b-ee', art: 'Honeybee', t: ['bugs', 'animals'] }, + { w: 'tree', g: 't-r-ee', art: 'Deciduous tree', t: ['outside'] }, + { w: 'feet', g: 'f-ee-t', art: 'Foot/Default', t: ['home', 'silly'] }, + { w: 'sheep', g: 'sh-ee-p', art: 'Ewe', t: ['animals'] }, + { w: 'teeth', g: 't-ee-th', art: 'Tooth', t: ['home', 'silly'] }, + { w: 'seed', g: 's-ee-d', art: 'Seedling', t: ['outside'] }, + { w: 'jeep', g: 'j-ee-p', art: 'Sport utility vehicle', t: ['wheels'] }, + { w: 'queen', g: 'qu-ee-n', art: 'Princess/Default', t: ['magic'] }, + { w: 'deer', g: 'd-ee-r', art: 'Deer', t: ['animals', 'outside'] }, + { w: 'sleep', g: 's-l-ee-p', art: 'Sleeping face', t: ['home', 'silly'] }, + { w: 'speedboat',g: 's-p-ee-d-b-oa-t',art: 'Speedboat', t: ['sea', 'wheels'] }, + { w: 'wheel', g: 'wh-ee-l', art: 'Wheel', t: ['wheels'] }, + { w: 'moon', g: 'm-oo-n', art: 'Crescent moon', t: ['space', 'outside'] }, + { w: 'boot', g: 'b-oo-t', art: 'Hiking boot', t: ['play', 'outside'] }, + { w: 'spoon', g: 's-p-oo-n', art: 'Spoon', t: ['home', 'food'] }, + { w: 'balloon', g: 'b-a-ll-oo-n', art: 'Balloon', t: ['play', 'silly'] }, + { w: 'raccoon', g: 'r-a-cc-oo-n', art: 'Raccoon', t: ['animals'] }, + { w: 'boo', g: 'b-oo', art: 'Ghost', t: ['magic', 'silly'] }, + { w: 'poop', g: 'p-oo-p', art: 'Pile of poo', t: ['silly'] }, + { w: 'boom', g: 'b-oo-m', art: 'Collision', t: ['silly', 'space'] }, + { w: 'scooter', g: 's-c-oo-t-er', art: 'Kick scooter', t: ['wheels', 'play'] }, + { w: 'rooster', g: 'r-oo-s-t-er', art: 'Rooster', t: ['animals'] }, + { w: 'book', g: 'b-oo-k', art: 'Open book', t: ['home', 'play'], p: 'b uu k' }, + { w: 'hook', g: 'h-oo-k', art: 'Hook', t: ['sea'], p: 'h uu k' }, + { w: 'foot', g: 'f-oo-t', art: 'Foot/Default', t: ['home', 'silly'], p: 'f uu t' }, + { w: 'cook', g: 'c-oo-k', art: 'Cook/Default', t: ['food', 'home'], p: 'k uu k' }, + { w: 'woof', g: 'w-oo-f', art: 'Dog face', t: ['silly', 'animals'], p: 'w uu f' }, + { w: 'boat', g: 'b-oa-t', art: 'Motor boat', t: ['sea', 'wheels'] }, + { w: 'goat', g: 'g-oa-t', art: 'Goat', t: ['animals'] }, + { w: 'coat', g: 'c-oa-t', art: 'Coat', t: ['play', 'home'] }, + { w: 'soap', g: 's-oa-p', art: 'Soap', t: ['home'] }, + { w: 'road', g: 'r-oa-d', art: 'Motorway', t: ['wheels', 'outside'] }, + { w: 'toast', g: 't-oa-s-t', art: 'Bread', t: ['food'] }, + { w: 'toad', g: 't-oa-d', art: 'Frog', t: ['animals', 'outside'] }, + { w: 'sailboat', g: 's-ai-l-b-oa-t', art: 'Sailboat', t: ['sea', 'wheels'] }, + // + ar or er + { w: 'star', g: 's-t-ar', art: 'Star', t: ['space', 'magic'] }, + { w: 'car', g: 'c-ar', art: 'Automobile', t: ['wheels'] }, + { w: 'cart', g: 'c-ar-t', art: 'Shopping cart', t: ['wheels', 'home'] }, + { w: 'shark', g: 'sh-ar-k', art: 'Shark', t: ['sea', 'animals'] }, + { w: 'park', g: 'p-ar-k', art: 'National park', t: ['outside'] }, + { w: 'jar', g: 'j-ar', art: 'Jar', t: ['home', 'food'] }, + { w: 'arm', g: 'ar-m', art: 'Flexed biceps/Default', t: ['silly', 'home'] }, + { w: 'art', g: 'ar-t', art: 'Artist palette', t: ['play'] }, + { w: 'garlic', g: 'g-ar-l-i-c', art: 'Garlic', t: ['food'] }, + { w: 'tractor', g: 't-r-a-c-t-or', art: 'Tractor', t: ['wheels'] }, + { w: 'scarf', g: 's-c-ar-f', art: 'Scarf', t: ['play', 'home'] }, + { w: 'target', g: 't-ar-g-e-t', art: 'Bullseye', t: ['play'] }, + { w: 'corn', g: 'c-or-n', art: 'Ear of corn', t: ['food'] }, + { w: 'horn', g: 'h-or-n', art: 'Postal horn', t: ['play', 'wheels'] }, + { w: 'fork', g: 'f-or-k', art: 'Fork and knife', t: ['home', 'food'] }, + { w: 'storm', g: 's-t-or-m', art: 'Cloud with lightning and rain', t: ['outside'] }, + { w: 'popcorn', g: 'p-o-p-c-or-n', art: 'Popcorn', t: ['food', 'silly'] }, + { w: 'roar', g: 'r-oar', art: 'Lion', t: ['animals', 'dinos', 'silly'] }, + { w: 'doctor', g: 'd-o-c-t-or', art: 'Health worker/Default', t: ['home'] }, + { w: 'monster', g: 'm-o-n-s-t-er', art: 'Alien monster', t: ['magic', 'silly', 'space'] }, + { w: 'hammer', g: 'h-a-mm-er', art: 'Hammer', t: ['home'] }, + { w: 'ladder', g: 'l-a-dd-er', art: 'Ladder', t: ['home', 'wheels'] }, + { w: 'butter', g: 'b-u-tt-er', art: 'Butter', t: ['food'] }, + { w: 'pepper', g: 'p-e-pp-er', art: 'Hot pepper', t: ['food'] }, + { w: 'hamster', g: 'h-a-m-s-t-er', art: 'Hamster', t: ['animals', 'home'] }, + { w: 'lobster', g: 'l-o-b-s-t-er', art: 'Lobster', t: ['sea', 'food'] }, + { w: 'otter', g: 'o-tt-er', art: 'Otter', t: ['animals', 'sea'] }, + { w: 'thunder', g: 'th-u-n-d-er', art: 'Cloud with lightning', t: ['outside'] }, + { w: 'soccer', g: 's-o-cc-er', art: 'Soccer ball', t: ['play'] }, + { w: 'helicopter', g: 'h-e-l-i-c-o-p-t-er', art: 'Helicopter', t: ['wheels'] }, + { w: 'printer', g: 'p-r-i-n-t-er', art: 'Printer', t: ['home'] }, + { w: 'lantern', g: 'l-a-n-t-er-n', art: 'Red paper lantern', t: ['magic', 'home'] }, + { w: 'oyster', g: 'oy-s-t-er', art: 'Oyster', t: ['sea', 'food'] }, + // + ai igh ow oi + { w: 'rain', g: 'r-ai-n', art: 'Cloud with rain', t: ['outside'] }, + { w: 'train', g: 't-r-ai-n', art: 'Locomotive', t: ['wheels'] }, + { w: 'snail', g: 's-n-ai-l', art: 'Snail', t: ['bugs', 'animals'] }, + { w: 'chain', g: 'ch-ai-n', art: 'Chains', t: ['home'] }, + { w: 'brain', g: 'b-r-ai-n', art: 'Brain', t: ['silly', 'magic'] }, + { w: 'crayon', g: 'c-r-ay-o-n', art: 'Crayon', t: ['play'] }, + { w: 'rainbow', g: 'r-ai-n-b-ow', art: 'Rainbow', t: ['outside', 'magic'], p: 'r ai n b oa' }, + { w: 'paint', g: 'p-ai-n-t', art: 'Paintbrush', t: ['play'] }, + { w: 'night', g: 'n-igh-t', art: 'Night with stars', t: ['space', 'outside'] }, + { w: 'light', g: 'l-igh-t', art: 'Light bulb', t: ['home'] }, + { w: 'lightning',g: 'l-igh-t-n-i-ng', art: 'High voltage', t: ['outside', 'magic'] }, + { w: 'flashlight', g: 'f-l-a-sh-l-igh-t', art: 'Flashlight', t: ['outside', 'magic'] }, + { w: 'pie', g: 'p-ie', art: 'Pie', t: ['food'] }, + { w: 'cow', g: 'c-ow', art: 'Cow', t: ['animals'] }, + { w: 'owl', g: 'ow-l', art: 'Owl', t: ['animals', 'outside'] }, + { w: 'clown', g: 'c-l-ow-n', art: 'Clown face', t: ['silly', 'magic'] }, + { w: 'crown', g: 'c-r-ow-n', art: 'Crown', t: ['magic'] }, + { w: 'cloud', g: 'c-l-ou-d', art: 'Cloud', t: ['outside', 'space'] }, + { w: 'mouth', g: 'm-ou-th', art: 'Mouth', t: ['silly', 'home'] }, + { w: 'cowboy', g: 'c-ow-b-oy', art: 'Cowboy hat face', t: ['play', 'magic'] }, + { w: 'snow', g: 's-n-ow', art: 'Snowflake', t: ['outside'], p: 's n oa' }, + { w: 'coin', g: 'c-oi-n', art: 'Coin', t: ['home', 'magic'] }, + { w: 'boy', g: 'b-oy', art: 'Boy/Default', t: ['home'] }, + { w: 'toy', g: 't-oy', art: 'Teddy bear', t: ['play'] }, + { w: 'oink', g: 'oi-n-k', art: 'Pig face', t: ['silly', 'animals'] }, + { w: 'toilet', g: 't-oi-l-e-t', art: 'Toilet', t: ['home', 'silly'] }, + ]; + const WORD_BY_NAME = {}; + WORDS.forEach((w) => { WORD_BY_NAME[w.w] = w; }); + + // ââ Patrons ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ + // The island kids who come to the desk. voice: {pitch, rate} for speech + // synthesis so each regular sounds different. art: Fluent Emoji asset. + const PATRONS = [ + { id: 'ember', name: 'Ember', kind: 'a little dragon', art: 'Dragon face', color: '#E63946', voice: { pitch: 0.8, rate: 0.95 } }, + { id: 'pip', name: 'Pip', kind: 'a parrot', art: 'Parrot', color: '#2EC4B6', voice: { pitch: 1.6, rate: 1.15 } }, + { id: 'reggie', name: 'Reggie', kind: 'a polite lizard', art: 'Lizard', color: '#1D7CF2', voice: { pitch: 0.9, rate: 0.85 } }, + { id: 'clawdia', name: 'Clawdia', kind: 'a coconut crab', art: 'Crab', color: '#F4A261', voice: { pitch: 0.7, rate: 1.0 } }, + { id: 'otto', name: 'Otto', kind: 'an octopus', art: 'Octopus', color: '#9B5DE5', voice: { pitch: 1.2, rate: 0.95 } }, + { id: 'marina', name: 'Marina', kind: 'a mermaid', art: 'Person merpeople/Default', color: '#2EC4B6', voice: { pitch: 1.3, rate: 1.0 } }, + { id: 'zephyr', name: 'Zephyr', kind: 'a genie', art: 'Person genie', color: '#1D7CF2', voice: { pitch: 1.0, rate: 1.1 } }, + { id: 'shelby', name: 'Shelby', kind: 'a turtle', art: 'Turtle', color: '#06A77D', voice: { pitch: 0.75, rate: 0.8 } }, + { id: 'flossie', name: 'Flossie', kind: 'a flamingo', art: 'Flamingo', color: '#FF7AA2', voice: { pitch: 1.5, rate: 1.05 } }, + { id: 'hopper', name: 'Hopper', kind: 'a frog', art: 'Frog', color: '#8AC926', voice: { pitch: 1.1, rate: 1.2 } }, + { id: 'hoot', name: 'Hoot', kind: 'an owl', art: 'Owl', color: '#FFD23F', voice: { pitch: 0.85, rate: 0.9 } }, + { id: 'splash', name: 'Splash', kind: 'a dolphin', art: 'Dolphin', color: '#1D7CF2', voice: { pitch: 1.4, rate: 1.1 } }, + { id: 'nova', name: 'Nova', kind: 'a unicorn', art: 'Unicorn', color: '#FF5DA2', voice: { pitch: 1.35, rate: 1.0 } }, + { id: 'bolt', name: 'Bolt', kind: 'a robot', art: 'Robot', color: '#7A6C5D', voice: { pitch: 0.6, rate: 1.05 } }, + { id: 'wisp', name: 'Wisp', kind: 'a friendly ghost', art: 'Ghost', color: '#9B5DE5', voice: { pitch: 1.25, rate: 0.9 } }, + { id: 'koko', name: 'Koko', kind: 'a koala', art: 'Koala', color: '#7A6C5D', voice: { pitch: 1.05, rate: 0.85 } }, + ]; + + // Pictures the scene itself uses (window, decor, the reading finger), on top of the words'. + const UI_ART = ['Bird', 'Sailboat', 'Palm tree', 'Backhand index pointing up/Default', 'Potted plant', 'Light bulb', 'Fish', 'Tropical fish', 'Cat', 'Globe showing americas', 'Telescope', 'Sauropod', 'Rocket', 'Penguin', 'Koala', 'Panda', 'Lion', 'Tiger face', 'Bear', 'Alien', 'Child/Default', 'Girl/Default', 'Boy/Default']; + + const AVATARS = ['Child/Default', 'Girl/Default', 'Boy/Default', 'Cat face', 'Dog face', 'Rabbit face', 'Panda', 'Lion', 'Tiger face', 'Penguin', 'Bear', 'Alien', 'Unicorn', 'Dragon face']; + + // ââ Deterministic hashing (same word -> same regular every time) âââââââ + function hashStr(s) { + let h = 2166136261; + for (let i = 0; i < s.length; i++) { h ^= s.charCodeAt(i); h = Math.imul(h, 16777619) >>> 0; } + return h >>> 0; + } + function patronFor(word) { return PATRONS[hashStr(word) % PATRONS.length]; } + + function mulberry32(seed) { + let a = seed >>> 0; + return function () { + a = (a + 0x6D2B79F5) >>> 0; + let t = a; + t = Math.imul(t ^ (t >>> 15), t | 1); + t ^= t + Math.imul(t ^ (t >>> 7), t | 61); + return ((t ^ (t >>> 14)) >>> 0) / 4294967296; + }; + } + + // ââ Segmentation âââââââââââââââââââââââââââââââââââââââââââââââââââââââ + function graphemes(word) { return word.g.split('-'); } + + function graphemePhonemes(g) { + if (GRAPHEMES[g]) return GRAPHEMES[g]; + if (g.length === 2 && g[0] === g[1] && GRAPHEMES[g[0]]) return GRAPHEMES[g[0]]; // ll, ss, gg ⌠+ throw new Error('unknown grapheme: ' + g); + } + + // Phoneme ids for a word, one entry per grapheme (x -> ['k','s'] is one entry). + function phonemeUnits(word) { + const gs = graphemes(word); + if (word.p) { + const ps = word.p.split(' '); + if (ps.length !== gs.length) throw new Error('phoneme override length mismatch: ' + word.w); + return ps.map((p) => (p === '-' ? [] : [p])); + } + return gs.map(graphemePhonemes); + } + + // Which teaching-sequence grapheme a spelling unit needs (ll -> l, ck -> ck, ay -> ai). + function baseGrapheme(g) { + if (BASE[g]) return BASE[g]; + if (SEQUENCE.indexOf(g) >= 0) return g; + if (g.length === 2 && g[0] === g[1]) return g[0]; + return g; + } + + // The grapheme that teaches a phoneme (k -> c, uu -> oo, ai -> ai). + function homeGrapheme(p) { + const ph = PHONEMES[p]; + if (ph && ph.home) return ph.home; + return SEQUENCE.find((g) => GRAPHEMES[g] && GRAPHEMES[g].length === 1 && GRAPHEMES[g][0] === p) || null; + } + + // Every spelling unit is known, and (for override words) so is the grapheme + // that teaches each sound actually used (robot needs 'oa' as well as 'o'). + function wordIsAvailable(word, known) { + if (!graphemes(word).every((g) => known.indexOf(baseGrapheme(g)) >= 0)) return false; + if (word.p) return [].concat(...phonemeUnits(word)).every((p) => known.indexOf(homeGrapheme(p)) >= 0); + return true; + } + + function availableWords(known) { return WORDS.filter((w) => wordIsAvailable(w, known)); } + + // ââ Profiles âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ + const STORE_KEY = 'kg-story-stamp-v1'; + const MAX_PROFILES = 6; + + function newProfile(name, avatar, now, id) { + return { + id: id || ('p' + now.toString(36) + Math.floor(Math.random() * 1e6).toString(36)), + name: name || 'Librarian', + avatar: avatar || AVATARS[0], + created: now, + introduced: SEQUENCE.slice(0, STARTING_GRAPHEMES), + graphemes: {}, // g -> { hits, misses } + words: {}, // w -> { reps, lapses, streak, interval, due, last, seen } + orders: 0, // books checked out, ever + recent: [], // first-try results of the last few books + sinceIntro: 0, + lastWord: null, + topics: {}, // topic id -> times chosen + }; + } + + function loadStore(storage) { + let data = null; + try { data = JSON.parse(storage.getItem(STORE_KEY)); } catch (_) {} + if (!data || !Array.isArray(data.profiles)) data = { version: 1, profiles: [], last: null }; + data.profiles.forEach((p) => { if (!p.topics) p.topics = {}; }); + return data; + } + function saveStore(storage, data) { + try { storage.setItem(STORE_KEY, JSON.stringify(data)); } catch (_) {} + } + + // ââ Mastery + scheduling âââââââââââââââââââââââââââââââââââââââââââââââ + const INTERVALS_DAYS = [1, 3, 7, 14, 30, 60]; + const MASTERED_STREAK = 2; + const RETURN_MODE_UNLOCK = 5; // mastered words before "returns" (typing by ear) appear + const BOOKS_PER_DAY = 6; // a natural stopping point + + function wordRecord(profile, w) { return profile.words[w] || null; } + function isMastered(rec) { return !!rec && rec.streak >= MASTERED_STREAK; } + // Words a profile has met that still exist in WORDS (a saved profile must + // survive a word being removed from the list in a later version). + function servedWords(profile) { return Object.keys(profile.words).filter((w) => WORD_BY_NAME[w]); } + function masteredWords(profile) { return servedWords(profile).filter((w) => isMastered(profile.words[w])); } + function shakyWords(profile) { return servedWords(profile).filter((w) => !isMastered(profile.words[w])); } + + // Apply the result of one checked-out book. firstTry = no wrong keys / wrong picks. + function recordOrder(profile, w, firstTry, now) { + const rec = profile.words[w] || { reps: 0, lapses: 0, streak: 0, interval: 0, due: now, last: null, seen: 0 }; + rec.seen += 1; + rec.last = now; + if (firstTry) { + rec.reps += 1; + rec.streak += 1; + rec.interval = INTERVALS_DAYS[Math.min(rec.streak - 1, INTERVALS_DAYS.length - 1)]; + rec.due = now + rec.interval * DAY; + } else { + rec.lapses += 1; + rec.streak = 0; + rec.interval = 0; + rec.due = now + 5 * MIN; // the regular wanders back a little later + } + profile.words[w] = rec; + profile.orders += 1; + profile.sinceIntro += 1; + profile.lastWord = w; + profile.recent = profile.recent.concat([!!firstTry]).slice(-8); + return rec; + } + + function recordGrapheme(profile, g, hit) { + const key = baseGrapheme(g); + const s = profile.graphemes[key] || { hits: 0, misses: 0 }; + if (hit) s.hits += 1; else s.misses += 1; + profile.graphemes[key] = s; + } + + function recordTopic(profile, topicId) { + if (!profile.topics) profile.topics = {}; + profile.topics[topicId] = (profile.topics[topicId] || 0) + 1; + } + + function dueWords(profile, now) { + return servedWords(profile) + .filter((w) => profile.words[w].due <= now) + .sort((a, b) => profile.words[a].due - profile.words[b].due); + } + + function unseenWords(profile) { + return availableWords(profile.introduced).map((w) => w.w).filter((w) => !profile.words[w]); + } + + function nextGrapheme(profile) { + return SEQUENCE.find((g) => profile.introduced.indexOf(g) < 0) || null; + } + + // Mastery-based pacing: a new sound arrives when the child is fluent with the + // current pool (recent books mostly first-try) or the pool has run dry. + function shouldIntroduce(profile) { + if (!nextGrapheme(profile)) return false; + if (unseenWords(profile).length === 0) return true; + if (profile.sinceIntro < 3) return false; + const r = profile.recent.slice(-5); + if (r.length < 4) return false; + const acc = r.filter(Boolean).length / r.length; + return acc >= 0.8; + } + + function introduceNext(profile) { + const g = nextGrapheme(profile); + if (!g) return null; + profile.introduced = profile.introduced.concat([g]); + profile.sinceIntro = 0; + return g; + } + + // Pick the next book. Returns { word, mode, review } â mode is 'read' + // (the request slip shows the word, the child decodes it) or 'return' + // (the patron says the word, the child types its sounds). `filter` narrows + // the candidates (used for topics); `exclude` are words already queued. + function pickNext(profile, now, rng, filter, exclude) { + rng = rng || Math.random; + filter = filter || (() => true); + exclude = exclude || []; + const ok = (w) => w !== profile.lastWord && exclude.indexOf(w) < 0 && filter(WORD_BY_NAME[w]); + const due = dueWords(profile, now).filter(ok); + const fresh = unseenWords(profile).filter(ok); + const newest = profile.introduced[profile.introduced.length - 1]; + let w = null, review = false; + + if (due.length && (fresh.length === 0 || rng() < 0.6)) { + w = due[Math.floor(rng() * Math.min(due.length, 3))]; // one of the most overdue + review = true; + } else if (fresh.length) { + const withNewest = fresh.filter((x) => graphemes(WORD_BY_NAME[x]).map(baseGrapheme).indexOf(newest) >= 0); + const pool = withNewest.length ? withNewest : fresh; + w = pool[Math.floor(rng() * pool.length)]; + } else { + // nothing due and nothing new: practise the least recently served word + let pool = servedWords(profile).filter(ok); + if (!pool.length) pool = servedWords(profile).filter((x) => exclude.indexOf(x) < 0 && filter(WORD_BY_NAME[x])); + if (!pool.length) pool = availableWords(profile.introduced).map((x) => x.w).filter((x) => exclude.indexOf(x) < 0 && filter(WORD_BY_NAME[x])); + if (!pool.length) return null; + pool.sort((a, b) => ((profile.words[a] || {}).last || 0) - ((profile.words[b] || {}).last || 0)); + w = pool[0]; + review = !!profile.words[w]; + } + + let mode = 'read'; + const rec = profile.words[w]; + if (rec && masteredWords(profile).length >= RETURN_MODE_UNLOCK && rng() < 0.55) mode = 'return'; + return { word: WORD_BY_NAME[w], mode, review }; + } + + // Topics that currently have a book the child could be asked for. + function topicsWithWords(profile) { + const avail = availableWords(profile.introduced); + return TOPICS.filter((t) => avail.some((w) => w.t.indexOf(t.id) >= 0)); + } + + // Who is waiting at the desk: up to `n` patrons, each wanting a different + // topic, each with a book picked by the scheduler. When too few topics are + // open yet, the extra patrons just want "a good book". + function buildQueue(profile, now, rng, n) { + rng = rng || Math.random; + n = n || 3; + const topics = topicsWithWords(profile).slice(); + // Favour topics the child has picked before, but keep everything in play. + const weight = (t) => 1 + Math.min(3, (profile.topics && profile.topics[t.id]) || 0) * 0.5; + const queue = []; + const used = []; + while (queue.length < n) { + let topic = ANY_TOPIC; + if (topics.length) { + const total = topics.reduce((s, t) => s + weight(t), 0); + let r = rng() * total, idx = 0; + while (idx < topics.length - 1 && (r -= weight(topics[idx])) > 0) idx++; + topic = topics.splice(idx, 1)[0]; + } + const filter = topic === ANY_TOPIC ? null : (w) => w.t.indexOf(topic.id) >= 0; + const next = pickNext(profile, now, rng, filter, used); + if (!next) { if (!topics.length) break; continue; } + used.push(next.word.w); + queue.push({ topic, word: next.word, mode: next.mode, review: next.review, patron: patronFor(next.word.w) }); + if (!topics.length && queue.length && topic === ANY_TOPIC) break; // one generic patron is enough + } + return queue; + } + + // Two distractor covers for the "which book is it?" check. Prefers words + // the child has met that share letters with the target (cat/cap, pin/pan). + function distractors(word, profile, rng) { + rng = rng || Math.random; + const seen = servedWords(profile); + const usable = (list) => list.filter((w) => w !== word.w && WORD_BY_NAME[w] && WORD_BY_NAME[w].art !== word.art); + const shares = (w) => WORD_BY_NAME[w].w.split('').some((c) => word.w.indexOf(c) >= 0); + let pool = usable(seen); + if (pool.length < 2) pool = usable(availableWords(profile.introduced).map((x) => x.w)); + if (pool.length < 2) pool = usable(WORDS.map((x) => x.w)); + const near = pool.filter(shares), far = pool.filter((w) => !shares(w)); + const pick = []; + const take = (arr) => { + while (arr.length && pick.length < 2) { + const w = arr.splice(Math.floor(rng() * arr.length), 1)[0]; + if (!pick.some((p) => WORD_BY_NAME[p].art === WORD_BY_NAME[w].art)) pick.push(w); + } + }; + take(near); take(far); + return pick.map((w) => WORD_BY_NAME[w]); + } + + // ââ Library growth âââââââââââââââââââââââââââââââââââââââââââââââââââââ + const LIBRARY_UPGRADES = [ + { at: 2, id: 'rug', label: 'a story rug' }, + { at: 5, id: 'plant', label: 'a potted palm' }, + { at: 9, id: 'lamp', label: 'a reading lamp' }, + { at: 14, id: 'fishtank', label: 'a fish tank' }, + { at: 20, id: 'bunting', label: 'paper bunting' }, + { at: 28, id: 'cat', label: 'a library cat' }, + { at: 38, id: 'globe', label: 'a spinning globe' }, + { at: 50, id: 'telescope', label: 'a telescope' }, + { at: 65, id: 'dino', label: 'a dinosaur skeleton' }, + { at: 85, id: 'rocket', label: 'a rocket mobile' }, + ]; + function libraryUpgrades(masteredCount) { return LIBRARY_UPGRADES.filter((u) => masteredCount >= u.at); } + + // ââ Blend timeline âââââââââââââââââââââââââââââââââââââââââââââââââââââ + // Three passes: each sound held long with big gaps, then shorter and + // closer, then nearly touching. The whole word is never played here: the + // child has to hear it fuse and say it. Stops and unstretchable sounds keep + // their natural length; `hold` is how long a stretchable sound is held. + const BLEND_PASSES = [ + { gap: 0.5, hold: 0.8 }, + { gap: 0.2, hold: 0.4 }, + { gap: 0.02, hold: 0.16 }, + ]; + const PASS_PAUSE = 0.6; + const MIN_HOLD = 0.12; + + // Seconds a phoneme takes when a pass asks for `hold`. + function phonemeLength(p, hold, durations) { + const ph = PHONEMES[p] || {}; + const nat = (durations && durations[p]) || 0.25; + if (!ph.stretch) return nat; + return Math.min(nat, Math.max(MIN_HOLD, hold)); + } + + // units: [[phonemeId, ...] per grapheme]; durations: phonemeId -> seconds. + function blendTimeline(units, durations, opts) { + opts = opts || {}; + const passes = opts.passes || BLEND_PASSES; + const ev = []; + let t = opts.start || 0; + passes.forEach((pass, pi) => { + ev.push({ t, kind: 'pass', pass: pi }); + units.forEach((unit, ui) => { + unit.forEach((p, k) => { + const len = phonemeLength(p, pass.hold, durations); + ev.push({ t, kind: 'phoneme', id: p, unit: ui, pass: pi, len }); + t += len + (k < unit.length - 1 ? 0.02 : 0); + }); + if (ui < units.length - 1) t += pass.gap; + }); + t += PASS_PAUSE; + }); + ev.push({ t: t - PASS_PAUSE + 0.2, kind: 'done' }); + return ev; + } + + return { + DAY, MIN, PHONEMES, GRAPHEMES, BASE, SEQUENCE, STARTING_GRAPHEMES, TOPICS, TOPIC_BY_ID, ANY_TOPIC, + WORDS, WORD_BY_NAME, PATRONS, AVATARS, UI_ART, + STORE_KEY, MAX_PROFILES, INTERVALS_DAYS, MASTERED_STREAK, RETURN_MODE_UNLOCK, BOOKS_PER_DAY, + LIBRARY_UPGRADES, BLEND_PASSES, PASS_PAUSE, MIN_HOLD, + hashStr, patronFor, mulberry32, + graphemes, graphemePhonemes, phonemeUnits, baseGrapheme, homeGrapheme, wordIsAvailable, availableWords, + newProfile, loadStore, saveStore, + wordRecord, isMastered, servedWords, masteredWords, shakyWords, recordOrder, recordGrapheme, recordTopic, + dueWords, unseenWords, nextGrapheme, shouldIntroduce, introduceNext, pickNext, topicsWithWords, buildQueue, + distractors, libraryUpgrades, phonemeLength, blendTimeline, + }; +})); diff --git a/games/story-stamp/style.css b/games/story-stamp/style.css @@ -0,0 +1,306 @@ +/* Story Stamp â DOM game styles. Everything scales from --u (1% of the shorter + side of #game-container) so the library looks the same on a phone and a TV. */ + +* { margin: 0; padding: 0; box-sizing: border-box; } +html, body { height: 100%; overflow: hidden; } +body { + background: #1A1A2E; + font-family: 'Fredoka', sans-serif; + display: flex; + flex-direction: column; + color: #1A1A2E; +} +nav { width: 100%; padding: 0.6rem 1.25rem; flex-shrink: 0; } +nav a { color: #aaa; text-decoration: none; font-size: 1rem; } +nav a:hover { color: #fff; } +#game-container { flex: 1; width: 100%; min-height: 0; position: relative; overflow: hidden; } + +#audio-controls { position: fixed; top: 0.45rem; right: 1rem; display: flex; gap: 0.4rem; z-index: 10; } +#audio-controls button { + background: rgba(255,255,255,0.08); border: 1px solid rgba(255,255,255,0.18); color: #bbb; + font-family: 'Fredoka', sans-serif; font-size: 0.85rem; padding: 0.25rem 0.65rem; border-radius: 6px; + cursor: pointer; user-select: none; +} +#audio-controls button:hover { background: rgba(255,255,255,0.16); color: #fff; } +#audio-controls button.muted { color: #555; border-color: rgba(255,255,255,0.08); } + +#attribution { + flex-shrink: 0; text-align: center; font-size: 0.65rem; color: #556; padding: 0.25rem 0.5rem 0.35rem; + white-space: nowrap; overflow: hidden; text-overflow: ellipsis; +} +#attribution a { color: #778; text-decoration: none; } +#attribution a:hover { color: #aab; } + +/* ââ Scene ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ +.ss-root { + --u: 5px; /* set by JS: 1% of min(width, height * 1.25) */ + --red: #E63946; --blue: #1D7CF2; --yellow: #FFD23F; --teal: #2EC4B6; + --wood: #b7743c; --wood-dark: #8a5227; --wood-light: #d9975a; + position: absolute; inset: 0; overflow: hidden; user-select: none; + background: #f6e7c6; + font-size: calc(var(--u) * 3.2); +} +.ss-root.ss-dusk { background: #e8c9a0; } +.ss-root.ss-dusk .ss-wall { filter: saturate(0.85) brightness(0.92); } +.ss-img { display: inline-block; width: 1em; height: 1em; vertical-align: -0.15em; object-fit: contain; } +.ss-root.shake { animation: ss-shake 0.35s; } +@keyframes ss-shake { 20% { transform: translateY(calc(var(--u) * 0.5)); } 50% { transform: translateY(calc(var(--u) * -0.3)); } } + +/* wall + window */ +.ss-wall { position: absolute; left: 0; right: 0; top: 0; height: 62%; + background: linear-gradient(#fff6de, #f3e0b8); } +.ss-wall::after { content: ''; position: absolute; left: 0; right: 0; bottom: 0; height: calc(var(--u) * 1.6); + background: var(--wood-dark); box-shadow: 0 calc(var(--u) * -0.6) 0 var(--wood-light); } +.ss-window { position: absolute; left: 50%; top: 4%; width: calc(var(--u) * 34); height: calc(var(--u) * 26); transform: translateX(-50%); + border-radius: calc(var(--u) * 17) calc(var(--u) * 17) calc(var(--u) * 1.5) calc(var(--u) * 1.5); overflow: hidden; + border: calc(var(--u) * 1.4) solid var(--wood); box-shadow: 0 0 0 calc(var(--u) * 0.5) var(--wood-light), inset 0 0 calc(var(--u) * 2) rgba(0,0,0,0.15); + background: #8fdcff; } +.ss-window::after { content: ''; position: absolute; left: 50%; top: 0; bottom: 0; width: calc(var(--u) * 1); margin-left: calc(var(--u) * -0.5); background: var(--wood); opacity: 0.9; } +.ss-sky { position: absolute; inset: 0; background: linear-gradient(#7fd3ff, #c8efff 70%); } +.ss-sun { position: absolute; right: 14%; top: 10%; width: calc(var(--u) * 7); height: calc(var(--u) * 7); + border-radius: 50%; background: radial-gradient(circle, #fff6c2 0%, #FFD23F 55%, rgba(255,210,63,0) 72%); } +.ss-cloud { position: absolute; top: 18%; width: calc(var(--u) * 9); height: calc(var(--u) * 3); background: #fff; + border-radius: 999px; opacity: 0.9; animation: ss-drift 40s linear infinite; } +.ss-cloud::before { content: ''; position: absolute; left: 25%; top: -50%; width: 45%; height: 130%; background: #fff; border-radius: 50%; } +.ss-cloud.c2 { top: 32%; width: calc(var(--u) * 6); animation-duration: 55s; animation-delay: -20s; } +@keyframes ss-drift { from { transform: translateX(-60%); } to { transform: translateX(400%); } } +.ss-sea { position: absolute; left: 0; right: 0; bottom: 0; height: 34%; background: linear-gradient(#3fb9e8, #1D7CF2); } +.ss-sea::after { content: ''; position: absolute; inset: 0; + background: repeating-linear-gradient(90deg, rgba(255,255,255,0.18) 0 6%, transparent 6% 14%); + mask: linear-gradient(transparent, rgba(0,0,0,0.6)); -webkit-mask: linear-gradient(transparent, rgba(0,0,0,0.6)); } +.ss-boat { position: absolute; top: 4%; font-size: calc(var(--u) * 4); animation: ss-sail 30s linear infinite; } +@keyframes ss-sail { from { transform: translateX(-120%); } 50% { transform: translateX(400%); } 50.01% { transform: translateX(-120%) scaleX(-1); } to { transform: translateX(400%) scaleX(-1); } } +.ss-bird { position: absolute; top: 22%; font-size: calc(var(--u) * 2); animation: ss-fly 18s linear infinite; } +@keyframes ss-fly { from { transform: translateX(1500%) translateY(0); } 50% { transform: translateX(700%) translateY(-60%); } to { transform: translateX(-200%) translateY(0); } } +.ss-palm { position: absolute; left: 4%; bottom: 26%; font-size: calc(var(--u) * 9); transform-origin: 50% 100%; animation: ss-sway 5s ease-in-out infinite; } +@keyframes ss-sway { 0%, 100% { transform: rotate(-2deg); } 50% { transform: rotate(2.5deg); } } +.ss-walkers { position: absolute; left: 0; right: 0; bottom: 28%; height: 1px; } +.ss-walker { position: absolute; bottom: 0; font-size: calc(var(--u) * 4); transition: transform 14s linear; will-change: transform; } + +/* shelves */ +.ss-shelf { position: absolute; top: 5%; width: calc(var(--u) * 26); height: 84%; display: flex; flex-direction: column; justify-content: space-between; + background: var(--wood); border-radius: calc(var(--u) * 1); padding: calc(var(--u) * 1.2); box-shadow: inset 0 0 0 calc(var(--u) * 0.5) var(--wood-dark); } +.ss-shelf.left { left: 2%; } .ss-shelf.right { right: 2%; } +.ss-row { flex: 1; margin-bottom: calc(var(--u) * 1.2); background: #6b3f1d; border-radius: calc(var(--u) * 0.5); position: relative; + box-shadow: 0 calc(var(--u) * 0.8) 0 var(--wood-light); } +.ss-row:last-child { margin-bottom: 0; } +.ss-spines { position: absolute; left: 6%; right: 6%; bottom: 0; top: 10%; display: flex; align-items: flex-end; gap: 3%; } +.ss-spines i { flex: 1; border-radius: calc(var(--u) * 0.4) calc(var(--u) * 0.4) 0 0; box-shadow: inset calc(var(--u) * -0.4) 0 rgba(0,0,0,0.18); } +.ss-sign { position: absolute; left: 50%; top: 58%; transform: translateX(-50%) rotate(-1.5deg); background: #fff; color: var(--red); + padding: 0.05em 0.6em; border-radius: 0.4em; font-weight: 600; font-size: calc(var(--u) * 3.2); border: calc(var(--u) * 0.45) solid var(--yellow); + white-space: nowrap; box-shadow: 0 calc(var(--u) * 0.5) 0 rgba(0,0,0,0.12); } + +/* decor (library upgrades) */ +.ss-decor { position: absolute; inset: 0; pointer-events: none; } +.ss-decor > * { position: absolute; font-size: calc(var(--u) * 5); } +.ss-decor .d-rug { left: 50%; top: 106%; width: calc(var(--u) * 70); height: calc(var(--u) * 14); transform: translateX(-50%); border-radius: 50%; + background: repeating-radial-gradient(ellipse, var(--red) 0 8%, var(--yellow) 8% 16%, var(--teal) 16% 24%); opacity: 0.75; font-size: 1px; } +.ss-decor .d-plant { left: 26%; bottom: 3%; font-size: calc(var(--u) * 7); } +.ss-decor .d-lamp { right: 27%; bottom: 40%; animation: ss-twinkle 2.4s ease-in-out infinite; } +.ss-decor .d-tank { right: 26%; bottom: 3%; width: calc(var(--u) * 12); height: calc(var(--u) * 8); background: linear-gradient(#bfefff, #4fc3f7); + border: calc(var(--u) * 0.6) solid var(--wood-dark); border-radius: calc(var(--u) * 1); font-size: calc(var(--u) * 3.5); overflow: hidden; } +.ss-decor .d-tank .ss-img { position: absolute; top: 30%; animation: ss-swimby 6s ease-in-out infinite alternate; } +.ss-decor .d-tank .ss-img + .ss-img { top: 55%; animation-duration: 8s; animation-direction: alternate-reverse; } +@keyframes ss-swimby { from { left: 5%; transform: scaleX(1); } to { left: 60%; transform: scaleX(1); } } +.ss-decor .d-bunting { left: 8%; right: 8%; top: 1%; height: calc(var(--u) * 3); font-size: 1px; + background: repeating-linear-gradient(90deg, var(--yellow) 0 8%, var(--teal) 8% 16%, var(--red) 16% 24%, var(--blue) 24% 32%); + clip-path: polygon(0 0, 100% 0, 100% 30%, 96% 100%, 92% 30%, 88% 100%, 84% 30%, 80% 100%, 76% 30%, 72% 100%, 68% 30%, 64% 100%, 60% 30%, 56% 100%, 52% 30%, 48% 100%, 44% 30%, 40% 100%, 36% 30%, 32% 100%, 28% 30%, 24% 100%, 20% 30%, 16% 100%, 12% 30%, 8% 100%, 4% 30%, 0 100%); } +.ss-decor .d-cat { left: 30%; top: 96%; font-size: calc(var(--u) * 4.5); } +.ss-decor .d-globe { left: 6%; top: 3%; font-size: calc(var(--u) * 4.5); animation: ss-spin 12s linear infinite; } +.ss-decor .d-telescope { right: 5%; bottom: 2%; font-size: calc(var(--u) * 6); } +.ss-decor .d-dino { left: 31%; top: 5%; font-size: calc(var(--u) * 8); filter: grayscale(1) brightness(1.3); } +.ss-decor .d-rocket { right: 32%; top: 4%; font-size: calc(var(--u) * 5); animation: ss-bob 2.2s ease-in-out infinite; } +@keyframes ss-twinkle { 50% { opacity: 0.55; } } +@keyframes ss-spin { from { transform: rotateY(0); } to { transform: rotateY(360deg); } } +@keyframes ss-bob { 50% { transform: translateY(-10%); } } + +/* floor */ +.ss-floor { position: absolute; left: 0; right: 0; top: 62%; bottom: 0; + background: repeating-linear-gradient(90deg, #d9a06a 0 9%, #cf9560 9% 10%), #d9a06a; } +.ss-floor::before { content: ''; position: absolute; inset: 0; background: linear-gradient(rgba(0,0,0,0.08), transparent 30%); } + +/* ââ The queue âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ +.ss-queue { position: absolute; left: 2%; bottom: 20%; display: flex; gap: calc(var(--u) * 1.5); align-items: flex-end; z-index: 1; } +.ss-patron { position: relative; width: calc(var(--u) * 12); text-align: center; transition: transform 0.25s, opacity 0.4s; } +.ss-patron.in { animation: ss-pop 0.5s both; } +.ss-patron-body { font-size: calc(var(--u) * 9); line-height: 1; filter: drop-shadow(0 calc(var(--u) * 0.6) 0 rgba(0,0,0,0.15)); transition: transform 0.25s; } +.ss-patron.sel { transform: translateY(calc(var(--u) * -1.5)); } +.ss-patron.sel .ss-patron-body { animation: ss-bounce 0.9s ease-in-out infinite; } +.ss-patron-name { display: inline-block; margin-top: calc(var(--u) * 0.4); background: var(--c, #555); color: #fff; font-weight: 600; font-size: calc(var(--u) * 2.4); + padding: 0 0.6em; border-radius: 999px; } +.ss-patron .ss-num { position: absolute; left: calc(var(--u) * -0.5); top: calc(var(--u) * -1); } +.ss-sign-card { display: inline-flex; flex-direction: column; align-items: center; background: #fff; border: calc(var(--u) * 0.45) solid #999; border-radius: calc(var(--u) * 1.2); + padding: calc(var(--u) * 0.5) calc(var(--u) * 1); margin-bottom: calc(var(--u) * 0.8); font-size: calc(var(--u) * 5); line-height: 1; opacity: 0.75; transition: transform 0.25s, opacity 0.25s; + box-shadow: 0 calc(var(--u) * 0.4) 0 rgba(0,0,0,0.12); } +.ss-sign-card span { font-size: calc(var(--u) * 2.2); font-weight: 600; color: #333; margin-top: calc(var(--u) * 0.4); white-space: nowrap; } +.ss-patron.sel .ss-sign-card { opacity: 1; transform: scale(1.12); } +.ss-holding { position: absolute; right: calc(var(--u) * -1); bottom: calc(var(--u) * 6); font-size: calc(var(--u) * 4); background: #fff; border-radius: 0.3em; padding: 0.1em; + transform: rotate(8deg); box-shadow: 0 0.1em 0 rgba(0,0,0,0.15); } +.ss-patron.wait { opacity: 0.4; } +.ss-patron.called { transform: translateX(calc(var(--u) * 20)) scale(1.05); opacity: 0; } +@keyframes ss-bounce { 50% { transform: translateY(-6%); } } +@keyframes ss-pop { from { transform: translateY(20%) scale(0.85); opacity: 0.4; } to { transform: none; opacity: 1; } } + +/* the patron at the desk */ +.ss-customer { position: absolute; right: 5%; bottom: 22%; width: calc(var(--u) * 16); text-align: center; + transform: translateX(150%); transition: transform 0.9s cubic-bezier(.3,1.2,.5,1); } +.ss-customer.in { transform: none; } +.ss-customer.out { transform: translateX(150%); transition-duration: 0.8s; } +.ss-cust-body { font-size: calc(var(--u) * 12); line-height: 1; filter: drop-shadow(0 calc(var(--u) * 0.6) 0 rgba(0,0,0,0.15)); } +.ss-customer.in .ss-cust-body { animation: ss-bob 2.2s ease-in-out infinite; } +.ss-customer.happy .ss-cust-body { animation: ss-jump 0.5s ease-in-out 2; } +.ss-customer.puzzled .ss-cust-body { animation: ss-wobble 0.6s ease-in-out; } +@keyframes ss-jump { 50% { transform: translateY(-18%); } } +@keyframes ss-wobble { 25% { transform: rotate(-8deg); } 75% { transform: rotate(8deg); } } +.ss-cust-name { display: inline-block; margin-top: calc(var(--u) * 0.4); color: #fff; font-weight: 600; font-size: calc(var(--u) * 2.6); padding: 0 0.6em; border-radius: 999px; } +.ss-bubble { position: absolute; left: 50%; bottom: 100%; transform: translate(-50%, 10%) scale(0.6); opacity: 0; transition: transform 0.3s, opacity 0.3s; + background: #fff; border-radius: calc(var(--u) * 2); padding: calc(var(--u) * 0.8) calc(var(--u) * 1.4); font-size: calc(var(--u) * 6); line-height: 1; + box-shadow: 0 calc(var(--u) * 0.5) 0 rgba(0,0,0,0.12); white-space: nowrap; } +.ss-bubble::after { content: ''; position: absolute; left: 50%; top: 100%; border: calc(var(--u) * 1.2) solid transparent; border-top-color: #fff; margin-left: calc(var(--u) * -1.2); } +.ss-bubble.show { opacity: 1; transform: translate(-50%, -10%) scale(1); } + +/* ââ Desk ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ +.ss-desk { position: absolute; left: 54%; bottom: 2%; width: calc(var(--u) * 52); height: calc(var(--u) * 24); transform: translateX(-50%); } +.ss-desk-top { position: absolute; left: -3%; right: -3%; top: 0; height: 18%; background: var(--wood-light); border-radius: calc(var(--u) * 1); + box-shadow: 0 calc(var(--u) * 0.6) 0 var(--wood-dark); } +.ss-desk-front { position: absolute; left: 0; right: 0; top: 16%; bottom: 0; background: var(--wood); border-radius: 0 0 calc(var(--u) * 1.2) calc(var(--u) * 1.2); + background-image: repeating-linear-gradient(90deg, transparent 0 24%, rgba(0,0,0,0.1) 24% 25%); } +.ss-desk-front::after { content: 'đ'; position: absolute; right: 4%; bottom: 8%; font-size: calc(var(--u) * 3); opacity: 0.6; } + +/* request slip with sound boxes */ +.ss-slip { position: absolute; left: 50%; bottom: 70%; transform: translate(-50%, 30%) rotate(-6deg) scale(0.7); opacity: 0; + background: #fffdf5; border-radius: calc(var(--u) * 1); padding: calc(var(--u) * 1.4) calc(var(--u) * 2.2) calc(var(--u) * 2.6); + box-shadow: 0 calc(var(--u) * 0.6) 0 rgba(0,0,0,0.12), 0 0 0 calc(var(--u) * 0.3) #e8dcc0; transition: transform 0.4s cubic-bezier(.3,1.3,.5,1), opacity 0.3s; + min-width: calc(var(--u) * 24); } +.ss-slip.show { opacity: 1; transform: translate(-50%, 0) rotate(-2deg); } +.ss-slip.ready { box-shadow: 0 0 0 calc(var(--u) * 0.6) var(--yellow), 0 calc(var(--u) * 0.6) 0 rgba(0,0,0,0.12); animation: ss-glow 1.2s ease-in-out infinite; } +.ss-slip.wobble { animation: ss-wobble 0.5s; } +.ss-desk.choosing .ss-slip.show { transform: translate(-118%, 0) rotate(-8deg) scale(0.8); } +.ss-desk.choosing .ss-slip .ss-finger { display: none; } +@keyframes ss-glow { 50% { box-shadow: 0 0 0 calc(var(--u) * 1.2) rgba(255,210,63,0.5), 0 calc(var(--u) * 0.6) 0 rgba(0,0,0,0.12); } } +.ss-slip-title { font-size: calc(var(--u) * 2); color: #98876a; letter-spacing: 0.1em; text-transform: uppercase; text-align: center; margin-bottom: calc(var(--u) * 0.8); } +.ss-boxes { display: flex; justify-content: center; gap: calc(var(--u) * 1.6); transition: gap 0.5s; } +.ss-boxes.pass1 { gap: calc(var(--u) * 0.9); } +.ss-boxes.pass2, .ss-boxes.fused { gap: calc(var(--u) * 0.3); } +.ss-box { width: calc(var(--u) * 8); height: calc(var(--u) * 9); border: calc(var(--u) * 0.6) dashed #b9ab8c; border-radius: calc(var(--u) * 1.2); + display: flex; align-items: center; justify-content: center; font-size: calc(var(--u) * 5.6); font-weight: 600; color: #b9ab8c; background: #fff; + transition: transform 0.2s, background 0.2s, color 0.2s, border-color 0.2s; position: relative; } +.ss-box.wide { width: calc(var(--u) * 12); } +.ss-box.next { border-color: var(--c); color: #cfc4a8; transform: scale(1.06); animation: ss-nudge 1.4s ease-in-out infinite; } +@keyframes ss-nudge { 50% { transform: scale(1.12); } } +.ss-box .ss-partial { color: var(--c); } +.ss-box.done { border-style: solid; border-color: var(--c); color: #fff; background: var(--c); } +.ss-box.stamped { animation: ss-stampin 0.35s cubic-bezier(.3,1.5,.5,1); transform: rotate(-3deg); } +.ss-box.stamped:nth-child(even) { transform: rotate(3deg); } +@keyframes ss-stampin { from { transform: scale(1.8) rotate(-10deg); opacity: 0.3; } 60% { transform: scale(0.9) rotate(2deg); } to { transform: scale(1) rotate(-3deg); opacity: 1; } } +.ss-box.hidden-letter { color: transparent; } +.ss-box.hidden-letter::after { content: '?'; position: absolute; color: #d8cdb0; } +.ss-box.hidden-letter.reveal { color: #fff; background: var(--c); border-color: var(--c); } +.ss-box.hidden-letter.reveal::after { display: none; } +.ss-box.speaking { transform: scale(1.22) !important; box-shadow: 0 0 0 calc(var(--u) * 0.8) rgba(255,210,63,0.7); z-index: 1; } +.ss-box.hint { animation: ss-wobble 0.5s; border-color: var(--red); } +.ss-finger { position: absolute; left: 10%; bottom: calc(var(--u) * -3.2); font-size: calc(var(--u) * 5); transform: translateX(-50%); opacity: 0; transition: left 0.18s ease-out, opacity 0.2s; pointer-events: none; } +.ss-finger.show { opacity: 1; } + +/* the rubber stamp */ +.ss-stamp { position: absolute; right: calc(var(--u) * -8); bottom: 74%; width: calc(var(--u) * 8); height: calc(var(--u) * 9); transition: transform 0.35s cubic-bezier(.3,1.2,.5,1); transform-origin: 50% 100%; z-index: 3; } +.ss-stamp-handle { position: absolute; left: 30%; width: 40%; top: 0; height: 55%; background: linear-gradient(90deg, #8a5227, #c98a4b, #8a5227); border-radius: 40% 40% 20% 20%; } +.ss-stamp-base { position: absolute; left: 0; right: 0; bottom: 0; height: 40%; background: linear-gradient(#444, #222); border-radius: calc(var(--u) * 0.8); } +.ss-stamp-base::after { content: ''; position: absolute; left: 8%; right: 8%; bottom: -14%; height: 18%; background: var(--red); border-radius: 30%; opacity: 0.8; } +.ss-stamp.press { animation: ss-press 0.25s; } +@keyframes ss-press { 40% { transform: translateY(18%) scaleY(0.9); } } +.ss-stamp.over { transform: translate(calc(var(--u) * -30), calc(var(--u) * -19)) scale(1.3) rotate(-6deg); } +.ss-stamp.over.slam { transform: translate(calc(var(--u) * -30), calc(var(--u) * -10)) scale(1.3) rotate(-2deg); transition-duration: 0.12s; transition-timing-function: cubic-bezier(.5,0,.8,.3); } + +/* the book cart */ +.ss-cart { position: absolute; left: 50%; bottom: 95%; transform: translate(-50%, 40%); opacity: 0; display: flex; gap: calc(var(--u) * 2.2); align-items: flex-end; + padding: calc(var(--u) * 1.2) calc(var(--u) * 2) calc(var(--u) * 1.4); background: #cfd8dc; border-radius: calc(var(--u) * 1); transition: transform 0.45s cubic-bezier(.3,1.2,.5,1), opacity 0.3s; + box-shadow: 0 calc(var(--u) * 0.6) 0 #90a4ae; z-index: 2; } +.ss-cart::before, .ss-cart::after { content: ''; position: absolute; bottom: calc(var(--u) * -1.6); width: calc(var(--u) * 2.4); height: calc(var(--u) * 2.4); border-radius: 50%; background: #37474f; box-shadow: inset 0 0 0 calc(var(--u) * 0.5) #90a4ae; } +.ss-cart::before { left: 8%; } .ss-cart::after { right: 8%; } +.ss-cart.show { opacity: 1; transform: translate(-50%, 0); } +.ss-cart.picked { background: transparent; box-shadow: none; } +.ss-cart.picked::before, .ss-cart.picked::after { display: none; } +.ss-book { position: relative; width: calc(var(--u) * 12); height: calc(var(--u) * 14); border-radius: calc(var(--u) * 0.6) calc(var(--u) * 1.2) calc(var(--u) * 1.2) calc(var(--u) * 0.6); + background: var(--c); box-shadow: inset calc(var(--u) * 1.4) 0 0 rgba(0,0,0,0.22), inset calc(var(--u) * 1.8) 0 0 rgba(255,255,255,0.25), 0 calc(var(--u) * 0.5) 0 rgba(0,0,0,0.2); + transition: transform 0.2s, opacity 0.4s; } +.ss-cover { position: absolute; left: 24%; right: 10%; top: 12%; bottom: 12%; background: #fff; border-radius: calc(var(--u) * 0.6); display: flex; align-items: center; justify-content: center; + font-size: calc(var(--u) * 6.5); line-height: 1; } +.ss-book.sel { transform: translateY(-10%) scale(1.12); box-shadow: 0 0 0 calc(var(--u) * 0.6) var(--yellow), 0 calc(var(--u) * 0.5) 0 rgba(0,0,0,0.2); z-index: 1; } +.ss-book.yes { animation: ss-jump 0.4s; } +.ss-book.no { animation: ss-wobble 0.5s; filter: grayscale(0.6); } +.ss-book.point { animation: ss-bounce 0.7s ease-in-out infinite; } +.ss-book.gone { opacity: 0; transform: translateY(20%) scale(0.8); } +.ss-book.stamped .ss-cover::after { content: 'OUT'; position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%) rotate(-18deg); color: var(--red); + border: calc(var(--u) * 0.5) solid var(--red); border-radius: calc(var(--u) * 0.8); padding: 0 0.25em; font-size: calc(var(--u) * 3.2); font-weight: 600; letter-spacing: 0.05em; + opacity: 0.85; animation: ss-stampin 0.3s; background: rgba(255,255,255,0.7); } +.ss-book.handed { transform: translate(calc(var(--u) * 30), calc(var(--u) * -4)) scale(0.75) rotate(10deg); transition: transform 0.6s cubic-bezier(.4,0,.6,1); } +.ss-book .ss-num { right: calc(var(--u) * -1); top: calc(var(--u) * -1); left: auto; bottom: auto; } +.ss-num { position: absolute; width: calc(var(--u) * 3.2); height: calc(var(--u) * 3.2); border-radius: 50%; background: #1A1A2E; color: #fff; font-size: calc(var(--u) * 2); + display: flex; align-items: center; justify-content: center; font-weight: 600; } +.ss-book-out { position: absolute; left: 50%; bottom: 95%; transform: translate(-50%, 0); opacity: 0; transition: transform 0.7s cubic-bezier(.4,0,.6,1), opacity 0.3s; z-index: 2; } +.ss-book-out.show { opacity: 1; } +.ss-book-out.fly { transform: translate(calc(var(--u) * -44), calc(var(--u) * -40)) scale(0.35) rotate(-20deg); opacity: 0.6; } + +/* ââ Prompt / HUD ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ +.ss-prompt { position: absolute; left: 50%; bottom: 4%; transform: translateX(-50%); background: #1A1A2E; color: #fff; padding: 0.25em 0.9em; border-radius: 999px; + font-size: calc(var(--u) * 2.8); white-space: nowrap; z-index: 4; box-shadow: 0 calc(var(--u) * 0.4) 0 rgba(0,0,0,0.25); } +.ss-prompt:empty { display: none; } +kbd { display: inline-block; background: #fff; color: #1A1A2E; border-radius: 0.3em; padding: 0 0.4em; font-family: inherit; font-size: 0.85em; font-weight: 600; box-shadow: 0 0.12em 0 #aab; } +.ss-hud { position: absolute; left: 0; right: 0; top: 0; display: flex; justify-content: space-between; padding: calc(var(--u) * 1); pointer-events: none; z-index: 5; } +.ss-chip { background: rgba(26,26,46,0.85); color: #fff; padding: 0.15em 0.7em; border-radius: 999px; font-size: calc(var(--u) * 2.6); } +.ss-chip small { opacity: 0.6; font-size: 0.7em; margin-left: 0.4em; } +.ss-day { position: absolute; top: calc(var(--u) * 1.4); left: 50%; transform: translateX(-50%); display: flex; gap: calc(var(--u) * 0.8); z-index: 5; } +.ss-day span { width: calc(var(--u) * 2.2); height: calc(var(--u) * 2.2); border-radius: 50%; background: rgba(26,26,46,0.25); border: 2px solid rgba(26,26,46,0.4); } +.ss-day span.on { background: var(--yellow); border-color: #c99e00; } + +.ss-toast { position: absolute; left: 50%; top: 14%; transform: translateX(-50%); background: #fff; color: #1A1A2E; padding: 0.4em 1.1em; border-radius: 999px; + font-size: calc(var(--u) * 3.4); font-weight: 600; box-shadow: 0 calc(var(--u) * 0.6) 0 rgba(0,0,0,0.15); animation: ss-toast 2.6s both; z-index: 6; white-space: nowrap; } +@keyframes ss-toast { 0% { transform: translate(-50%, -80%) scale(0.8); opacity: 0; } 12%, 85% { transform: translate(-50%, 0) scale(1); opacity: 1; } 100% { transform: translate(-50%, -60%); opacity: 0; } } +.ss-fx { position: absolute; font-size: calc(var(--u) * 5); animation: ss-fx 1.4s ease-out both; pointer-events: none; z-index: 6; } +@keyframes ss-fx { from { transform: translateY(0) scale(0.6); opacity: 1; } to { transform: translateY(-140%) scale(1.3); opacity: 0; } } + +/* ââ Overlay panels ââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ +.ss-overlay[hidden] { display: none; } +.ss-overlay { position: absolute; inset: 0; background: rgba(26,26,46,0.55); display: flex; align-items: center; justify-content: center; z-index: 20; padding: calc(var(--u) * 2); } +.ss-panel { background: #fffdf5; border-radius: calc(var(--u) * 2.5); padding: calc(var(--u) * 3) calc(var(--u) * 4); max-width: 92%; max-height: 92%; overflow: auto; text-align: center; + box-shadow: 0 calc(var(--u) * 1.2) 0 rgba(0,0,0,0.2); animation: ss-popin 0.3s cubic-bezier(.3,1.3,.5,1) both; } +@keyframes ss-popin { from { transform: scale(0.85); } to { transform: none; } } +.ss-panel h2 { font-size: calc(var(--u) * 4.4); color: var(--red); margin-bottom: calc(var(--u) * 1.6); } +.ss-panel p { font-size: calc(var(--u) * 2.8); color: #555; margin-top: calc(var(--u) * 1.4); } +.ss-panel p .ss-img { font-size: calc(var(--u) * 6); } +.ss-list { display: flex; flex-direction: column; gap: calc(var(--u) * 1); } +.ss-item { font-size: calc(var(--u) * 3.6); padding: 0.3em 1em; border-radius: 999px; border: calc(var(--u) * 0.4) solid transparent; display: flex; align-items: center; gap: 0.4em; justify-content: center; } +.ss-item small { opacity: 0.5; font-size: 0.6em; } +.ss-item.sel { border-color: var(--blue); background: #e8f1ff; } +.ss-avatars { display: flex; flex-wrap: wrap; gap: calc(var(--u) * 1.2); justify-content: center; font-size: calc(var(--u) * 8); max-width: calc(var(--u) * 70); } +.ss-avatars .ss-img { border-radius: 25%; padding: 0.06em; border: calc(var(--u) * 0.5) solid transparent; } +.ss-avatars .ss-img.sel { border-color: var(--blue); background: #e8f1ff; transform: scale(1.12); } +.ss-namefield { font-size: calc(var(--u) * 5); min-height: 1.4em; min-width: 6em; border-bottom: calc(var(--u) * 0.5) solid var(--blue); display: inline-block; padding: 0 0.4em; margin-top: calc(var(--u) * 1); } +.ss-namefield::after { content: '|'; animation: ss-blink 1s steps(2) infinite; color: var(--blue); } +@keyframes ss-blink { 50% { opacity: 0; } } +.ss-big-letter { font-size: calc(var(--u) * 16); font-weight: 600; color: var(--blue); line-height: 1.1; animation: ss-bounce 1.2s ease-in-out infinite; } +.ss-books { display: flex; flex-wrap: wrap; gap: calc(var(--u) * 1.2); justify-content: center; max-width: calc(var(--u) * 80); align-items: flex-end; } +.ss-shelf-label { width: 100%; text-align: left; font-size: calc(var(--u) * 2.6); color: #98876a; margin-top: calc(var(--u) * 1); border-bottom: 2px solid #eee3c8; } +.ss-book-mini { width: calc(var(--u) * 10); padding: calc(var(--u) * 0.6); border-radius: calc(var(--u) * 1); border: calc(var(--u) * 0.4) solid transparent; font-size: calc(var(--u) * 2.4); display: flex; flex-direction: column; align-items: center; } +.ss-book-mini .ss-img { font-size: calc(var(--u) * 5.5); } +.ss-book-mini.shaky { opacity: 0.55; } +.ss-book-mini.sel { border-color: var(--blue); background: #e8f1ff; } +.ss-summary { display: flex; flex-wrap: wrap; gap: calc(var(--u) * 1.2); justify-content: center; font-size: calc(var(--u) * 6); } +.ss-day-book { display: inline-flex; flex-direction: column; align-items: center; font-size: calc(var(--u) * 6); } +.ss-day-book i { font-style: normal; font-size: calc(var(--u) * 2.2); color: #555; } + +/* ââ Phone portrait ââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ +.ss-root.ss-narrow .ss-shelf { width: calc(var(--u) * 14); } +.ss-root.ss-narrow .ss-window { width: calc(var(--u) * 46); } +.ss-root.ss-narrow .ss-queue { left: 2%; bottom: 30%; gap: calc(var(--u) * 1.2); } +.ss-root.ss-narrow .ss-patron { width: calc(var(--u) * 13); } +.ss-root.ss-narrow .ss-customer { right: 2%; bottom: 30%; } +.ss-root.ss-narrow .ss-desk { left: 50%; width: calc(var(--u) * 84); height: calc(var(--u) * 22); } +.ss-root.ss-narrow .ss-stamp { right: 2%; } +.ss-root.ss-narrow .ss-prompt { bottom: 2%; font-size: calc(var(--u) * 2.5); } +.ss-root.ss-narrow .ss-box { width: calc(var(--u) * 6.5); height: calc(var(--u) * 7.5); font-size: calc(var(--u) * 4.6); } +.ss-root.ss-narrow .ss-box.wide { width: calc(var(--u) * 10); } + +/* ââ Test mode: no waiting on animations âââââââââââââââââââââââââââââââââââ */ +.ss-root.ss-test, .ss-root.ss-test * { animation-duration: 0.01s !important; animation-delay: 0s !important; transition-duration: 0.01s !important; } diff --git a/js/main.js b/js/main.js @@ -4,7 +4,7 @@ const GAMES = [ { slug: 'animal-letter', title: 'Animal Letter', thumb: 'assets/thumbnails/animal-letter.svg', status: 'live' }, { slug: 'animal-spell', title: 'Animal Spell', thumb: 'assets/thumbnails/animal-spell.svg', status: 'live' }, { slug: 'work', title: 'Work', thumb: 'assets/thumbnails/work.svg', status: 'live' }, - { slug: 'sound-soup', title: 'Sound Soup', thumb: 'assets/thumbnails/sound-soup.svg', status: 'live' }, + { slug: 'story-stamp', title: 'Story Stamp', thumb: 'assets/thumbnails/story-stamp.svg', status: 'live' }, { slug: null, title: 'Coming Soon', thumb: 'assets/thumbnails/placeholder.svg', status: 'placeholder' }, ]; diff --git a/tests/browser/runner.js b/tests/browser/runner.js @@ -49,9 +49,9 @@ async function main() { const animalLetter = await page.$('a[href*="animal-letter"]'); const animalSpell = await page.$('a[href*="animal-spell"]'); const work = await page.$('a[href*="work"]'); - const soundSoup = await page.$('a[href*="sound-soup"]'); + const storyStamp = await page.$('a[href*="story-stamp"]'); if (!letterFind) throw new Error('Letter Find tile not found'); - if (!soundSoup) throw new Error('Sound Soup tile not found'); + if (!storyStamp) throw new Error('Story Stamp tile not found'); if (!fireTruck) throw new Error('Fire Truck tile not found'); if (!animalLetter) throw new Error('Animal Letter tile not found'); if (!animalSpell) throw new Error('Animal Spell tile not found'); @@ -842,9 +842,9 @@ async function main() { }); - // ââ Sound Soup (DOM game, no canvas) ââââââââââââââââââââââââââââââââââââââ + // ââ Story Stamp (DOM game, no canvas) âââââââââââââââââââââââââââââââââââââ - const SS_URL = `${url}/games/sound-soup/`; + const SS_URL = `${url}/games/story-stamp/`; const ssState = (page) => page.evaluate(() => window.__SS_GAME__ && window.__SS_GAME__.state); const ssWait = (page, st, ms) => page.waitForFunction((s) => window.__SS_GAME__ && window.__SS_GAME__.state === s, st, { timeout: ms || 8000 }) .catch(async () => { throw new Error(`state never became ${st} (now ${await ssState(page)})`); }); @@ -852,94 +852,114 @@ async function main() { await page.goto(SS_URL + ((opts && opts.query) || ''), { waitUntil: 'domcontentloaded' }); await page.waitForFunction(() => !!window.__SS_GAME__); } - async function ssQuickStart(page, name) { + // Start a profile and call the first patron in the queue: lands on the request slip. + async function ssQuickStart(page, name, pick) { await page.evaluate((n) => { window.__SS_GAME__.quickStart(n); }, name || 'Tester'); - await ssWait(page, 'cook'); + await ssWait(page, 'queue'); + if (pick != null) await page.keyboard.press(String(pick + 1)); else await page.keyboard.press('Enter'); + await ssWait(page, 'read', 15000); } - // Cook the current order end to end: type the letters, stir, pick the picture. - async function ssCookOrder(page) { + // Serve the current patron end to end: type the letters, sound it out, pick the book. + async function ssServe(page) { const o = await page.evaluate(() => ({ graphemes: window.__SS_GAME__.order.graphemes, word: window.__SS_GAME__.order.word.w, mode: window.__SS_GAME__.order.mode })); await page.keyboard.type(o.graphemes.join(''), { delay: 20 }); - await ssWait(page, 'stir'); + await ssWait(page, 'blend'); await page.keyboard.press('Space'); - await page.waitForFunction(() => ['choose', 'serve', 'arrive', 'cook', 'dayend'].includes(window.__SS_GAME__.state), null, { timeout: 15000 }); + await page.waitForFunction(() => ['choose', 'stamp', 'serve', 'arrive', 'queue', 'dayend'].includes(window.__SS_GAME__.state), null, { timeout: 15000 }); if ((await ssState(page)) === 'choose') { const idx = await page.evaluate((w) => window.__SS_GAME__.order.cards.findIndex((c) => c.w === w), o.word); await page.keyboard.press(String(idx + 1)); } - await page.waitForFunction(() => ['cook', 'dayend'].includes(window.__SS_GAME__.state), null, { timeout: 15000 }); + await page.waitForFunction(() => ['queue', 'dayend'].includes(window.__SS_GAME__.state), null, { timeout: 15000 }); return o; } - await test('sound-soup: loads, no JS errors, scene + profile screen render', async (page, url, errors) => { + await test('story-stamp: loads, no JS errors, library scene + profile screen render', async (page, url, errors) => { await ssOpen(page); await page.waitForTimeout(400); if (errors.length) throw new Error(errors[0]); - if (!(await page.$('.ss-root .ss-pot'))) throw new Error('scene not built'); + if (!(await page.$('.ss-root .ss-desk'))) throw new Error('scene not built'); if ((await ssState(page)) !== 'profiles') throw new Error('should start on the profile screen'); if (!(await page.isVisible('.ss-overlay'))) throw new Error('profile overlay hidden'); const imgs = await page.evaluate(() => Array.from(document.images).filter((i) => i.src.includes('/assets/img/')).map((i) => i.complete && i.naturalWidth > 0)); if (!imgs.length || imgs.includes(false)) throw new Error('scene pictures did not load'); }); - await test('sound-soup: keyboard alone creates a cook and the first customer arrives', async (page, url, errors) => { + await test('story-stamp: keyboard alone creates a librarian and a queue of patrons with topics appears', async (page, url, errors) => { await ssOpen(page); - await page.keyboard.press('Enter'); // "New cook" + await page.keyboard.press('Enter'); // "New librarian" if ((await ssState(page)) !== 'avatar') throw new Error('avatar step not reached'); await page.keyboard.press('ArrowRight'); await page.keyboard.press('Enter'); await page.keyboard.type('ada'); await page.keyboard.press('Enter'); - await ssWait(page, 'cook'); + await ssWait(page, 'queue'); const info = await page.evaluate(() => { const g = window.__SS_GAME__; - return { name: g.profile.name, boxes: document.querySelectorAll('.ss-box').length, n: g.order.graphemes.length, mode: g.order.mode, - stored: JSON.parse(localStorage.getItem('kg-sound-soup-v1')).profiles.length, cust: document.querySelector('.ss-cust-name').textContent }; + return { name: g.profile.name, patrons: document.querySelectorAll('.ss-patron').length, n: g.queue.length, + topics: new Set(g.queue.map((q) => q.topic.id)).size, signs: document.querySelectorAll('.ss-sign-card').length, + overlay: !document.querySelector('.ss-overlay').hidden && getComputedStyle(document.querySelector('.ss-overlay')).display !== 'none', + stored: JSON.parse(localStorage.getItem('kg-story-stamp-v1')).profiles.length }; }); if (info.name !== 'Ada') throw new Error(`name not captured: ${JSON.stringify(info)}`); - if (info.boxes !== info.n || info.n < 2) throw new Error(`sound boxes wrong: ${JSON.stringify(info)}`); - if (info.mode !== 'cook') throw new Error('first order must be cook mode'); + if (info.n < 2 || info.patrons !== info.n || info.signs !== info.n) throw new Error(`queue wrong: ${JSON.stringify(info)}`); + if (info.topics !== info.n) throw new Error('every patron should want a different topic'); + if (info.overlay) throw new Error('profile overlay should be gone once the desk opens'); if (info.stored !== 1) throw new Error('profile not saved to localStorage'); - if (!info.cust) throw new Error('no customer name shown'); + // Choosing a patron by arrow + Enter brings the request slip. + await page.keyboard.press('ArrowRight'); + const sel = await page.evaluate(() => window.__SS_GAME__.sel); + if (sel !== 1) throw new Error('arrow did not move the selection'); + await page.keyboard.press('Enter'); + await ssWait(page, 'read', 15000); + const o = await page.evaluate(() => ({ boxes: document.querySelectorAll('.ss-box').length, n: window.__SS_GAME__.order.graphemes.length, + mode: window.__SS_GAME__.order.mode, topic: window.__SS_GAME__.order.topic.id, t: window.__SS_GAME__.order.word.t, cust: document.querySelector('.ss-cust-name').textContent, + counted: window.__SS_GAME__.profile.topics })); + if (o.boxes !== o.n || o.n < 2) throw new Error(`sound boxes wrong: ${JSON.stringify(o)}`); + if (o.mode !== 'read') throw new Error('first book must be read mode'); + if (o.topic !== 'any' && !o.t.includes(o.topic)) throw new Error('book does not match the topic asked for'); + if (!o.cust) throw new Error('no patron name shown'); + if (!Object.keys(o.counted).length) throw new Error('topic choice not recorded'); if (errors.length) throw new Error(errors[0]); }); - await test('sound-soup: typing the word plays each sound, stirring blends them into the word', async (page, url, errors) => { + await test('story-stamp: stamping the letters plays each sound; reading it out never plays the whole word', async (page, url, errors) => { await ssOpen(page); await ssQuickStart(page); const o = await page.evaluate(() => ({ graphemes: window.__SS_GAME__.order.graphemes, units: window.__SS_GAME__.order.units, word: window.__SS_GAME__.order.word.w })); await page.evaluate(() => { window.__SS_GAME__.audio.log.length = 0; }); await page.keyboard.type(o.graphemes.join(''), { delay: 20 }); - await ssWait(page, 'stir'); - const typed = await page.evaluate(() => ({ blobs: document.querySelectorAll('.ss-blob').length, done: document.querySelectorAll('.ss-box.done').length, - played: window.__SS_GAME__.audio.log.filter((e) => e.kind === 'phoneme').map((e) => e.id) })); - if (typed.blobs !== o.graphemes.length) throw new Error(`expected ${o.graphemes.length} blobs in the pot, got ${typed.blobs}`); - if (typed.done !== o.graphemes.length) throw new Error('boxes not all filled'); + await ssWait(page, 'blend'); + const typed = await page.evaluate(() => ({ stamped: document.querySelectorAll('.ss-box.stamped').length, done: document.querySelectorAll('.ss-box.done').length, + played: window.__SS_GAME__.audio.log.filter((e) => e.kind === 'phoneme').map((e) => e.id), words: window.__SS_GAME__.audio.log.filter((e) => e.kind === 'word').length })); + if (typed.stamped !== o.graphemes.length || typed.done !== o.graphemes.length) throw new Error(`boxes not all stamped: ${JSON.stringify(typed)}`); const expect = [].concat(...o.units); if (JSON.stringify(typed.played) !== JSON.stringify(expect)) throw new Error(`phonemes played ${typed.played} != ${expect}`); + if (typed.words) throw new Error('the word must not be played while typing'); await page.evaluate(() => { window.__SS_GAME__.audio.log.length = 0; }); await page.keyboard.press('Space'); await ssWait(page, 'choose', 15000); // Clips are logged when scheduled; order them by when they actually sound. const blend = (await page.evaluate(() => window.__SS_GAME__.audio.log)).filter((e) => e.kind !== 'speak').sort((a, b) => a.at - b.at); const ph = blend.filter((e) => e.kind === 'phoneme'); - if (ph.length !== expect.length * 3) throw new Error(`blend should play every sound 3 times, played ${ph.length}`); - const lastWord = blend[blend.length - 1]; - if (lastWord.kind !== 'word' || lastWord.id !== o.word) throw new Error(`blend must end with the whole word: ${JSON.stringify(blend.map((e) => e.kind + ':' + e.id))}`); - const holds = ph.slice(0, expect.length).map((e) => e.hold); - if (!holds.some((h) => h > 0)) throw new Error('first pass should stretch sounds'); - if (!(await page.evaluate(() => document.querySelector('.ss-blobs').classList.contains('fused')))) throw new Error('blobs did not fuse'); - const cards = await page.evaluate(() => document.querySelectorAll('.ss-card').length); - if (cards !== 3) throw new Error(`expected 3 picture cards, got ${cards}`); + if (ph.length !== expect.length * 3) throw new Error(`sounding out should play every sound 3 times, played ${ph.length}`); + if (blend.some((e) => e.kind === 'word')) throw new Error('the whole word must never be played: the child has to say it'); + const first = ph.slice(0, expect.length), last = ph.slice(-expect.length); + const held = first.some((e, i) => e.len > last[i].len); + if (!held) throw new Error('first pass should hold sounds longer than the last'); + if (!(await page.evaluate(() => document.querySelector('.ss-boxes').classList.contains('fused')))) throw new Error('boxes did not fuse'); + const cards = await page.evaluate(() => document.querySelectorAll('.ss-book').length); + if (cards !== 3) throw new Error(`expected 3 books on the cart, got ${cards}`); const idx = await page.evaluate((w) => window.__SS_GAME__.order.cards.findIndex((c) => c.w === w), o.word); await page.keyboard.press(String(idx + 1)); - await ssWait(page, 'cook', 15000); + await page.waitForFunction(() => document.querySelector('.ss-book.stamped'), null, { timeout: 8000 }).catch(() => { throw new Error('book never got stamped'); }); + await ssWait(page, 'queue', 15000); const rec = await page.evaluate((w) => window.__SS_GAME__.profile.words[w], o.word); if (!rec || rec.streak !== 1) throw new Error(`word not recorded as first-try: ${JSON.stringify(rec)}`); if (errors.length) throw new Error(errors[0]); }); - await test('sound-soup: a wrong key gets a re-demonstration, never a fail state', async (page, url) => { + await test('story-stamp: a wrong key gets a re-demonstration, never a fail state', async (page, url) => { await ssOpen(page); await ssQuickStart(page); const o = await page.evaluate(() => ({ graphemes: window.__SS_GAME__.order.graphemes, first: window.__SS_GAME__.order.units[0] })); @@ -949,116 +969,143 @@ async function main() { await page.waitForFunction(() => window.__SS_GAME__.busy === false, null, { timeout: 5000 }); const after = await page.evaluate(() => ({ idx: window.__SS_GAME__.order.idx, missed: window.__SS_GAME__.order.missed, state: window.__SS_GAME__.state, log: window.__SS_GAME__.audio.log.map((e) => e.kind + ':' + e.id) })); - if (after.idx !== 0 || after.state !== 'cook') throw new Error(`wrong key changed progress: ${JSON.stringify(after)}`); + if (after.idx !== 0 || after.state !== 'read') throw new Error(`wrong key changed progress: ${JSON.stringify(after)}`); if (!after.missed) throw new Error('miss not recorded'); if (!after.log.includes('phoneme:' + o.first[0])) throw new Error(`expected sound re-demonstrated, log: ${after.log}`); await page.keyboard.type(o.graphemes.join(''), { delay: 20 }); - await ssWait(page, 'stir'); + await ssWait(page, 'blend'); }); - await test('sound-soup: a picture mix-up is corrected and the word is re-blended', async (page, url) => { + await test('story-stamp: a wrong book is corrected by re-reading; the third miss gives the word away', async (page, url) => { await ssOpen(page); await ssQuickStart(page); const o = await page.evaluate(() => ({ graphemes: window.__SS_GAME__.order.graphemes, word: window.__SS_GAME__.order.word.w })); await page.keyboard.type(o.graphemes.join(''), { delay: 20 }); - await ssWait(page, 'stir'); + await ssWait(page, 'blend'); await page.keyboard.press('Space'); await ssWait(page, 'choose', 15000); const wrongIdx = await page.evaluate((w) => window.__SS_GAME__.order.cards.findIndex((c) => c.w !== w), o.word); await page.evaluate(() => { window.__SS_GAME__.audio.log.length = 0; }); await page.keyboard.press(String(wrongIdx + 1)); await page.waitForFunction(() => window.__SS_GAME__.state === 'choose' && window.__SS_GAME__.busy === false, null, { timeout: 8000 }); - const log = await page.evaluate(() => window.__SS_GAME__.audio.log.filter((e) => e.kind === 'word').map((e) => e.id)); - if (log[log.length - 1] !== o.word) throw new Error(`target word not replayed after mix-up: ${log}`); + const log = await page.evaluate(() => window.__SS_GAME__.audio.log.map((e) => e.kind + ':' + e.id)); + if (log.some((e) => e === 'word:' + o.word)) throw new Error('the word must not be given away on the first miss'); + if (log.filter((e) => e.startsWith('phoneme:')).length < o.graphemes.length) throw new Error('sounds should be re-read after a miss'); + // Second miss: a wrong book is taken away. Third miss: the word is said and the book pointed out. + let again = await page.evaluate((w) => window.__SS_GAME__.order.cards.findIndex((c) => c.w !== w), o.word); + await page.keyboard.press(String(again + 1)); + await page.waitForFunction(() => window.__SS_GAME__.state === 'choose' && window.__SS_GAME__.busy === false, null, { timeout: 8000 }); + const two = await page.evaluate(() => window.__SS_GAME__.order.cards.length); + if (two !== 2) throw new Error(`expected 2 books after the second miss, got ${two}`); + again = await page.evaluate((w) => window.__SS_GAME__.order.cards.findIndex((c) => c.w !== w), o.word); + await page.evaluate(() => { window.__SS_GAME__.audio.log.length = 0; }); + await page.keyboard.press(String(again + 1)); + await page.waitForFunction(() => window.__SS_GAME__.state === 'choose' && window.__SS_GAME__.busy === false, null, { timeout: 8000 }); + const help = await page.evaluate((w) => ({ said: window.__SS_GAME__.audio.log.some((e) => e.kind === 'word' && e.id === w), pointed: !!document.querySelector('.ss-book.point') }), o.word); + if (!help.said || !help.pointed) throw new Error(`third miss should give the word and point at the book: ${JSON.stringify(help)}`); const rightIdx = await page.evaluate((w) => window.__SS_GAME__.order.cards.findIndex((c) => c.w === w), o.word); await page.keyboard.press(String(rightIdx + 1)); - await ssWait(page, 'cook', 15000); + await ssWait(page, 'queue', 15000); const rec = await page.evaluate((w) => window.__SS_GAME__.profile.words[w], o.word); if (rec.streak !== 0 || rec.lapses !== 1) throw new Error(`mix-up should count as a lapse: ${JSON.stringify(rec)}`); }); - await test('sound-soup: six orders make a day, then the stall reopens', async (page, url, errors) => { + await test('story-stamp: six books make a day, then the library reopens', async (page, url, errors) => { await ssOpen(page); await ssQuickStart(page); const words = []; for (let i = 0; i < 6; i++) { - words.push((await ssCookOrder(page)).word); + words.push((await ssServe(page)).word); const st = await ssState(page); - if (i < 5 && st !== 'cook') throw new Error(`order ${i + 1}: expected the next customer, got ${st}`); + if (i < 5) { + if (st !== 'queue') throw new Error(`book ${i + 1}: expected the queue, got ${st}`); + await page.keyboard.press('Enter'); + await ssWait(page, 'read', 15000); + } } - if ((await ssState(page)) !== 'dayend') throw new Error('day did not end after six orders'); + if ((await ssState(page)) !== 'dayend') throw new Error('day did not end after six books'); const dots = await page.evaluate(() => document.querySelectorAll('.ss-day span.on').length); if (dots !== 6) throw new Error(`day progress shows ${dots}/6`); await page.keyboard.press('Enter'); - await ssWait(page, 'cook', 15000); + await ssWait(page, 'queue', 15000); const p = await page.evaluate(() => ({ orders: window.__SS_GAME__.profile.orders, introduced: window.__SS_GAME__.profile.introduced.length })); if (p.orders !== 6) throw new Error(`profile.orders = ${p.orders}`); - if (p.introduced < 7) throw new Error('six clean orders should have introduced a new sound'); + if (p.introduced < 7) throw new Error('six clean books should have introduced a new sound'); if (errors.length) throw new Error(errors[0]); }); - await test('sound-soup: take-the-order mode says the word, hides the letters, and is typed by ear', async (page, url) => { + await test('story-stamp: a return says the word, hides the letters, and is typed by ear', async (page, url) => { await ssOpen(page); await page.evaluate(() => { const g = window.__SS_GAME__, SS = window.SS, now = Date.now(); const p = SS.newProfile('Pro', SS.AVATARS[1], now - 10 * SS.DAY, 'pro'); - ['tap', 'pin', 'pan', 'ant', 'nap', 'pants'].forEach((w) => { SS.recordOrder(p, w, true, now - 9 * SS.DAY); SS.recordOrder(p, w, true, now - 8 * SS.DAY); }); + ['tap', 'pin', 'pan', 'ant', 'snap', 'pants'].forEach((w) => { SS.recordOrder(p, w, true, now - 9 * SS.DAY); SS.recordOrder(p, w, true, now - 8 * SS.DAY); }); g.store.profiles.push(p); g.save(); - window.__SS_FORCE_MODE__ = 'order'; + window.__SS_FORCE_MODE__ = 'return'; g.quickStart('Pro'); }); - await ssWait(page, 'cook'); + await ssWait(page, 'queue'); + const idx = await page.evaluate(() => window.__SS_GAME__.queue.findIndex((q) => q.mode === 'return')); + if (idx < 0) throw new Error('no return in the queue'); + await page.keyboard.press(String(idx + 1)); + await ssWait(page, 'read', 15000); const o = await page.evaluate(() => ({ mode: window.__SS_GAME__.order.mode, graphemes: window.__SS_GAME__.order.graphemes, word: window.__SS_GAME__.order.word.w, hidden: document.querySelectorAll('.ss-box.hidden-letter').length, pic: !!document.querySelector('.ss-bubble img'), said: window.__SS_GAME__.audio.log.some((e) => e.kind === 'word' && e.id === window.__SS_GAME__.order.word.w) })); - if (o.mode !== 'order') throw new Error('mode not forced to order'); - if (o.hidden !== o.graphemes.length) throw new Error('letters should be hidden in order mode'); - if (!o.pic) throw new Error('customer should show the picture'); + if (o.mode !== 'return') throw new Error('mode not forced to return'); + if (o.hidden !== o.graphemes.length) throw new Error('letters should be hidden in return mode'); + if (!o.pic) throw new Error('patron should show the book'); if (!o.said) throw new Error('the word should have been played before typing'); await page.keyboard.type(o.graphemes.join(''), { delay: 20 }); - await ssWait(page, 'stir'); + await ssWait(page, 'blend'); await page.keyboard.press('Space'); - await ssWait(page, 'cook', 15000); // no picture check in order mode: straight to serving + await ssWait(page, 'queue', 15000); // no book choice for a return: straight back to the shelf const rec = await page.evaluate((w) => window.__SS_GAME__.profile.words[w], o.word); if (rec.reps !== 3) throw new Error(`review not recorded: ${JSON.stringify(rec)}`); }); - await test('sound-soup: progress survives a reload and due words return as regulars', async (page, url) => { + await test('story-stamp: progress survives a reload and due words return as regulars', async (page, url) => { await ssOpen(page); await ssQuickStart(page, 'Keep'); - const first = await ssCookOrder(page); + const first = await ssServe(page); await page.goto(SS_URL, { waitUntil: 'domcontentloaded' }); await page.waitForFunction(() => !!window.__SS_GAME__); - await page.keyboard.press('Enter'); // last cook is preselected - await ssWait(page, 'cook'); + await page.keyboard.press('Enter'); // last librarian is preselected + await ssWait(page, 'queue'); const back = await page.evaluate(() => ({ name: window.__SS_GAME__.profile.name, words: Object.keys(window.__SS_GAME__.profile.words) })); if (back.name !== 'Keep' || !back.words.includes(first.word)) throw new Error(`progress lost: ${JSON.stringify(back)}`); - // Jump two days ahead: the served word is due and comes back as a review. + // Jump two days ahead: the served word is due and its regular comes back. const review = await page.evaluate((w) => { const g = window.__SS_GAME__, SS = window.SS; window.__SS_NOW__ = Date.now() + 2 * SS.DAY; - return SS.dueWords(g.profile, g.now()).includes(w); + let queued = false; + for (let seed = 1; seed <= 12 && !queued; seed++) { + const q = SS.buildQueue(g.profile, g.now(), SS.mulberry32(seed), 3); + queued = q.some((e) => e.word.w === w && e.review && e.patron.id === SS.patronFor(w).id); + } + return { due: SS.dueWords(g.profile, g.now()).includes(w), queued }; }, first.word); - if (!review) throw new Error('served word should be due for review two days later'); + if (!review.due) throw new Error('served word should be due for review two days later'); + if (!review.queued) throw new Error('the due word should come back with its regular'); }); - await test('sound-soup: menu and recipe book open from Backspace and close again', async (page, url) => { + await test('story-stamp: menu and shelf open from Backspace and close again', async (page, url) => { await ssOpen(page); await ssQuickStart(page); await page.keyboard.press('Backspace'); if ((await ssState(page)) !== 'menu') throw new Error('menu did not open'); - await page.keyboard.press('Enter'); // Recipe book - if ((await ssState(page)) !== 'book') throw new Error('book did not open'); - if (!(await page.evaluate(() => document.querySelector('.ss-panel').textContent.includes('No recipes yet')))) throw new Error('empty book message missing'); + await page.keyboard.press('Enter'); // My shelf + if ((await ssState(page)) !== 'shelf') throw new Error('shelf did not open'); + if (!(await page.evaluate(() => document.querySelector('.ss-panel').textContent.includes('No books yet')))) throw new Error('empty shelf message missing'); await page.keyboard.press('Backspace'); - if ((await ssState(page)) !== 'cook') throw new Error('book did not close back to cooking'); + if ((await ssState(page)) !== 'read') throw new Error('shelf did not close back to the desk'); await page.keyboard.press('Backspace'); await page.keyboard.press('ArrowDown'); - await page.keyboard.press('Enter'); // Change cook - if ((await ssState(page)) !== 'profiles') throw new Error('change cook did not return to profiles'); + await page.keyboard.press('Enter'); // Change librarian + if ((await ssState(page)) !== 'profiles') throw new Error('change librarian did not return to profiles'); }); - await test('sound-soup: a cook can be removed from the profile screen', async (page, url) => { + await test('story-stamp: a librarian can be removed from the profile screen', async (page, url) => { await ssOpen(page); await ssQuickStart(page, 'Gone'); await page.keyboard.press('Backspace'); await page.keyboard.press('ArrowDown'); await page.keyboard.press('Enter'); @@ -1069,15 +1116,31 @@ async function main() { if (left !== 0) throw new Error(`profile not removed: ${left} left`); }); + await test('story-stamp: phone portrait keeps the desk, queue and slip inside the screen', async (page, url) => { + await page.setViewportSize({ width: 390, height: 780 }); + await ssOpen(page); + await ssQuickStart(page); + const box = await page.evaluate(() => { + const r = (sel) => { const b = document.querySelector(sel).getBoundingClientRect(); return { l: b.left, r: b.right, t: b.top, b: b.bottom }; }; + return { w: innerWidth, h: innerHeight, slip: r('.ss-slip'), desk: r('.ss-desk'), cust: r('.ss-customer') }; + }); + for (const k of ['slip', 'desk', 'cust']) { + const b = box[k]; + if (b.l < -2 || b.r > box.w + 2 || b.t < 0 || b.b > box.h + 2) throw new Error(`${k} is off screen: ${JSON.stringify(b)} in ${box.w}x${box.h}`); + } + }); + - await test('touch: sound-soup on-screen keyboard types a sound and has arrow keys', async (page, url) => { + await test('touch: story-stamp on-screen keyboard types a sound and has arrow keys', async (page, url) => { await page.addInitScript(() => { window.__FORCE_TOUCH_CONTROLS__ = true; }); - await page.goto(`${url}/games/sound-soup/`, { waitUntil: 'domcontentloaded' }); + await page.goto(`${url}/games/story-stamp/`, { waitUntil: 'domcontentloaded' }); await page.waitForSelector('#kg-touch'); await page.waitForFunction(() => !!window.__SS_GAME__); for (const label of ['â', 'â˛', 'âź', 'âś', 'â']) await touchKey(page, label); await page.evaluate(() => { window.__SS_GAME__.quickStart('Tap'); }); - await page.waitForFunction(() => window.__SS_GAME__.state === 'cook', null, { timeout: 8000 }); + await page.waitForFunction(() => window.__SS_GAME__.state === 'queue', null, { timeout: 8000 }); + await (await touchKey(page, 'â')).tap(); + await page.waitForFunction(() => window.__SS_GAME__.state === 'read', null, { timeout: 15000 }); const first = await page.evaluate(() => window.__SS_GAME__.order.graphemes[0][0].toUpperCase()); const key = await touchKey(page, first); await key.tap(); @@ -1091,7 +1154,7 @@ async function main() { // Every shell is a fixed-height, non-scrolling flex column. If the canvas // refuses to shrink (flex items default to min-height:auto), whatever sits // below it is pushed past the fold with no way to scroll to it. - const SHELLS = ['letter-find', 'fire-truck', 'animal-letter', 'animal-spell', 'work', 'sound-soup']; + const SHELLS = ['letter-find', 'fire-truck', 'animal-letter', 'animal-spell', 'work', 'story-stamp']; for (const slug of SHELLS) { await test(`${slug}: page does not overflow the viewport`, async (page, url) => { await page.setViewportSize({ width: 1000, height: 700 }); @@ -1246,19 +1309,20 @@ async function main() { }, { hasTouch: true }); - await test('audio: sound-soup audio context unlocks from a physical key under Safari policy', async (page, url) => { + await test('audio: story-stamp audio context unlocks from a physical key under Safari policy', async (page, url) => { await page.addInitScript(emulateSafariAutoplayPolicy); - await page.goto(`${url}/games/sound-soup/`, { waitUntil: 'domcontentloaded' }); + await page.goto(`${url}/games/story-stamp/`, { waitUntil: 'domcontentloaded' }); await page.waitForFunction(() => !!window.__SS_GAME__); await page.evaluate(() => { window.__SS_GAME__.quickStart('Aud'); }); - await page.waitForFunction(() => window.__SS_GAME__.state === 'cook', null, { timeout: 8000 }); + await page.waitForFunction(() => window.__SS_GAME__.state === 'queue', null, { timeout: 8000 }); const before = await page.evaluate(() => ({ ready: window.__SS_GAME__.audio.ready, state: window.__SS_GAME__.audio.ctx && window.__SS_GAME__.audio.ctx.state })); if (before.ready || before.state === 'running') throw new Error(`context should be blocked before a gesture: ${JSON.stringify(before)}`); await page.keyboard.press('Space'); // a trusted key press = activation await page.waitForFunction(() => window.__SS_GAME__.audio.ready === true, null, { timeout: 4000 }) .catch(() => { throw new Error('audio never became ready from a key press'); }); - const clips = await page.evaluate(() => Object.keys(window.__SS_GAME__.audio.buffers).filter((k) => k.startsWith('ph:') && window.__SS_GAME__.audio.buffers[k]).length); - if (clips < 25) throw new Error(`only ${clips} phoneme clips decoded`); + const decoded = () => Object.keys(window.__SS_GAME__.audio.buffers).filter((k) => k.startsWith('ph:') && window.__SS_GAME__.audio.buffers[k]).length; + await page.waitForFunction(decoded.toString().replace('() =>', '() => 30 <='), null, { timeout: 8000 }) + .catch(async () => { throw new Error(`only ${await page.evaluate(decoded)} phoneme clips decoded`); }); }); // ââ Summary âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ diff --git a/tests/serve.js b/tests/serve.js @@ -9,6 +9,9 @@ const MIME = { '.css': 'text/css', '.svg': 'image/svg+xml', '.png': 'image/png', + '.webp': 'image/webp', + '.mp3': 'audio/mpeg', + '.md': 'text/markdown', }; function serve(port) { diff --git a/tests/unit/sound-soup-assets.test.js b/tests/unit/sound-soup-assets.test.js @@ -1,47 +0,0 @@ -// The committed Sound Soup assets must match the content in lib.js: every -// phoneme has a recorded clip, every picture exists, every clip is credited. -const { test } = require('node:test'); -const assert = require('node:assert'); -const fs = require('fs'); -const path = require('path'); -const vm = require('vm'); -const SS = require('../../games/sound-soup/lib.js'); - -const GAME = path.join(__dirname, '..', '..', 'games', 'sound-soup'); -const sandbox = { window: {} }; -vm.runInNewContext(fs.readFileSync(path.join(GAME, 'assets', 'manifest.js'), 'utf8'), sandbox); -const M = sandbox.window.SS_ASSETS; - -test('manifest lists a recorded clip for every phoneme, and the files exist', () => { - for (const p of Object.keys(SS.PHONEMES)) { - assert.ok(M.phonemes[p], `phoneme ${p} has no recording`); - const f = path.join(GAME, 'assets', 'audio', M.phonemes[p].file); - assert.ok(fs.existsSync(f), f); - assert.ok(M.phonemes[p].dur > 0.03 && M.phonemes[p].dur < 1.0, `${p}: ${M.phonemes[p].dur}s`); - } -}); - -test('most words have recordings and every listed word file exists', () => { - const have = SS.WORDS.filter((w) => M.words[w.w]).length; - assert.ok(have / SS.WORDS.length > 0.85, `${have}/${SS.WORDS.length} words recorded`); - for (const w of Object.keys(M.words)) { - assert.ok(SS.WORD_BY_NAME[w], `manifest word ${w} not in lib`); - assert.ok(fs.existsSync(path.join(GAME, 'assets', 'audio', M.words[w].file))); - } -}); - -test('every word and customer picture is present as a local SVG', () => { - const need = SS.WORDS.map((w) => w.emoji).concat(SS.CUSTOMERS.map((c) => c.emoji), SS.CUSTOMERS.filter((c) => c.hat).map((c) => c.hat), SS.AVATARS); - for (const cp of need) { - assert.ok(M.images[cp], `no image for ${cp}`); - assert.ok(fs.existsSync(path.join(GAME, 'assets', M.images[cp])), cp); - } -}); - -test('CREDITS.md attributes every audio source used', () => { - const credits = fs.readFileSync(path.join(GAME, 'CREDITS.md'), 'utf8'); - for (const p of Object.keys(SS.PHONEMES)) assert.ok(credits.includes(`phoneme /${p}/`), `credit for /${p}/`); - for (const w of Object.keys(M.words)) assert.ok(credits.includes(`word "${w}"`), `credit for ${w}`); - assert.ok(credits.includes('Twemoji')); - assert.ok(credits.includes('CC BY-SA 3.0')); -}); diff --git a/tests/unit/sound-soup-lib.test.js b/tests/unit/sound-soup-lib.test.js @@ -1,249 +0,0 @@ -// Unit tests for games/sound-soup/lib.js â phonics content, scheduler, blend -// timeline, profile store. No browser, no audio. -const { test } = require('node:test'); -const assert = require('node:assert'); -const SS = require('../../games/sound-soup/lib.js'); - -const DAY = SS.DAY; -const NOW = Date.UTC(2026, 8, 14, 9, 0, 0); - -function profile(name) { return SS.newProfile(name || 'Test', SS.AVATARS[0], NOW, 'p1'); } - -// ââ Content integrity ââââââââââââââââââââââââââââââââââââââââââââââââââââââ - -test('every word splits into known graphemes and phonemes', () => { - for (const w of SS.WORDS) { - const gs = SS.graphemes(w); - assert.strictEqual(gs.join(''), w.w, `${w.w}: graphemes must spell the word`); - const units = SS.phonemeUnits(w); - assert.strictEqual(units.length, gs.length, `${w.w}: one phoneme unit per grapheme`); - for (const unit of units) for (const p of unit) assert.ok(SS.PHONEMES[p], `${w.w}: unknown phoneme ${p}`); - for (const g of gs) assert.ok(SS.SEQUENCE.includes(SS.baseGrapheme(g)), `${w.w}: grapheme ${g} is not in the teaching sequence`); - assert.match(w.emoji, /^[0-9a-f-]+$/, `${w.w}: emoji must be a Twemoji codepoint file name`); - } -}); - -test('word list has no duplicates and every sequence grapheme unlocks something', () => { - const names = SS.WORDS.map((w) => w.w); - assert.strictEqual(new Set(names).size, names.length); - const known = []; - let prev = 0; - for (const g of SS.SEQUENCE) { - known.push(g); - const n = SS.availableWords(known).length; - assert.ok(n >= prev, `pool never shrinks (${g})`); - prev = n; - } - assert.strictEqual(prev, SS.WORDS.length, 'the full sequence unlocks every word'); - assert.ok(SS.availableWords(SS.SEQUENCE.slice(0, SS.STARTING_GRAPHEMES)).length >= 4, 'the opening six letters give a few words'); -}); - -test('doubled consonants and ck resolve to the single sound', () => { - assert.deepStrictEqual(SS.graphemePhonemes('ll'), ['l']); - assert.deepStrictEqual(SS.graphemePhonemes('ss'), ['s']); - assert.deepStrictEqual(SS.graphemePhonemes('ck'), ['k']); - assert.deepStrictEqual(SS.graphemePhonemes('x'), ['k', 's']); - assert.strictEqual(SS.baseGrapheme('ll'), 'l'); - assert.strictEqual(SS.baseGrapheme('ck'), 'ck'); - assert.throws(() => SS.graphemePhonemes('q')); -}); - -test('phoneme override keeps book as short oo', () => { - assert.deepStrictEqual(SS.phonemeUnits(SS.WORD_BY_NAME.book), [['b'], ['uu'], ['k']]); - assert.deepStrictEqual(SS.phonemeUnits(SS.WORD_BY_NAME.moon), [['m'], ['oo'], ['n']]); -}); - -test('customers are deterministic per word and cover the cast', () => { - assert.strictEqual(SS.customerFor('cat'), SS.customerFor('cat')); - const ids = new Set(SS.WORDS.map((w) => SS.customerFor(w.w).id)); - assert.ok(ids.size >= SS.CUSTOMERS.length - 2, 'most regulars get at least one favourite dish'); -}); - -// ââ Availability / introduction ââââââââââââââââââââââââââââââââââââââââââââ - -test('a new profile knows the first six sounds and has words to cook', () => { - const p = profile(); - assert.deepStrictEqual(p.introduced, ['s', 'a', 't', 'p', 'i', 'n']); - assert.ok(SS.unseenWords(p).includes('tap')); - assert.ok(!SS.unseenWords(p).includes('cat'), 'c is not known yet'); - assert.strictEqual(SS.nextGrapheme(p), 'm'); -}); - -test('a new sound is introduced after fluent orders, never while stumbling', () => { - const p = profile(); - assert.strictEqual(SS.shouldIntroduce(p), false, 'nothing yet'); - ['tap', 'pin', 'pan'].forEach((w) => SS.recordOrder(p, w, true, NOW)); - assert.strictEqual(SS.shouldIntroduce(p), false, 'needs at least four recent orders'); - SS.recordOrder(p, 'ant', true, NOW); - assert.strictEqual(SS.shouldIntroduce(p), true, 'four first-try orders in a row'); - assert.strictEqual(SS.introduceNext(p), 'm'); - assert.strictEqual(p.sinceIntro, 0); - assert.strictEqual(SS.shouldIntroduce(p), false, 'just introduced'); - - const q = profile(); - ['tap', 'pin', 'pan', 'ant'].forEach((w) => SS.recordOrder(q, w, false, NOW)); - assert.strictEqual(SS.shouldIntroduce(q), false, 'struggling: no new sound'); -}); - -test('the pool never starves: an empty unseen pool forces a new sound', () => { - const p = profile(); - SS.unseenWords(p).forEach((w) => SS.recordOrder(p, w, false, NOW)); - assert.strictEqual(SS.unseenWords(p).length, 0); - assert.strictEqual(SS.shouldIntroduce(p), true); -}); - -test('the sequence eventually runs out and stops introducing', () => { - const p = profile(); - while (SS.nextGrapheme(p)) SS.introduceNext(p); - assert.strictEqual(SS.shouldIntroduce(p), false); - assert.strictEqual(SS.availableWords(p.introduced).length, SS.WORDS.length); -}); - -// ââ Spaced repetition ââââââââââââââââââââââââââââââââââââââââââââââââââââââ - -test('first-try orders grow the interval; a stumble resets it to soon', () => { - const p = profile(); - let rec = SS.recordOrder(p, 'tap', true, NOW); - assert.strictEqual(rec.interval, 1); - assert.strictEqual(rec.due, NOW + DAY); - assert.strictEqual(SS.isMastered(rec), false); - rec = SS.recordOrder(p, 'tap', true, NOW + DAY); - assert.strictEqual(rec.interval, 3); - assert.strictEqual(SS.isMastered(rec), true, 'two clean serves = mastered'); - rec = SS.recordOrder(p, 'tap', false, NOW + 4 * DAY); - assert.strictEqual(rec.streak, 0); - assert.strictEqual(rec.lapses, 1); - assert.ok(rec.due <= NOW + 4 * DAY + 10 * SS.MIN, 'comes back within minutes'); - assert.strictEqual(SS.isMastered(rec), false); - assert.deepStrictEqual(SS.shakyWords(p), ['tap']); -}); - -test('due words surface as reviews once their interval passes', () => { - const p = profile(); - SS.recordOrder(p, 'tap', true, NOW); - SS.recordOrder(p, 'pin', true, NOW); - assert.deepStrictEqual(SS.dueWords(p, NOW + 1000), []); - assert.deepStrictEqual(SS.dueWords(p, NOW + DAY + 1), ['tap', 'pin']); - const rng = SS.mulberry32(7); - let reviews = 0; - for (let i = 0; i < 20; i++) { - const n = SS.pickNext(p, NOW + DAY + 1, rng); - if (n.review) reviews++; - } - assert.ok(reviews > 5, 'due words come back as regulars'); -}); - -test('pickNext never repeats the last word and prefers the newest sound for fresh words', () => { - const p = profile(); - const rng = SS.mulberry32(3); - let last = null; - for (let i = 0; i < 30; i++) { - const n = SS.pickNext(p, NOW, rng); - assert.notStrictEqual(n.word.w, last, 'no immediate repeat'); - assert.ok(SS.wordIsAvailable(n.word, p.introduced), 'only known sounds'); - assert.strictEqual(n.mode, 'cook', 'order mode is locked until words are mastered'); - SS.recordOrder(p, n.word.w, true, NOW); - last = n.word.w; - } - SS.introduceNext(p); // 'm' - const fresh = SS.pickNext(p, NOW, rng); - assert.ok(fresh.word.w.includes('m'), `fresh word should use the new sound, got ${fresh.word.w}`); -}); - -test('order (encoding) mode unlocks after enough mastered words, only for reviews', () => { - const p = profile(); - const words = ['tap', 'pin', 'pan', 'ant', 'nap', 'pants']; - words.forEach((w) => { SS.recordOrder(p, w, true, NOW); SS.recordOrder(p, w, true, NOW + DAY); }); - assert.ok(SS.masteredWords(p).length >= SS.ORDER_MODE_UNLOCK); - const rng = SS.mulberry32(11); - const modes = new Set(); - for (let i = 0; i < 40; i++) { - const n = SS.pickNext(p, NOW + 4 * DAY, rng); - modes.add(n.mode); - if (n.mode === 'order') assert.ok(p.words[n.word.w], 'order mode only for words already met'); - } - assert.ok(modes.has('order') && modes.has('cook')); -}); - -test('distractors are distinct words with distinct pictures', () => { - const p = profile(); - const rng = SS.mulberry32(5); - for (const w of SS.WORDS) { - const d = SS.distractors(w, p, rng); - assert.strictEqual(d.length, 2, w.w); - assert.ok(d.every((x) => x.w !== w.w && x.emoji !== w.emoji), w.w); - assert.notStrictEqual(d[0].w, d[1].w); - } -}); - -test('grapheme stats count hits and misses by base grapheme', () => { - const p = profile(); - SS.recordGrapheme(p, 'll', true); - SS.recordGrapheme(p, 'l', false); - assert.deepStrictEqual(p.graphemes.l, { hits: 1, misses: 1 }); -}); - -test('a saved profile with a word that no longer exists keeps working', () => { - const p = profile(); - SS.recordOrder(p, 'tap', true, NOW); - SS.recordOrder(p, 'tap', true, NOW + DAY); - p.words.zork = { reps: 2, lapses: 0, streak: 2, interval: 3, due: NOW - DAY, last: NOW - 2 * DAY, seen: 2 }; - assert.deepStrictEqual(SS.masteredWords(p), ['tap']); - assert.deepStrictEqual(SS.dueWords(p, NOW + 5 * DAY), ['tap']); - const rng = SS.mulberry32(2); - for (let i = 0; i < 20; i++) { - const n = SS.pickNext(p, NOW + 5 * DAY, rng); - assert.ok(n.word && n.word.w !== 'zork'); - assert.strictEqual(SS.distractors(n.word, p, rng).length, 2); - } -}); - -// ââ Stall + blend ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ - -test('stall upgrades accumulate with mastered words', () => { - assert.deepStrictEqual(SS.stallUpgrades(0), []); - assert.deepStrictEqual(SS.stallUpgrades(2).map((u) => u.id), ['awning']); - assert.ok(SS.stallUpgrades(100).length === SS.STALL_UPGRADES.length); -}); - -test('blend timeline: gaps shrink pass by pass, stops are never stretched, word comes last', () => { - const units = SS.phonemeUnits(SS.WORD_BY_NAME.sat || SS.WORD_BY_NAME.cat); - const durs = { k: 0.06, a: 0.45, t: 0.06 }; - const ev = SS.blendTimeline(units, durs); - const passes = ev.filter((e) => e.kind === 'pass'); - assert.strictEqual(passes.length, SS.BLEND_PASSES.length); - const gapOf = (pi) => { - const ph = ev.filter((e) => e.kind === 'phoneme' && e.pass === pi); - return ph[1].t - (ph[0].t + ph[0].len); - }; - assert.ok(gapOf(0) > gapOf(1) && gapOf(1) > gapOf(2), 'gaps shrink'); - assert.ok(Math.abs(gapOf(2)) < 1e-9, 'last pass is fused'); - for (const e of ev.filter((e) => e.kind === 'phoneme')) { - if (SS.PHONEMES[e.id].type === 'stop') assert.strictEqual(e.len, durs[e.id], 'stops keep their natural length'); - else if (e.pass === 0) assert.ok(e.len > durs[e.id], 'continuants are stretched in pass 1'); - } - const last = ev[ev.length - 1]; - assert.strictEqual(last.kind, 'word'); - assert.ok(ev.every((e) => e.t <= last.t), 'monotonic'); - for (let i = 1; i < ev.length; i++) assert.ok(ev[i].t >= ev[i - 1].t); -}); - -// ââ Store ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ - -test('profile store round-trips through a storage object and tolerates junk', () => { - const mem = {}; - const storage = { getItem: (k) => (k in mem ? mem[k] : null), setItem: (k, v) => { mem[k] = v; }, removeItem: (k) => { delete mem[k]; } }; - const empty = SS.loadStore(storage); - assert.deepStrictEqual(empty.profiles, []); - empty.profiles.push(profile('Ada')); - empty.last = 'p1'; - SS.saveStore(storage, empty); - const back = SS.loadStore(storage); - assert.strictEqual(back.profiles[0].name, 'Ada'); - assert.strictEqual(back.last, 'p1'); - mem[SS.STORE_KEY] = '{not json'; - assert.deepStrictEqual(SS.loadStore(storage).profiles, []); - const throwing = { getItem() { throw new Error('nope'); }, setItem() { throw new Error('nope'); } }; - assert.doesNotThrow(() => SS.saveStore(throwing, empty)); - assert.deepStrictEqual(SS.loadStore(throwing).profiles, []); -}); diff --git a/tests/unit/story-stamp-assets.test.js b/tests/unit/story-stamp-assets.test.js @@ -0,0 +1,56 @@ +// The committed Story Stamp assets must match the content in lib.js: every +// phoneme has a recorded clip of the right kind of length, every picture +// exists, every clip is credited. +const { test } = require('node:test'); +const assert = require('node:assert'); +const fs = require('fs'); +const path = require('path'); +const vm = require('vm'); +const SS = require('../../games/story-stamp/lib.js'); + +const GAME = path.join(__dirname, '..', '..', 'games', 'story-stamp'); +const sandbox = { window: {} }; +vm.runInNewContext(fs.readFileSync(path.join(GAME, 'assets', 'manifest.js'), 'utf8'), sandbox); +const M = sandbox.window.SS_ASSETS; + +test('manifest lists a recorded clip for every phoneme, and the files exist', () => { + for (const p of Object.keys(SS.PHONEMES)) { + assert.ok(M.phonemes[p], `phoneme ${p} has no recording`); + const f = path.join(GAME, 'assets', 'audio', M.phonemes[p].file); + assert.ok(fs.existsSync(f), f); + const dur = M.phonemes[p].dur; + const ph = SS.PHONEMES[p]; + if (ph.stretch) assert.ok(dur >= 0.8 && dur <= 1.2, `${p} is a held sound and should last about a second: ${dur}s`); + else if (ph.type === 'stop') assert.ok(dur >= 0.06 && dur <= 0.2, `${p} is a stop and should be short but audible: ${dur}s`); + else assert.ok(dur >= 0.12 && dur <= 0.8, `${p}: ${dur}s`); + } +}); + +test('most words have recordings and every listed word file exists', () => { + const have = SS.WORDS.filter((w) => M.words[w.w]).length; + assert.ok(have / SS.WORDS.length > 0.8, `${have}/${SS.WORDS.length} words recorded`); + for (const w of Object.keys(M.words)) { + assert.ok(SS.WORD_BY_NAME[w], `manifest word ${w} not in lib`); + assert.ok(fs.existsSync(path.join(GAME, 'assets', 'audio', M.words[w].file))); + } +}); + +test('every word, topic, patron, avatar and scene picture is present as a local WebP', () => { + const need = new Set(SS.WORDS.map((w) => w.art).concat(SS.TOPICS.map((t) => t.art), [SS.ANY_TOPIC.art], SS.PATRONS.map((c) => c.art), SS.AVATARS, SS.UI_ART)); + for (const name of need) { + assert.ok(M.images[name], `no image for ${name}`); + assert.ok(fs.existsSync(path.join(GAME, 'assets', M.images[name])), name); + } + // Every art name the game's scene code uses is registered in lib.js. + const game = fs.readFileSync(path.join(GAME, 'game.js'), 'utf8'); + for (const m of game.matchAll(/img\('([^']+)'/g)) assert.ok(need.has(m[1]), `game.js uses unregistered art: ${m[1]}`); +}); + +test('CREDITS.md attributes every audio source used', () => { + const credits = fs.readFileSync(path.join(GAME, 'CREDITS.md'), 'utf8'); + for (const p of Object.keys(SS.PHONEMES)) assert.ok(credits.includes(`phoneme /${p}/`), `credit for /${p}/`); + for (const w of Object.keys(M.words)) assert.ok(credits.includes(`word "${w}"`), `credit for ${w}`); + assert.ok(credits.includes('Fluent Emoji')); + assert.ok(credits.includes('MIT')); + assert.ok(credits.includes('CC BY-SA 3.0')); +}); diff --git a/tests/unit/story-stamp-lib.test.js b/tests/unit/story-stamp-lib.test.js @@ -0,0 +1,317 @@ +// Unit tests for games/story-stamp/lib.js â phonics content, topics, the +// queue, scheduler, blend timeline, profile store. No browser, no audio. +const { test } = require('node:test'); +const assert = require('node:assert'); +const SS = require('../../games/story-stamp/lib.js'); + +const DAY = SS.DAY; +const NOW = Date.UTC(2026, 8, 14, 9, 0, 0); + +function profile(name) { return SS.newProfile(name || 'Test', SS.AVATARS[0], NOW, 'p1'); } + +// ââ Content integrity ââââââââââââââââââââââââââââââââââââââââââââââââââââââ + +test('every word splits into known graphemes and phonemes and belongs to a topic', () => { + for (const w of SS.WORDS) { + const gs = SS.graphemes(w); + assert.strictEqual(gs.join(''), w.w, `${w.w}: graphemes must spell the word`); + const units = SS.phonemeUnits(w); + assert.strictEqual(units.length, gs.length, `${w.w}: one phoneme unit per grapheme`); + for (const unit of units) for (const p of unit) assert.ok(SS.PHONEMES[p], `${w.w}: unknown phoneme ${p}`); + for (const g of gs) assert.ok(SS.SEQUENCE.includes(SS.baseGrapheme(g)), `${w.w}: grapheme ${g} is not in the teaching sequence`); + assert.ok(w.art && /^[A-Z]/.test(w.art), `${w.w}: art must be a Fluent Emoji asset name`); + assert.ok(w.t.length >= 1 && w.t.every((t) => SS.TOPIC_BY_ID[t]), `${w.w}: topics ${w.t}`); + } +}); + +test('word list has no duplicates and every sequence grapheme unlocks something', () => { + const names = SS.WORDS.map((w) => w.w); + assert.strictEqual(new Set(names).size, names.length); + const known = []; + let prev = 0; + for (const g of SS.SEQUENCE) { + known.push(g); + const n = SS.availableWords(known).length; + assert.ok(n >= prev, `pool never shrinks (${g})`); + prev = n; + } + assert.strictEqual(prev, SS.WORDS.length, 'the full sequence unlocks every word'); + assert.ok(SS.availableWords(SS.SEQUENCE.slice(0, SS.STARTING_GRAPHEMES)).length >= 6, 'the opening six letters give a few words'); + assert.ok(SS.WORDS.length >= 200, 'a big enough library to keep topics varied'); +}); + +test('every topic has plenty of books and every phoneme is used by a word', () => { + for (const t of SS.TOPICS) { + const n = SS.WORDS.filter((w) => w.t.includes(t.id)).length; + assert.ok(n >= 8, `topic ${t.id} has only ${n} words`); + } + const used = new Set(); + SS.WORDS.forEach((w) => SS.phonemeUnits(w).forEach((u) => u.forEach((p) => used.add(p)))); + for (const p of Object.keys(SS.PHONEMES)) assert.ok(used.has(p), `phoneme ${p} appears in no word`); +}); + +test('doubled consonants, ck, qu, x and alternative spellings resolve to the taught sound', () => { + assert.deepStrictEqual(SS.graphemePhonemes('ll'), ['l']); + assert.deepStrictEqual(SS.graphemePhonemes('ck'), ['k']); + assert.deepStrictEqual(SS.graphemePhonemes('x'), ['k', 's']); + assert.deepStrictEqual(SS.graphemePhonemes('qu'), ['k', 'w']); + assert.deepStrictEqual(SS.graphemePhonemes('tch'), ['ch']); + assert.deepStrictEqual(SS.graphemePhonemes('ay'), ['ai']); + assert.strictEqual(SS.baseGrapheme('ll'), 'l'); + assert.strictEqual(SS.baseGrapheme('ck'), 'ck'); + assert.strictEqual(SS.baseGrapheme('oy'), 'oi'); + assert.strictEqual(SS.baseGrapheme('tch'), 'ch'); + assert.throws(() => SS.graphemePhonemes('q')); +}); + +test('phoneme overrides keep book short and robot long, and gate availability on both spellings', () => { + assert.deepStrictEqual(SS.phonemeUnits(SS.WORD_BY_NAME.book), [['b'], ['uu'], ['k']]); + assert.deepStrictEqual(SS.phonemeUnits(SS.WORD_BY_NAME.moon), [['m'], ['oo'], ['n']]); + assert.deepStrictEqual(SS.phonemeUnits(SS.WORD_BY_NAME.robot), [['r'], ['oa'], ['b'], ['o'], ['t']]); + assert.deepStrictEqual(SS.phonemeUnits(SS.WORD_BY_NAME.thumb), [['th'], ['u'], ['m'], []]); + const upToOo = SS.SEQUENCE.slice(0, SS.SEQUENCE.indexOf('oo') + 1); + assert.ok(SS.wordIsAvailable(SS.WORD_BY_NAME.book, upToOo), 'book needs b, oo, k'); + assert.ok(!SS.wordIsAvailable(SS.WORD_BY_NAME.robot, upToOo), 'robot also needs oa for its first o'); + assert.ok(SS.wordIsAvailable(SS.WORD_BY_NAME.robot, upToOo.concat(['oa']))); + assert.strictEqual(SS.homeGrapheme('k'), 'c'); + assert.strictEqual(SS.homeGrapheme('uu'), 'oo'); +}); + +test('patrons are deterministic per word and cover the cast', () => { + assert.strictEqual(SS.patronFor('cat'), SS.patronFor('cat')); + const ids = new Set(SS.WORDS.map((w) => SS.patronFor(w.w).id)); + assert.ok(ids.size >= SS.PATRONS.length - 2, 'most regulars get at least one favourite book'); +}); + +// ââ Availability and introduction âââââââââââââââââââââââââââââââââââââââââ + +test('a new profile knows the first six letters and can be served', () => { + const p = profile(); + assert.deepStrictEqual(p.introduced, ['s', 'a', 't', 'p', 'i', 'n']); + const words = SS.unseenWords(p); + assert.ok(words.includes('ant') && words.includes('tap')); + assert.ok(!words.includes('cat')); + assert.strictEqual(SS.nextGrapheme(p), 'm'); +}); + +test('new sounds arrive on mastery, not on a schedule', () => { + const p = profile(); + assert.ok(!SS.shouldIntroduce(p), 'nothing yet'); + SS.recordOrder(p, 'tap', true, NOW); + SS.recordOrder(p, 'pin', true, NOW); + assert.ok(!SS.shouldIntroduce(p), 'two orders is too soon'); + SS.recordOrder(p, 'pan', false, NOW); + SS.recordOrder(p, 'ant', true, NOW); + assert.ok(!SS.shouldIntroduce(p), '3 of 4 first-try is under the bar'); + SS.recordOrder(p, 'nap', true, NOW); + assert.ok(SS.shouldIntroduce(p), '4 of the last 5 first-try unlocks the next sound'); + assert.strictEqual(SS.introduceNext(p), 'm'); + assert.strictEqual(p.sinceIntro, 0); + assert.ok(!SS.shouldIntroduce(p), 'not two in a row'); +}); + +test('a dry pool forces the next sound even when accuracy is poor', () => { + const p = profile(); + SS.unseenWords(p).forEach((w) => SS.recordOrder(p, w, false, NOW)); + assert.strictEqual(SS.unseenWords(p).length, 0); + assert.ok(SS.shouldIntroduce(p)); +}); + +// ââ Spaced repetition âââââââââââââââââââââââââââââââââââââââââââââââââââââ + +test('first-try orders grow the interval; a miss resets it and brings the regular back soon', () => { + const p = profile(); + let rec = SS.recordOrder(p, 'tap', true, NOW); + assert.strictEqual(rec.streak, 1); + assert.strictEqual(rec.due, NOW + 1 * DAY); + rec = SS.recordOrder(p, 'tap', true, NOW + DAY); + assert.strictEqual(rec.interval, 3); + assert.ok(SS.isMastered(rec)); + rec = SS.recordOrder(p, 'tap', false, NOW + 4 * DAY); + assert.strictEqual(rec.streak, 0); + assert.strictEqual(rec.lapses, 1); + assert.strictEqual(rec.due, NOW + 4 * DAY + 5 * SS.MIN); + assert.ok(!SS.isMastered(rec)); + assert.deepStrictEqual(SS.shakyWords(p), ['tap']); +}); + +test('due words come back before fresh ones, most overdue first', () => { + const p = profile(); + SS.recordOrder(p, 'tap', true, NOW - 5 * DAY); + SS.recordOrder(p, 'pin', true, NOW - 3 * DAY); + SS.recordOrder(p, 'ant', true, NOW); + p.lastWord = null; + assert.deepStrictEqual(SS.dueWords(p, NOW), ['tap', 'pin']); + const rng = SS.mulberry32(1); + const seen = new Set(); + for (let i = 0; i < 40; i++) { + const n = SS.pickNext(p, NOW, rng); + if (n.review) seen.add(n.word.w); + } + assert.ok(seen.has('tap') && seen.has('pin')); + assert.ok(!seen.has('ant'), 'a word served today is not due'); +}); + +test('pickNext never repeats the previous word and prefers words using the newest sound', () => { + const p = profile(); + p.introduced.push('m'); + const rng = SS.mulberry32(7); + let last = null; + for (let i = 0; i < 30; i++) { + const n = SS.pickNext(p, NOW, rng); + assert.notStrictEqual(n.word.w, last); + if (!n.review) assert.ok(SS.graphemes(n.word).map(SS.baseGrapheme).includes('m'), `fresh word ${n.word.w} should use the new sound`); + SS.recordOrder(p, n.word.w, true, NOW); + last = n.word.w; + if (SS.unseenWords(p).filter((w) => w !== last && SS.graphemes(SS.WORD_BY_NAME[w]).map(SS.baseGrapheme).includes('m')).length === 0) break; + } +}); + +test('returns (typing by ear) only unlock after five mastered words', () => { + const p = profile(); + const rng = SS.mulberry32(3); + ['tap', 'pin', 'pan'].forEach((w) => { SS.recordOrder(p, w, true, NOW - 3 * DAY); SS.recordOrder(p, w, true, NOW - 2 * DAY); }); + p.lastWord = null; + for (let i = 0; i < 30; i++) assert.strictEqual(SS.pickNext(p, NOW, rng).mode, 'read'); + ['ant', 'nap', 'tin'].forEach((w) => { SS.recordOrder(p, w, true, NOW - 3 * DAY); SS.recordOrder(p, w, true, NOW - 2 * DAY); }); + p.lastWord = null; + assert.ok(SS.masteredWords(p).length >= SS.RETURN_MODE_UNLOCK); + // Returns only make sense for a book the child has met: make them all due. + Object.values(p.words).forEach((r) => { r.due = NOW - DAY; }); + let returns = 0; + for (let i = 0; i < 80; i++) if (SS.pickNext(p, NOW, rng).mode === 'return') returns++; + assert.ok(returns > 10 && returns < 70, `returns should be a healthy mix, got ${returns}/80`); +}); + +test('pickNext respects a filter and an exclusion list', () => { + const p = profile(); + p.introduced = SS.SEQUENCE.slice(0, 16); + const rng = SS.mulberry32(5); + for (let i = 0; i < 20; i++) { + const n = SS.pickNext(p, NOW, rng, (w) => w.t.includes('animals'), ['dog']); + assert.ok(n.word.t.includes('animals')); + assert.notStrictEqual(n.word.w, 'dog'); + } + assert.strictEqual(SS.pickNext(p, NOW, rng, () => false), null); +}); + +// ââ The queue âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ + +test('the queue offers different topics, different words, and a patron for each', () => { + const p = profile(); + p.introduced = SS.SEQUENCE.slice(0, 20); + const rng = SS.mulberry32(11); + for (let i = 0; i < 10; i++) { + const q = SS.buildQueue(p, NOW, rng, 3); + assert.strictEqual(q.length, 3); + assert.strictEqual(new Set(q.map((e) => e.topic.id)).size, 3, 'topics differ'); + assert.strictEqual(new Set(q.map((e) => e.word.w)).size, 3, 'words differ'); + q.forEach((e) => { + assert.ok(e.patron && e.patron.name); + if (e.topic.id !== 'any') assert.ok(e.word.t.includes(e.topic.id), `${e.word.w} is not about ${e.topic.id}`); + }); + } +}); + +test('a beginner still gets a choice: generic patrons fill in until topics open up', () => { + const p = profile(); + const rng = SS.mulberry32(2); + const q = SS.buildQueue(p, NOW, rng, 3); + assert.ok(q.length >= 2, 'at least two to choose from'); + q.forEach((e) => assert.ok(SS.wordIsAvailable(e.word, p.introduced))); + const topics = SS.topicsWithWords(p).map((t) => t.id); + assert.ok(topics.includes('bugs') && topics.includes('play'), `opening topics: ${topics}`); +}); + +test('recordTopic counts what the child asks for', () => { + const p = profile(); + SS.recordTopic(p, 'dinos'); SS.recordTopic(p, 'dinos'); SS.recordTopic(p, 'sea'); + assert.deepStrictEqual(p.topics, { dinos: 2, sea: 1 }); +}); + +// ââ Distractors âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ + +test('distractors are two other words with different pictures, sharing letters when possible', () => { + const p = profile(); + p.introduced = SS.SEQUENCE.slice(0, 12); + ['cat', 'cap', 'pig', 'dog', 'sun', 'map'].forEach((w) => SS.recordOrder(p, w, true, NOW)); + const rng = SS.mulberry32(9); + for (let i = 0; i < 20; i++) { + const d = SS.distractors(SS.WORD_BY_NAME.cat, p, rng); + assert.strictEqual(d.length, 2); + assert.ok(!d.some((w) => w.w === 'cat')); + assert.strictEqual(new Set(d.map((w) => w.art).concat(['Cat'])).size, 3, 'pictures must differ'); + assert.ok(d.some((w) => /[cat]/.test(w.w)), 'at least one near miss'); + } + const fresh = profile(); + assert.strictEqual(SS.distractors(SS.WORD_BY_NAME.tap, fresh, rng).length, 2, 'works before any word is served'); +}); + +test('a profile that served a word since removed from the list still schedules cleanly', () => { + const p = profile(); + SS.recordOrder(p, 'tap', true, NOW - 3 * DAY); + SS.recordOrder(p, 'zzzgone', true, NOW - 3 * DAY); + assert.deepStrictEqual(SS.servedWords(p), ['tap']); + assert.deepStrictEqual(SS.dueWords(p, NOW), ['tap']); + assert.strictEqual(SS.masteredWords(p).length, 0); + const n = SS.pickNext(p, NOW, SS.mulberry32(1)); + assert.ok(n.word && n.word.w !== 'zzzgone'); + assert.ok(SS.distractors(SS.WORD_BY_NAME.tap, p, SS.mulberry32(1)).every((w) => w.w !== 'zzzgone')); +}); + +// ââ Library growth ââââââââââââââââââââââââââââââââââââââââââââââââââââââââ + +test('the library grows with mastered words', () => { + assert.deepStrictEqual(SS.libraryUpgrades(0), []); + assert.deepStrictEqual(SS.libraryUpgrades(2).map((u) => u.id), ['rug']); + assert.strictEqual(SS.libraryUpgrades(100).length, SS.LIBRARY_UPGRADES.length); +}); + +// ââ Blend timeline ââââââââââââââââââââââââââââââââââââââââââââââââââââââââ + +test('blend timeline: three passes of shrinking gaps and holds, never the whole word', () => { + const units = SS.phonemeUnits(SS.WORD_BY_NAME.sat || SS.WORD_BY_NAME.tap); + const durations = { s: 1.0, a: 0.9, t: 0.1, p: 0.09 }; + const ev = SS.blendTimeline(units, durations); + const phon = ev.filter((e) => e.kind === 'phoneme'); + assert.strictEqual(phon.length, units.length * 3); + assert.ok(!ev.some((e) => e.kind === 'word'), 'the word is left for the child to say'); + assert.strictEqual(ev[ev.length - 1].kind, 'done'); + const pass = (i) => phon.filter((e) => e.pass === i); + const gap = (i) => pass(i)[1].t - (pass(i)[0].t + pass(i)[0].len); + assert.ok(gap(0) > gap(1) && gap(1) > gap(2), 'gaps shrink'); + const held = (i) => pass(i).find((e) => SS.PHONEMES[e.id].stretch); + assert.ok(held(0).len > held(1).len && held(1).len > held(2).len, 'held sounds get shorter'); + assert.strictEqual(held(0).len, SS.BLEND_PASSES[0].hold); + const stop = (i) => pass(i).find((e) => SS.PHONEMES[e.id].type === 'stop'); + assert.strictEqual(stop(0).len, stop(2).len, 'stops keep their natural length'); + for (let i = 1; i < ev.length; i++) assert.ok(ev[i].t >= ev[i - 1].t, 'events are in time order'); +}); + +test('phonemeLength cuts held sounds to the pass but never below the minimum or above the clip', () => { + assert.strictEqual(SS.phonemeLength('s', 0.4, { s: 1.0 }), 0.4); + assert.strictEqual(SS.phonemeLength('s', 0.05, { s: 1.0 }), SS.MIN_HOLD); + assert.strictEqual(SS.phonemeLength('s', 2, { s: 1.0 }), 1.0); + assert.strictEqual(SS.phonemeLength('p', 0.4, { p: 0.09 }), 0.09, 'stops are never stretched or cut'); + assert.strictEqual(SS.phonemeLength('ow', 0.1, { ow: 0.4 }), 0.4, 'diphthongs play whole'); +}); + +// ââ Store âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ + +test('profiles round-trip through storage and bad data is ignored', () => { + const mem = {}; + const storage = { getItem: (k) => (k in mem ? mem[k] : null), setItem: (k, v) => { mem[k] = v; } }; + const store = SS.loadStore(storage); + assert.deepStrictEqual(store.profiles, []); + const p = profile('Ada'); + SS.recordOrder(p, 'tap', true, NOW); + store.profiles.push(p); store.last = p.id; + SS.saveStore(storage, store); + const back = SS.loadStore(storage); + assert.strictEqual(back.profiles[0].name, 'Ada'); + assert.strictEqual(back.profiles[0].words.tap.streak, 1); + assert.deepStrictEqual(back.profiles[0].topics, {}); + assert.strictEqual(back.last, p.id); + mem[SS.STORE_KEY] = '{not json'; + assert.deepStrictEqual(SS.loadStore(storage).profiles, []); +}); diff --git a/tools/sound-soup-assets.py b/tools/sound-soup-assets.py @@ -1,353 +0,0 @@ -#!/usr/bin/env python3 -"""Fetch + build the Sound Soup audio and picture assets. - - python3 tools/sound-soup-assets.py # fetch what is missing, rebuild everything - python3 tools/sound-soup-assets.py --offline # rebuild from the cache only - -Sources (all recorded / drawn by people, all GPL-3.0-compatible): - - * Phonemes: IPA sound recordings from Wikimedia Commons (mostly by - Peter Isotalo, CC BY-SA 3.0). Those clips are recorded as "sa ... sa" or - "ta ... ata"; this script cuts the isolated sound out of the first - utterance (a stretched /sss/, a crisp /t/, a sustained /ĂŚ/) so the game can - blend them without a tacked-on schwa. - * Words: Wiktionary pronunciations from Wikimedia Commons (mostly by - Dvortygirl, CC BY-SA 3.0; a few public domain / CC0), trimmed and - level-matched. Words with no recording fall back to browser speech in-game. - * Pictures: Twemoji SVGs (CC BY 4.0) fetched from the jsDelivr mirror. - -Outputs (committed): - games/sound-soup/assets/audio/phonemes/<id>.mp3 - games/sound-soup/assets/audio/words/<word>.mp3 - games/sound-soup/assets/img/<codepoint>.svg - games/sound-soup/assets/manifest.js (what exists + per-file attribution) - games/sound-soup/CREDITS.md (human-readable attribution) - -Requires python3 + numpy + ffmpeg on PATH. Downloads are cached in -.cache/sound-soup/ (gitignored) and rate-limited to be polite to Commons. -""" -import json, os, re, subprocess, sys, time, urllib.error, urllib.parse, urllib.request - -try: - import numpy as np -except ImportError: - sys.exit('numpy is required: pip install numpy') - -ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -GAME = os.path.join(ROOT, 'games', 'sound-soup') -CACHE = os.path.join(ROOT, '.cache', 'sound-soup') -OUT_PH = os.path.join(GAME, 'assets', 'audio', 'phonemes') -OUT_WD = os.path.join(GAME, 'assets', 'audio', 'words') -OUT_IMG = os.path.join(GAME, 'assets', 'img') -OFFLINE = '--offline' in sys.argv -UA = {'User-Agent': 'KGames-asset-fetch/1.0 (https://www.keyboard.games)'} -TWEMOJI = 'https://cdn.jsdelivr.net/gh/jdecked/twemoji@15.1.0/assets/svg/' -SR = 44100 - -# ââ Phoneme sources ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ -# id -> (Commons file, cut mode). Modes: -# vowel : sustained vowel; take the first ~450 ms -# nasal : "Ca ..." â voiced continuant up to the vowel onset (low band jumps) -# approx : "Ca ..." â glide; keep it until the vowel is nearly at full level -# fric : "Ca ..." â voiceless fricative found from its high-band noise -# between : "aCa" â consonant sits between two vowels -# stop : "Ca ..." â burst + short release -# affric : like stop but with the long fricative release kept -PHONEME_SOURCES = { - 's': ('Voiceless_alveolar_sibilant.ogg', 'fric'), - 'a': ('Near-open_front_unrounded_vowel.ogg', 'vowel'), - 't': ('Voiceless_alveolar_plosive.ogg', 'stop'), - 'p': ('Voiceless_bilabial_plosive.ogg', 'stop'), - 'i': ('Near-close_near-front_unrounded_vowel.ogg', 'vowel'), - 'n': ('Alveolar_nasal.ogg', 'nasal'), - 'm': ('Bilabial_nasal.ogg', 'nasal'), - 'd': ('Voiced_alveolar_plosive.ogg', 'stop'), - 'g': ('Voiced_velar_plosive.ogg', 'stop'), - 'o': ('Open_back_unrounded_vowel.ogg', 'vowel'), - 'k': ('Voiceless_velar_plosive.ogg', 'stop'), - 'e': ('Open-mid_front_unrounded_vowel.ogg', 'vowel'), - 'u': ('Open-mid_back_unrounded_vowel.ogg', 'vowel'), - 'r': ('Alveolar_approximant.ogg', 'approx'), - 'h': ('Voiceless_glottal_fricative.ogg', 'fric'), - 'b': ('Voiced_bilabial_plosive.ogg', 'stop'), - 'f': ('Voiceless_labiodental_fricative.ogg', 'between'), - 'l': ('Alveolar_lateral_approximant.ogg', 'approx'), - 'j': ('Voiced_postalveolar_affricate.ogg', 'affric'), - 'v': ('Voiced_labiodental_fricative.ogg', 'between'), - 'w': ('Voiced_labio-velar_approximant.ogg', 'approx'), - 'y': ('Palatal_approximant.ogg', 'approx'), - 'z': ('Voiced_alveolar_sibilant.ogg', 'fric'), - 'sh': ('Voiceless_palato-alveolar_sibilant.ogg', 'fric'), - 'ch': ('Voiceless_palato-alveolar_affricate.ogg', 'affric'), - 'th': ('Voiceless_dental_fricative.ogg', 'fric'), - 'ng': ('Velar_nasal.ogg', 'nasal'), - 'ee': ('Close_front_unrounded_vowel.ogg', 'vowel'), - 'oo': ('Close_back_rounded_vowel.ogg', 'vowel'), - 'uu': ('Near-close_near-back_rounded_vowel.ogg', 'vowel'), - 'oa': ('Close-mid_back_rounded_vowel.ogg', 'vowel'), -} - -# ââ helpers ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ - -def log(*a): - print(*a, flush=True) - -def commons_info(titles): - """Batch imageinfo query. Returns {title: {url, license, artist, ...}} (missing files omitted).""" - out = {} - for i in range(0, len(titles), 20): - batch = titles[i:i + 20] - q = 'https://commons.wikimedia.org/w/api.php?' + urllib.parse.urlencode({ - 'action': 'query', 'format': 'json', 'titles': '|'.join('File:' + t for t in batch), - 'prop': 'imageinfo', 'iiprop': 'url|extmetadata|user', - 'iiextmetadatafilter': 'LicenseShortName|Artist|LicenseUrl'}) - d = json.load(urllib.request.urlopen(urllib.request.Request(q, headers=UA), timeout=30)) - for p in d['query']['pages'].values(): - t = p['title'].replace('File:', '').replace(' ', '_') - if 'missing' in p: - continue - ii = p['imageinfo'][0]; em = ii.get('extmetadata', {}) - artist = re.sub('<[^>]+>', '', em.get('Artist', {}).get('value', '') or '').strip() - artist = re.sub(r'^No machine-readable author provided\.\s*', '', artist) - artist = re.sub(r'\s+assumed \(based on copyright claims\)\.?$', '', artist) - out[t] = { - 'url': ii['url'].split('?')[0], - 'page': 'https://commons.wikimedia.org/wiki/File:' + t, - 'license': em.get('LicenseShortName', {}).get('value', '?'), - 'licenseUrl': em.get('LicenseUrl', {}).get('value', ''), - 'author': artist or ii.get('user', '?'), - } - time.sleep(1.0) - return out - -def fetch(url, dest): - if os.path.exists(dest): - return True - if OFFLINE: - return False - for attempt in range(6): - try: - data = urllib.request.urlopen(urllib.request.Request(url, headers=UA), timeout=60).read() - with open(dest, 'wb') as f: - f.write(data) - time.sleep(2.5 if 'wikimedia' in url else 0.15) - return True - except urllib.error.HTTPError as e: - if e.code == 429: - log(' 429 from server, backing off ...'); time.sleep(45 * (attempt + 1)) - elif e.code == 404: - return False - else: - raise - return False - -def decode(path): - raw = subprocess.run(['ffmpeg', '-v', 'quiet', '-i', path, '-f', 'f32le', '-ac', '1', '-ar', str(SR), '-'], - capture_output=True, check=True).stdout - return np.frombuffer(raw, np.float32).copy() - -def encode_mp3(x, dest): - raw = (np.clip(x, -1, 1) * 32767).astype(np.int16).tobytes() - subprocess.run(['ffmpeg', '-v', 'quiet', '-y', '-f', 's16le', '-ac', '1', '-ar', str(SR), '-i', '-', - '-codec:a', 'libmp3lame', '-b:a', '64k', dest], input=raw, check=True) - -FRAME = SR // 100 # 10 ms - -def frames(x): - n = len(x) // FRAME - return x[:n * FRAME].reshape(n, FRAME) - -def rms(fr): - return np.sqrt((fr ** 2).mean(1) + 1e-12) - -def band_rms(x, lo, hi): - """Per-10ms-frame RMS of the signal band-limited to [lo, hi] Hz (FFT filter).""" - X = np.fft.rfft(x) - f = np.fft.rfftfreq(len(x), 1 / SR) - X[(f < lo) | (f > hi)] = 0 - return rms(frames(np.fft.irfft(X, len(x)).astype(np.float32))) - -def fade(x, fin=0.008, fout=0.03): - x = x.copy() - a = int(fin * SR); b = int(fout * SR) - if a: x[:a] *= np.linspace(0, 1, a) - if b: x[-b:] *= np.linspace(1, 0, b) - return x - -def normalize(x, target_db=-18.0, peak_db=-1.0): - r = np.sqrt((x ** 2).mean() + 1e-12) - g = 10 ** (target_db / 20) / r - x = x * g - pk = np.abs(x).max() - lim = 10 ** (peak_db / 20) - if pk > lim: - x = x * (lim / pk) - return x - -def sec(fr_index): - return fr_index * FRAME / SR - -# ââ phoneme cutting ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ - -def sustained(arr, thresh, start=0, run=4): - """First index >= start where arr stays above thresh for `run` frames.""" - for k in range(start, len(arr) - run): - if all(arr[k + j] > thresh for j in range(run)): - return k - return len(arr) - -def cut_phoneme(x, mode): - tot = rms(frames(x)); low = band_rms(x, 0, 1000); high = band_rms(x, 1500, 12000) - peak = tot.max() - onset = sustained(tot, peak * 0.12) - if mode == 'vowel': - end = min(onset + 45, len(tot)) - for k in range(onset + 10, end): # stop early if the vowel dies out - if tot[k] < peak * 0.1: - end = k; break - return x[onset * FRAME:end * FRAME], (sec(onset), sec(end)) - if mode in ('nasal', 'approx', 'fric'): - if mode == 'fric': - onset = sustained(high, high.max() * 0.06, run=5) - else: - onset = sustained(tot, peak * 0.05) # nasals / glides start quietly - win = low[onset:onset + 90] - vmax = win.max() - frac = {'nasal': 0.5, 'approx': 0.8, 'fric': 0.5}[mode] - vowel = onset + int(np.argmax(win > vmax * frac)) - vowel = max(onset + 8, vowel - 1) - return x[onset * FRAME:vowel * FRAME], (sec(onset), sec(vowel)) - if mode == 'between': - vmax = low[onset:onset + 30].max() - k = onset - while k < len(low) and low[k] < vmax * 0.4: k += 1 # into the first vowel - while k < len(low) and low[k] > vmax * 0.25: k += 1 # to its end - start = k + 1 - k = start + 5 - while k < len(low) and low[k] < vmax * 0.35: k += 1 # until the second vowel - end = k - 1 - return x[start * FRAME:end * FRAME], (sec(start), sec(end)) - if mode in ('stop', 'affric'): - win = high[onset:onset + 60] - burst = onset + int(np.argmax(win > win.max() * 0.3)) - start = max(0, burst * FRAME - int(0.004 * SR)) - length = int((0.13 if mode == 'affric' else 0.06) * SR) - return x[start:start + length], (start / SR, (start + length) / SR) - raise ValueError(mode) - -def trim_word(x): - tot = rms(frames(x)); peak = tot.max() - on = np.where(tot > peak * 0.05)[0] - a = max(0, on[0] - 3); b = min(len(tot), on[-1] + 6) - return x[a * FRAME:b * FRAME] - -# ââ main âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ - -def word_list(): - js = "const L=require(process.argv[1]);console.log(JSON.stringify(L.WORDS.map(w=>[w.w,w.emoji])))" - out = subprocess.run(['node', '-e', js, os.path.join(GAME, 'lib.js')], capture_output=True, text=True, check=True).stdout - return json.loads(out) - -def main(): - for d in (CACHE, OUT_PH, OUT_WD, OUT_IMG): - os.makedirs(d, exist_ok=True) - words = word_list() - manifest = {'phonemes': {}, 'words': {}, 'images': {}} - credits = {} - - # ââ metadata + downloads âââââââââââââââââââââââââââââââââââââââââââââââ - info_path = os.path.join(CACHE, 'commons-info.json') - info = json.load(open(info_path)) if os.path.exists(info_path) else {} - wanted = [src for src, _ in PHONEME_SOURCES.values()] + ['En-us-%s.ogg' % w for w, _ in words] - missing_meta = [t for t in wanted if t not in info] - if missing_meta and not OFFLINE: - log('Querying Commons for %d files ...' % len(missing_meta)) - info.update(commons_info(missing_meta)) - for t in missing_meta: - info.setdefault(t, None) # remember 404s too - json.dump(info, open(info_path, 'w'), indent=1) - - def get_clip(title): - meta = info.get(title) - if not meta: - return None, None - dest = os.path.join(CACHE, title) - if not fetch(meta['url'], dest): - return None, meta - return dest, meta - - # ââ phonemes ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ - log('Phonemes:') - for pid, (src, mode) in PHONEME_SOURCES.items(): - path, meta = get_clip(src) - if not path: - log(' %-3s MISSING %s' % (pid, src)); continue - x = decode(path) - seg, (t0, t1) = cut_phoneme(x, mode) - seg = normalize(fade(seg, fin=0.004 if mode in ('stop', 'affric') else 0.015, fout=0.03 if mode in ('stop', 'affric') else 0.04)) - encode_mp3(seg, os.path.join(OUT_PH, pid + '.mp3')) - manifest['phonemes'][pid] = {'file': 'phonemes/%s.mp3' % pid, 'dur': round(len(seg) / SR, 3)} - credits[src] = dict(meta, use='phoneme /%s/ â cut %.2fâ%.2fs of the original' % (pid, t0, t1)) - log(' %-3s %-45s %s %.2f-%.2fs -> %d ms' % (pid, src, mode, t0, t1, 1000 * len(seg) / SR)) - - # ââ words âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ - log('Words:') - for w, _ in words: - src = 'En-us-%s.ogg' % w - path, meta = get_clip(src) - if not path: - log(' %-6s no recording (browser speech fallback)' % w); continue - x = normalize(fade(trim_word(decode(path)), fin=0.005, fout=0.02)) - encode_mp3(x, os.path.join(OUT_WD, w + '.mp3')) - manifest['words'][w] = {'file': 'words/%s.mp3' % w, 'dur': round(len(x) / SR, 3)} - credits[src] = dict(meta, use='word "%s"' % w) - log(' %d of %d words have recordings' % (len(manifest['words']), len(words))) - - # ââ pictures ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ - log('Pictures:') - for cp in sorted(set(e for _, e in words) | set(EXTRA_EMOJI)): - dest = os.path.join(OUT_IMG, cp + '.svg') - if not os.path.exists(dest) and not fetch(TWEMOJI + cp + '.svg', dest): - log(' MISSING twemoji %s' % cp); continue - manifest['images'][cp] = 'img/%s.svg' % cp - log(' %d SVGs' % len(manifest['images'])) - - # ââ manifest + credits ââââââââââââââââââââââââââââââââââââââââââââââââ - with open(os.path.join(GAME, 'assets', 'manifest.js'), 'w') as f: - f.write('// Generated by tools/sound-soup-assets.py â do not edit by hand.\n') - f.write('window.SS_ASSETS = ' + json.dumps(manifest, indent=1, sort_keys=True) + ';\n') - - by_author = {} - for title, c in sorted(credits.items()): - by_author.setdefault((c['author'], c['license'], c.get('licenseUrl', '')), []).append((title, c)) - with open(os.path.join(GAME, 'CREDITS.md'), 'w') as f: - f.write('# Sound Soup â asset credits\n\n') - f.write('Generated by `tools/sound-soup-assets.py`. Every third-party file used by the game is listed here with its source, author and license.\n\n') - f.write('## Pictures\n\n') - f.write('All pictures are [Twemoji](https://github.com/jdecked/twemoji) graphics, Š Twitter, Inc and other contributors, licensed [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). Files: `games/sound-soup/assets/img/*.svg` (unmodified).\n\n') - f.write('## Audio\n\n') - f.write('Recordings from [Wikimedia Commons](https://commons.wikimedia.org/). Phoneme clips are *cut from* the listed originals (the isolated sound is extracted from an "sa ⌠sa" / "ta ⌠ata" style recording); word clips are trimmed and level-matched. Modified files are `games/sound-soup/assets/audio/**/*.mp3`.\n\n') - for (author, lic, url), items in sorted(by_author.items(), key=lambda kv: (-len(kv[1]), kv[0])): - lic_md = '[%s](%s)' % (lic, url) if url else lic - f.write('### %s â %s\n\n' % (author, lic_md)) - for title, c in items: - f.write('- [%s](%s) â %s\n' % (title, c['page'], c['use'])) - f.write('\n') - f.write('## Speech\n\nInstructions, customer chatter, and any word without a recording are voiced by the browser\'s built-in speech synthesis (Web Speech API); nothing is sent to a server.\n') - log('Wrote manifest.js and CREDITS.md') - -# Pictures used by the UI itself (customers, stall, scenery), on top of the word pictures. -EXTRA_EMOJI = [ - '1f409', '1f99c', '1f98e', '1f980', '1f419', '1f9dc-200d-2640-fe0f', '1f9de', '1f422', - '1f9a9', '1f438', '1f989', '1f42c', # customers - '1f3a9', # the lizard's hat - '1f372', '1f963', '1f334', '26f5', '1f3dd', '2600', '1f41f', '1f34d', '1f965', '1f6f6', - '1f3ee', '1f33a', '1f38b', '1f4d6', '1f4a8', '2728', '2b50', '1f31f', '1f320', '1f30a', - '1f423', '1f426', '2601', '1f319', '1f9c2', '1f37d', '1f9ed', '1f3ea', '1f6a4', '1f41a', - '1f33a', '1f33b', '1f490', '1f3a8', '1f9f8', '1f9d1-200d-1f373', '1f9d2', '1f467', '1f466', - '1f469', '1f468', '1f475', '1f474', '1f47d', '1f427', '1f43c', '1f981', '1f42f', '1f43b', - '1f9ab', '1f425', '1f428', '1f43a', '1f42e', '1f437', '1f430', '1f431', '1f436', -] - -if __name__ == '__main__': - main() diff --git a/tools/story-stamp-assets.py b/tools/story-stamp-assets.py @@ -0,0 +1,533 @@ +#!/usr/bin/env python3 +"""Fetch + build the Story Stamp audio and picture assets. + + python3 tools/story-stamp-assets.py # fetch what is missing, rebuild everything + python3 tools/story-stamp-assets.py --offline # rebuild from the cache only + +Sources (all recorded / drawn by people, all GPL-3.0-compatible): + + * Phonemes + - Continuants and short vowels: IPA sound recordings from Wikimedia + Commons (mostly by Peter Isotalo, CC BY-SA 3.0). Those clips are + recorded as "sa ... sa" / "ata"; the isolated sound is cut out of the + first utterance and then held for about a second with WSOLA + time-stretching of its steady middle (the natural onset and release + are kept), so a stretched /sss/ is one continuous sound, not a loop. + - Stops (p t k b d g) and affricates (ch j): cut from the start of a + Wiktionary word recording (pig, tap, cat ...): burst + aspiration up + to the moment the vowel's voicing begins. English initial stops are + aspirated; the IPA recordings are the unaspirated cardinal sounds, + which are too quiet and clipped for a four-year-old to hear. + - r-coloured vowels and diphthongs (ar or er ow oi ai igh): whole + Wiktionary recordings of the words "are", "or", "er", "ow", "eye", + and "boy" / "day" with their first consonant removed. + * Words: Wiktionary pronunciations from Wikimedia Commons (mostly by + Dvortygirl, CC BY-SA 3.0; a few public domain / CC0), trimmed and + level-matched. Words with no recording fall back to browser speech. + * Pictures: Fluent Emoji 3D (Microsoft, MIT) fetched from the jsDelivr + GitHub mirror, scaled to 192 px and saved as WebP (a fifth of the PNG size). + +Outputs (committed): + games/story-stamp/assets/audio/phonemes/<id>.mp3 + games/story-stamp/assets/audio/words/<word>.mp3 + games/story-stamp/assets/img/<slug>.webp + games/story-stamp/assets/manifest.js (what exists + durations) + games/story-stamp/CREDITS.md (human-readable attribution) + +Requires python3 + numpy + ffmpeg on PATH. Downloads are cached in +.cache/story-stamp/ (gitignored) and rate-limited to be polite to Commons. +Run tools/story-stamp-audit.py afterwards to check the phoneme clips. +""" +import json, os, re, subprocess, sys, time, urllib.error, urllib.parse, urllib.request + +try: + import numpy as np +except ImportError: + sys.exit('numpy is required: pip install numpy') + +ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +GAME = os.path.join(ROOT, 'games', 'story-stamp') +CACHE = os.path.join(ROOT, '.cache', 'story-stamp') +OUT_PH = os.path.join(GAME, 'assets', 'audio', 'phonemes') +OUT_WD = os.path.join(GAME, 'assets', 'audio', 'words') +OUT_IMG = os.path.join(GAME, 'assets', 'img') +OFFLINE = '--offline' in sys.argv +UA = {'User-Agent': 'KGames-asset-fetch/1.0 (https://www.keyboard.games)'} +FLUENT = 'https://cdn.jsdelivr.net/gh/microsoft/fluentui-emoji@main/' +FLUENT_LICENSE = 'https://github.com/microsoft/fluentui-emoji/blob/main/LICENSE' +IMG_PX = 192 +SR = 44100 + +# ââ Phoneme sources ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ +# id -> (mode, source file(s)). Modes: +# vowel : sustained IPA vowel; cut the first ~450 ms, then hold +# fric : "Ca ..." IPA clip â voiceless fricative found from its high-band noise, then hold +# nasal : "Ca ..." â voiced continuant up to the vowel onset, then hold +# voiced : same cut, from the start of a word recording (zip -> z) +# approx : "Ca ..." â glide; kept until the vowel is nearly at full level (not held) +# between : "aCa" â consonant between two vowels, then hold +# stop : word recordings â burst + aspiration up to voicing onset; first source that cuts cleanly wins +# affric : like stop but voicing onset is searched later (the fricative release is long) +# word : a whole word recording, trimmed +# strip : a word recording with its initial consonant removed (boy -> oy) +PHONEME_SOURCES = { + 's': ('fric', ['Voiceless_alveolar_sibilant.ogg']), + 'a': ('vowel', ['Near-open_front_unrounded_vowel.ogg']), + 't': ('stop', ['En-us-tap.ogg', 'En-us-ten.ogg', 'En-us-tent.ogg', 'En-us-tin.ogg']), + 'p': ('stop', ['En-us-pig.ogg', 'En-us-pan.ogg', 'En-us-pot.ogg', 'En-us-pen.ogg']), + 'i': ('vowel', ['Near-close_near-front_unrounded_vowel.ogg']), + 'n': ('nasal', ['Alveolar_nasal.ogg']), + 'm': ('nasal', ['Bilabial_nasal.ogg']), + 'd': ('stop', ['En-us-dog.ogg', 'En-us-duck.ogg', 'En-us-dad.ogg', 'En-us-dish.ogg']), + 'g': ('stop', ['En-us-goat.ogg', 'En-us-gift.ogg', 'En-us-gem.ogg']), + 'o': ('vowel', ['Open_back_unrounded_vowel.ogg']), + 'k': ('stop', ['En-us-cat.ogg', 'En-us-cup.ogg', 'En-us-camp.ogg', 'En-us-can.ogg']), + 'e': ('vowel', ['Open-mid_front_unrounded_vowel.ogg']), + 'u': ('vowel', ['Open-mid_back_unrounded_vowel.ogg']), + 'r': ('approx', ['Alveolar_approximant.ogg']), + 'h': ('fric', ['Voiceless_glottal_fricative.ogg']), + 'b': ('stop', ['En-us-bug.ogg', 'En-us-bus.ogg', 'En-us-bed.ogg', 'En-us-bat.ogg']), + 'f': ('between', ['Voiceless_labiodental_fricative.ogg']), + 'l': ('approx', ['Alveolar_lateral_approximant.ogg']), + 'j': ('affric', ['En-us-jam.ogg', 'En-us-jump.ogg', 'En-us-jeep.ogg', 'En-us-jar.ogg']), + 'v': ('between', ['Voiced_labiodental_fricative.ogg']), + 'w': ('approx', ['Voiced_labio-velar_approximant.ogg']), + 'y': ('approx', ['Palatal_approximant.ogg']), + 'z': ('voiced', ['En-us-zip.ogg', 'En-us-zest.ogg', 'En-us-zone.ogg', 'Voiced_alveolar_sibilant.ogg']), + 'sh': ('fric', ['Voiceless_palato-alveolar_sibilant.ogg']), + 'ch': ('affric', ['En-us-chick.ogg', 'En-us-chips.ogg', 'En-us-chain.ogg', 'En-us-chest.ogg']), + 'th': ('fric', ['Voiceless_dental_fricative.ogg']), + 'ng': ('nasal', ['Velar_nasal.ogg']), + 'ee': ('vowel', ['Close_front_unrounded_vowel.ogg']), + 'oo': ('vowel', ['Close_back_rounded_vowel.ogg']), + 'uu': ('vowel', ['Near-close_near-back_rounded_vowel.ogg']), + 'oa': ('vowel', ['Close-mid_back_rounded_vowel.ogg']), + 'ar': ('word', ['En-us-are.ogg']), + 'or': ('word', ['En-us-or.ogg']), + 'er': ('word', ['En-us-er.ogg']), + 'ai': ('strip', ['En-us-day.ogg', 'En-us-a.ogg']), + 'igh': ('word', ['En-us-eye.ogg']), + 'ow': ('word', ['En-us-ow.ogg']), + 'oi': ('strip', ['En-us-boy.ogg']), +} +# The sounds that are held (must match PHONEMES[id].stretch in lib.js) and how long. +HOLD_TARGET = { 'cont': 1.0, 'vowel': 0.9 } +# Glides and /h/ are not stretched: keep them natural but bounded. +NATURAL_MAX = { 'cont': 0.32, 'vowel': 0.45 } + +# ââ helpers ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ + +def log(*a): + print(*a, flush=True) + +def commons_info(titles): + """Batch imageinfo query. Returns {title: {url, license, artist, ...}} (missing files omitted).""" + out = {} + for i in range(0, len(titles), 50): + batch = titles[i:i + 50] + q = 'https://commons.wikimedia.org/w/api.php?' + urllib.parse.urlencode({ + 'action': 'query', 'format': 'json', 'titles': '|'.join('File:' + t for t in batch), + 'prop': 'imageinfo', 'iiprop': 'url|extmetadata|user', + 'iiextmetadatafilter': 'LicenseShortName|Artist|LicenseUrl'}) + d = json.load(urllib.request.urlopen(urllib.request.Request(q, headers=UA), timeout=60)) + for p in d['query']['pages'].values(): + t = p['title'].replace('File:', '').replace(' ', '_') + if 'missing' in p: + continue + ii = p['imageinfo'][0]; em = ii.get('extmetadata', {}) + artist = re.sub('<[^>]+>', '', em.get('Artist', {}).get('value', '') or '').strip() + artist = re.sub(r'^No machine-readable author provided\.\s*', '', artist) + artist = re.sub(r'\s+assumed \(based on copyright claims\)\.?$', '', artist) + out[t] = { + 'url': ii['url'].split('?')[0], + 'page': 'https://commons.wikimedia.org/wiki/File:' + t, + 'license': em.get('LicenseShortName', {}).get('value', '?'), + 'licenseUrl': em.get('LicenseUrl', {}).get('value', ''), + 'author': artist or ii.get('user', '?'), + } + time.sleep(1.0) + return out + +def fetch(url, dest): + if os.path.exists(dest): + return True + if OFFLINE: + return False + for attempt in range(6): + try: + data = urllib.request.urlopen(urllib.request.Request(url, headers=UA), timeout=60).read() + with open(dest, 'wb') as f: + f.write(data) + time.sleep(2.5 if 'wikimedia' in url else 0.1) + return True + except urllib.error.HTTPError as e: + if e.code == 429: + log(' 429 from server, backing off ...'); time.sleep(45 * (attempt + 1)) + elif e.code == 404: + return False + else: + raise + return False + +def decode(path): + raw = subprocess.run(['ffmpeg', '-v', 'quiet', '-i', path, '-f', 'f32le', '-ac', '1', '-ar', str(SR), '-'], + capture_output=True, check=True).stdout + return np.frombuffer(raw, np.float32).astype(np.float64) + +def encode_mp3(x, dest): + raw = (np.clip(x, -1, 1) * 32767).astype(np.int16).tobytes() + subprocess.run(['ffmpeg', '-v', 'quiet', '-y', '-f', 's16le', '-ac', '1', '-ar', str(SR), '-i', '-', + '-codec:a', 'libmp3lame', '-b:a', '64k', dest], input=raw, check=True) + +FRAME = SR // 100 # 10 ms + +def frames(x): + n = len(x) // FRAME + return x[:n * FRAME].reshape(n, FRAME) + +def rms(fr): + return np.sqrt((fr ** 2).mean(1) + 1e-12) + +def bandpass(x, lo, hi): + X = np.fft.rfft(x) + f = np.fft.rfftfreq(len(x), 1 / SR) + X[(f < lo) | (f > hi)] = 0 + return np.fft.irfft(X, len(x)) + +def band_rms(x, lo, hi): + """Per-10ms-frame RMS of the signal band-limited to [lo, hi] Hz.""" + return rms(frames(bandpass(x, lo, hi))) + +def fade(x, fin=0.008, fout=0.03): + x = x.copy() + a = int(fin * SR); b = int(fout * SR) + if a: x[:a] *= np.linspace(0, 1, a) + if b: x[-b:] *= np.linspace(1, 0, b) + return x + +def normalize(x, target_db=-18.0, peak_db=-1.0): + r = np.sqrt((x ** 2).mean() + 1e-12) + g = 10 ** (target_db / 20) / r + x = x * g + pk = np.abs(x).max() + lim = 10 ** (peak_db / 20) + if pk > lim: + x = x * (lim / pk) + return x + +def sec(fr_index): + return fr_index * FRAME / SR + +def sustained(arr, thresh, start=0, run=4): + """First index >= start where arr stays above thresh for `run` frames.""" + for k in range(start, len(arr) - run): + if all(arr[k + j] > thresh for j in range(run)): + return k + return len(arr) + +def voicing_onset(x, start_sample, search=0.25): + """Sample index where periodic low-band energy (a vowel) begins after start_sample. + + Uses 5 ms hops: the low band (80â900 Hz) has to climb past 35 % of the peak + it reaches within the search window and stay there. + """ + hop = SR // 200 + lo = bandpass(x, 80, 900) + end = min(len(x), start_sample + int(search * SR)) + n = (end - start_sample) // hop + if n < 3: + return start_sample + e = np.sqrt((lo[start_sample:start_sample + n * hop].reshape(n, hop) ** 2).mean(1) + 1e-12) + peak = e.max() + for k in range(n - 2): + if e[k] > peak * 0.35 and e[k + 1] > peak * 0.35 and e[k + 2] > peak * 0.35: + return start_sample + k * hop + return start_sample + n * hop + +# ââ WSOLA time-stretch (holds a continuant without looping artefacts) âââââââ + +def wsola(x, factor, N=1024, tol=320): + """Waveform-similarity overlap-add stretch of x by `factor` (>1 = longer). + + Frames of N samples (23 ms) with a Hann window at hop N/2 are laid down at + the output rate; each is taken from the input near its nominal position, + shifted by up to `tol` samples to line up with the natural continuation of + the previous frame (cross-correlation). Periodic sounds stay periodic and + noise stays noise â no seams, no buzz. When the nominal position runs past + the end of a short input it wraps around, so any steady sound can be held + for as long as needed. + """ + if factor <= 1.0 or len(x) < 512: + return x + N = min(N, (len(x) // 4) & ~1) # short inputs get shorter frames + tol = min(tol, N // 3) + Hs = N // 2 + w = 0.5 - 0.5 * np.cos(2 * np.pi * np.arange(N) / N) # periodic Hann: COLA at hop N/2 + Ha = Hs / factor + out_len = int(len(x) * factor) + y = np.zeros(out_len + N) + y[:N] += x[:N] * w + span = len(x) - N - 2 * tol - 1 + if span < N: + tol = max(0, (len(x) - 2 * N) // 2); span = len(x) - N - 2 * tol - 1 + prev, k = 0, 1 + while True: + out = k * Hs + if out + N > out_len: + break + pos = tol + int(round(k * Ha)) % max(1, span) + if prev + Hs + N > len(x): + prev = len(x) - Hs - N - 1 + nat = x[prev + Hs: prev + Hs + N] + lo = max(0, pos - tol); hi = min(len(x) - N - 1, pos + tol) + c = np.correlate(x[lo:hi + N], nat, 'valid') + d = lo + int(np.argmax(c)) + y[out:out + N] += x[d:d + N] * w + prev = d + k += 1 + return y[:out] + +def hold(x, target, onset=0.04, tail=0.04): + """Make a cut continuant last `target` seconds by stretching its steady middle.""" + need = target * SR + if len(x) >= need: + return x[:int(need)] + a = int(onset * SR); b = int(tail * SR) + if len(x) < a + b + SR // 20: + a = b = len(x) // 6 + head, mid, end = x[:a], x[a:len(x) - b], x[len(x) - b:] + factor = (need - a - b) / max(1, len(mid)) + mid2 = wsola(mid, factor) + xf = int(0.005 * SR) + def join(p, q): + if len(p) < xf or len(q) < xf: + return np.concatenate([p, q]) + ramp = np.linspace(0, 1, xf) + return np.concatenate([p[:-xf], p[-xf:] * (1 - ramp) + q[:xf] * ramp, q[xf:]]) + return join(join(head, mid2), end) + +# ââ phoneme cutting ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ + +def cut_phoneme(x, mode): + """Return (segment, (t0, t1), ok) â the isolated sound from a source recording.""" + tot = rms(frames(x)); low = band_rms(x, 0, 1000); high = band_rms(x, 1500, 12000) + peak = tot.max() + onset = sustained(tot, peak * 0.12) + if mode == 'vowel': + end = min(onset + 45, len(tot)) + for k in range(onset + 10, end): # stop early if the vowel dies out + if tot[k] < peak * 0.1: + end = k; break + return x[onset * FRAME:end * FRAME], (sec(onset), sec(end)), end - onset >= 15 + if mode in ('nasal', 'voiced', 'approx', 'fric'): + if mode == 'fric': + onset = sustained(high, high.max() * 0.06, run=5) + else: + onset = sustained(tot, peak * 0.09) # nasals / glides start quietly; skip the faintest lead-in + win = low[onset:onset + 90] + vmax = win.max() + frac = {'nasal': 0.5, 'voiced': 0.45, 'approx': 0.8, 'fric': 0.5}[mode] + vowel = onset + int(np.argmax(win > vmax * frac)) + vowel = max(onset + 8, vowel - 1) + return x[onset * FRAME:vowel * FRAME], (sec(onset), sec(vowel)), vowel - onset >= 8 + if mode == 'between': + vmax = low[onset:onset + 30].max() + k = onset + while k < len(low) and low[k] < vmax * 0.4: k += 1 # into the first vowel + while k < len(low) and low[k] > vmax * 0.25: k += 1 # to its end + start = k + 1 + k = start + 5 + while k < len(low) and low[k] < vmax * 0.35: k += 1 # until the second vowel + end = k - 1 + return x[start * FRAME:end * FRAME], (sec(start), sec(end)), end - start >= 8 + if mode in ('stop', 'affric'): + # Burst: the first frame where the high band jumps out of the leading silence. + hmax = high.max() + burst = sustained(high, hmax * 0.08, run=1) + s0 = max(0, burst * FRAME - int(0.005 * SR)) + v = voicing_onset(x, s0 + int(0.012 * SR), search=0.30 if mode == 'affric' else 0.18) + vot = v - s0 # burst -> voicing (aspiration length) + lo_len = int((0.10 if mode == 'affric' else 0.075) * SR) + hi_len = int((0.20 if mode == 'affric' else 0.16) * SR) + ok = int(0.02 * SR) <= vot <= hi_len + # Keep ~25 ms of the vowel's onset after voicing starts (faded out by the + # caller) so the release is audible; voiced stops get their 75 ms floor. + length = int(min(max(vot + 0.025 * SR, lo_len), hi_len)) + return x[s0:s0 + length], (s0 / SR, (s0 + length) / SR), ok + if mode == 'word': + seg = trim_word(x) + return seg, (0, len(seg) / SR), True + if mode == 'strip': + burst = sustained(high, high.max() * 0.08, run=1) + v = voicing_onset(x, burst * FRAME + int(0.01 * SR), search=0.15) + start = v + int(0.02 * SR) + seg = trim_word(x[start:]) + ok = 0.2 <= len(seg) / SR <= 0.8 + return seg, (start / SR, (start + len(seg)) / SR), ok + raise ValueError(mode) + +def trim_word(x): + tot = rms(frames(x)); peak = tot.max() + on = np.where(tot > peak * 0.05)[0] + a = max(0, on[0] - 3); b = min(len(tot), on[-1] + 6) + return x[a * FRAME:b * FRAME] + +def build_phoneme(pid, mode, x): + seg, span, ok = cut_phoneme(x, mode) + ptype = PHONEME_TYPES.get(pid, 'cont') + stretch = PHONEME_STRETCH.get(pid, False) + if mode in ('stop', 'affric'): + seg = fade(seg, fin=0.003, fout=0.03) + elif mode in ('word', 'strip'): + if stretch: # a steady vowel recorded as a word (er) + seg = hold(seg, HOLD_TARGET[ptype], onset=0.08, tail=0.08) + seg = fade(seg, fin=0.02 if mode == 'strip' else 0.006, fout=0.05 if stretch else 0.03) + elif stretch: + seg = hold(seg, HOLD_TARGET[ptype], onset=0.03 if ptype == 'vowel' else 0.04) + seg = fade(seg, fin=0.012, fout=0.06) + else: + seg = fade(seg[:int(NATURAL_MAX[ptype] * SR)], fin=0.012, fout=0.05) + return normalize(seg), span, ok + +# ââ main âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ + +def lib(): + js = ("const L=require(process.argv[1]);" + "console.log(JSON.stringify({words:L.WORDS.map(w=>[w.w,w.art])," + "art:[...new Set([].concat(L.TOPICS.map(t=>t.art),[L.ANY_TOPIC.art],L.PATRONS.map(p=>p.art),L.AVATARS,L.UI_ART,L.WORDS.map(w=>w.art)))]," + "ph:Object.fromEntries(Object.entries(L.PHONEMES).map(([k,v])=>[k,[v.type,!!v.stretch]]))}))") + out = subprocess.run(['node', '-e', js, os.path.join(GAME, 'lib.js')], capture_output=True, text=True, check=True).stdout + return json.loads(out) + +PHONEME_TYPES = {} +PHONEME_STRETCH = {} + +def credit(credits, src, meta, use): + """One source file can serve several uses (a word recording also lends its first sound).""" + c = credits.setdefault(src, dict(meta, uses=[])) + c['uses'].append(use) + +def art_slug(name): + return name.split('/')[0].lower().replace(' ', '_') + +def art_paths(name): + """(url path inside the Fluent repo, output file name) for an asset like 'Bug' or 'Child/Default'.""" + base, _, tone = name.partition('/') + slug = art_slug(name) + if tone: + return ('assets/%s/%s/3D/%s_3d_%s.png' % (base, tone, slug, tone.lower()), slug + '_' + tone.lower() + '.webp') + return ('assets/%s/3D/%s_3d.png' % (base, slug), slug + '.webp') + +def main(): + for d in (CACHE, OUT_PH, OUT_WD, OUT_IMG): + os.makedirs(d, exist_ok=True) + L = lib() + for k, (t, st) in L['ph'].items(): + PHONEME_TYPES[k] = t; PHONEME_STRETCH[k] = st + words = L['words'] + manifest = {'phonemes': {}, 'words': {}, 'images': {}} + credits = {} + + # ââ metadata + downloads âââââââââââââââââââââââââââââââââââââââââââââââ + info_path = os.path.join(CACHE, 'commons-info.json') + info = json.load(open(info_path)) if os.path.exists(info_path) else {} + wanted = sorted(set(sum((srcs for _, srcs in PHONEME_SOURCES.values()), [])) | set('En-us-%s.ogg' % w for w, _ in words)) + missing_meta = [t for t in wanted if t not in info] + if missing_meta and not OFFLINE: + log('Querying Commons for %d files ...' % len(missing_meta)) + info.update(commons_info(missing_meta)) + for t in missing_meta: + info.setdefault(t, None) # remember 404s too + json.dump(info, open(info_path, 'w'), indent=1) + + def get_clip(title): + meta = info.get(title) + if not meta: + return None, None + dest = os.path.join(CACHE, title) + if not fetch(meta['url'], dest): + return None, meta + return dest, meta + + # ââ phonemes ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ + log('Phonemes:') + for pid, (mode, srcs) in PHONEME_SOURCES.items(): + chosen = None + for src in srcs: + path, meta = get_clip(src) + if not path: + continue + seg, (t0, t1), ok = build_phoneme(pid, mode, decode(path)) + if chosen is None or (ok and not chosen[3]): + chosen = (src, meta, seg, ok, t0, t1) + if ok: + break + if not chosen: + log(' %-3s MISSING (%s)' % (pid, ', '.join(srcs))); continue + src, meta, seg, ok, t0, t1 = chosen + encode_mp3(seg, os.path.join(OUT_PH, pid + '.mp3')) + manifest['phonemes'][pid] = {'file': 'phonemes/%s.mp3' % pid, 'dur': round(len(seg) / SR, 3)} + held = ' (held by time-stretching)' if PHONEME_STRETCH.get(pid) and mode not in ('word', 'strip') else '' + credit(credits, src, meta, 'phoneme /%s/ â cut %.2fâ%.2fs of the original%s' % (pid, t0, t1, held)) + log(' %-3s %-7s %-45s %.2f-%.2fs -> %4d ms%s' % (pid, mode, src, t0, t1, 1000 * len(seg) / SR, '' if ok else ' (no source cut cleanly; best effort)')) + + # ââ words âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ + log('Words:') + for w, _ in words: + src = 'En-us-%s.ogg' % w + path, meta = get_clip(src) + if not path: + continue + x = normalize(fade(trim_word(decode(path)), fin=0.005, fout=0.02)) + encode_mp3(x, os.path.join(OUT_WD, w + '.mp3')) + manifest['words'][w] = {'file': 'words/%s.mp3' % w, 'dur': round(len(x) / SR, 3)} + credit(credits, src, meta, 'word "%s"' % w) + norec = [w for w, _ in words if w not in manifest['words']] + log(' %d of %d words have recordings; browser speech for: %s' % (len(manifest['words']), len(words), ' '.join(norec))) + + # ââ pictures ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ + log('Pictures:') + missing = [] + for name in sorted(L['art']): + rel, fname = art_paths(name) + dest = os.path.join(OUT_IMG, fname) + if not os.path.exists(dest): + raw = os.path.join(CACHE, 'fluent-' + fname.replace('.webp', '.png')) + if not fetch(FLUENT + urllib.parse.quote(rel), raw): + missing.append(name); continue + subprocess.run(['ffmpeg', '-v', 'quiet', '-y', '-i', raw, '-vf', 'scale=%d:%d:flags=lanczos' % (IMG_PX, IMG_PX), + '-c:v', 'libwebp', '-quality', '88', dest], check=True) + manifest['images'][name] = 'img/' + fname + for name in missing: + log(' MISSING fluent asset: %s' % name) + log(' %d pictures' % len(manifest['images'])) + for f in os.listdir(OUT_IMG): # drop pictures no longer referenced + if 'img/' + f not in manifest['images'].values(): + os.remove(os.path.join(OUT_IMG, f)) + + # ââ manifest + credits ââââââââââââââââââââââââââââââââââââââââââââââââ + with open(os.path.join(GAME, 'assets', 'manifest.js'), 'w') as f: + f.write('// Generated by tools/story-stamp-assets.py â do not edit by hand.\n') + f.write('window.SS_ASSETS = ' + json.dumps(manifest, indent=1, sort_keys=True) + ';\n') + + by_author = {} + for title, c in sorted(credits.items()): + by_author.setdefault((c['author'], c['license'], c.get('licenseUrl', '')), []).append((title, c)) + with open(os.path.join(GAME, 'CREDITS.md'), 'w') as f: + f.write('# Story Stamp â asset credits\n\n') + f.write('Generated by `tools/story-stamp-assets.py`. Every third-party file used by the game is listed here with its source, author and license.\n\n') + f.write('## Pictures\n\n') + f.write('All pictures are [Fluent Emoji](https://github.com/microsoft/fluentui-emoji) 3D graphics, Š Microsoft Corporation, licensed [MIT](%s). Files: `games/story-stamp/assets/img/*.webp` (scaled to %d px, WebP). Assets used: %s.\n\n' % (FLUENT_LICENSE, IMG_PX, ', '.join(sorted(L['art'])))) + f.write('## Audio\n\n') + f.write('Recordings from [Wikimedia Commons](https://commons.wikimedia.org/). Phoneme clips are *cut from* the listed originals (an isolated sound is extracted from an IPA "sa ⌠sa" recording or from the start of a word, and continuants are held longer by time-stretching); word clips are trimmed and level-matched. Modified files are `games/story-stamp/assets/audio/**/*.mp3`.\n\n') + for (author, lic, url), items in sorted(by_author.items(), key=lambda kv: (-len(kv[1]), kv[0])): + lic_md = '[%s](%s)' % (lic, url) if url else lic + f.write('### %s â %s\n\n' % (author, lic_md)) + for title, c in items: + f.write('- [%s](%s) â %s\n' % (title, c['page'], '; '.join(c['uses']))) + f.write('\n') + f.write('## Speech\n\nInstructions, patron chatter, and any word without a recording are voiced by the browser\'s built-in speech synthesis (Web Speech API); nothing is sent to a server.\n') + log('Wrote manifest.js and CREDITS.md') + +if __name__ == '__main__': + main() diff --git a/tools/story-stamp-audit.py b/tools/story-stamp-audit.py @@ -0,0 +1,367 @@ +#!/usr/bin/env python3 +"""Audit the Story Stamp phoneme clips with methods independent of how they were cut. + + python3 tools/story-stamp-audit.py # all checks, prints a report + python3 tools/story-stamp-audit.py --no-asr # skip the Vosk recognition test + +Checks (each one uses different evidence, so a clip has to pass all of them): + + 1. Acoustic sanity per clip â duration within the range its class needs, + voicing (autocorrelation periodicity) where the sound is voiced and not + where it is voiceless, spectral centroid in the band expected for the + class, no stutter (amplitude modulation at 5â40 Hz, which is what a + looped short clip sounds like), and a flat level over a held sound. + 2. Cross-speaker match â the onset of every recorded *word* (a different + speaker, a different recording) is cut out and pooled by its first + sound; each phoneme clip is then classified by nearest MFCC reference. + The true sound should be the nearest, or at least in the top three. + 3. Recognition â words are assembled from the phoneme clips (at speech + rate with no gaps, and as the game's final blend pass plays them) and fed + to the Vosk speech recogniser with the word list as its grammar; the real + recordings are the control. Per-phoneme accuracy is the recognition rate + of the natural-rate words containing it. Concatenated sounds have no + coarticulation, so around half is a good result; a sound far below the + others is what to look for. + 4. Spectrograms â a contact sheet of every clip is written to + .cache/audit/phonemes.png for a human to look at. + +Exit status is 1 when a hard check fails, so the audit can gate a commit. +""" +import json, os, subprocess, sys, wave + +import numpy as np + +ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +GAME = os.path.join(ROOT, 'games', 'story-stamp') +AUDIO = os.path.join(GAME, 'assets', 'audio') +OUT = os.path.join(ROOT, '.cache', 'audit') +VOSK_MODEL = os.path.join(ROOT, '.cache', 'vosk', 'vosk-model-small-en-us-0.15') +PYLIB = os.path.join(ROOT, '.cache', 'pylib') +SR = 16000 +NO_ASR = '--no-asr' in sys.argv + +# Expected acoustics per phoneme: voiced?, centroid band (Hz), duration band (s). +EXPECT = { + 's': (False, (4500, 9000), (0.8, 1.2)), 'z': (True, (1500, 8000), (0.8, 1.2)), + 'sh': (False, (2500, 6000), (0.8, 1.2)), 'f': (False, (1200, 8000), (0.8, 1.2)), + 'v': (True, (150, 4000), (0.8, 1.2)), 'th': (False, (1200, 8000), (0.8, 1.2)), + 'h': (False, (600, 5000), (0.15, 0.4)), + 'm': (True, (150, 900), (0.8, 1.2)), 'n': (True, (150, 1200), (0.8, 1.2)), + 'ng': (True, (150, 1200), (0.8, 1.2)), 'l': (True, (200, 1800), (0.8, 1.2)), + 'r': (True, (200, 1800), (0.8, 1.2)), 'w': (True, (150, 1200), (0.12, 0.4)), + 'y': (True, (200, 2500), (0.12, 0.4)), + 'p': (False, (300, 6000), (0.06, 0.17)), 't': (False, (700, 7000), (0.06, 0.17)), + 'k': (False, (600, 5000), (0.06, 0.17)), 'b': (True, (150, 3000), (0.05, 0.17)), + 'd': (True, (200, 4000), (0.05, 0.17)), 'g': (True, (200, 3500), (0.05, 0.17)), + 'ch': (False, (2000, 6000), (0.09, 0.22)), 'j': (True, (1000, 5000), (0.08, 0.22)), + 'a': (True, (400, 2200), (0.7, 1.1)), 'e': (True, (400, 2200), (0.7, 1.1)), + 'i': (True, (300, 2200), (0.7, 1.1)), 'o': (True, (300, 1600), (0.7, 1.1)), + 'u': (True, (300, 1800), (0.7, 1.1)), 'ee': (True, (250, 2200), (0.7, 1.1)), + 'oo': (True, (200, 1500), (0.7, 1.1)), 'uu': (True, (200, 1500), (0.7, 1.1)), + 'oa': (True, (200, 1500), (0.3, 0.6)), 'er': (True, (250, 1800), (0.3, 1.1)), + 'ar': (True, (250, 1800), (0.3, 0.9)), 'or': (True, (200, 1600), (0.3, 0.9)), + 'ai': (True, (300, 2400), (0.3, 0.9)), 'igh':(True, (300, 2200), (0.3, 0.9)), + 'ow': (True, (250, 1800), (0.3, 0.9)), 'oi': (True, (250, 1800), (0.3, 0.9)), +} +HELD = {'s', 'z', 'sh', 'f', 'v', 'th', 'm', 'n', 'ng', 'l', 'r', 'a', 'e', 'i', 'o', 'u', 'ee', 'oo', 'uu', 'er'} + +def log(*a): + print(*a, flush=True) + +def decode(path, sr=SR): + raw = subprocess.run(['ffmpeg', '-v', 'quiet', '-i', path, '-f', 'f32le', '-ac', '1', '-ar', str(sr), '-'], + capture_output=True, check=True).stdout + return np.frombuffer(raw, np.float32).astype(np.float64) + +def write_wav(path, x, sr=SR): + with wave.open(path, 'wb') as w: + w.setnchannels(1); w.setsampwidth(2); w.setframerate(sr) + w.writeframes((np.clip(x, -1, 1) * 32767).astype(np.int16).tobytes()) + +def lib(): + js = ("const L=require(process.argv[1]);console.log(JSON.stringify({" + "words:L.WORDS.map(w=>({w:w.w,units:L.phonemeUnits(w)})),ph:Object.keys(L.PHONEMES)," + "stretch:Object.fromEntries(Object.entries(L.PHONEMES).map(([k,v])=>[k,!!v.stretch]))," + "passes:L.BLEND_PASSES,minHold:L.MIN_HOLD}))") + return json.loads(subprocess.run(['node', '-e', js, os.path.join(GAME, 'lib.js')], capture_output=True, text=True, check=True).stdout) + +# ââ features âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ + +def frame(x, n=400, hop=160): + if len(x) < n: + x = np.concatenate([x, np.zeros(n - len(x))]) + m = 1 + (len(x) - n) // hop + idx = np.arange(n)[None, :] + hop * np.arange(m)[:, None] + return x[idx] + +def envelope(x, hop=80): + m = len(x) // hop + return np.sqrt((x[:m * hop].reshape(m, hop) ** 2).mean(1) + 1e-12) + +def lowpass(x, hz=1000): + X = np.fft.rfft(x); f = np.fft.rfftfreq(len(x), 1 / SR); X[f > hz] = 0 + return np.fft.irfft(X, len(x)) + +def voicing_fraction(x): + """Share of 25 ms frames whose normalised autocorrelation (below 1 kHz) peaks at 75â400 Hz above 0.5.""" + fr = frame(lowpass(x), 400, 160) + fr = fr[envelope_of(fr) > envelope_of(fr).max() * 0.2] + if not len(fr): + return 0.0 + hi = SR // 75; lo = SR // 400 + voiced = 0 + for f in fr: + f = f - f.mean() + e = (f ** 2).sum() + 1e-9 + ac = np.correlate(f, f, 'full')[len(f) - 1:] / e + voiced += ac[lo:hi].max() > 0.5 + return voiced / len(fr) + +def envelope_of(fr): + return np.sqrt((fr ** 2).mean(1) + 1e-12) + +def centroid(x): + fr = frame(x, 400, 160) + fr = fr[envelope_of(fr) > envelope_of(fr).max() * 0.25] + spec = np.abs(np.fft.rfft(fr * np.hanning(400), axis=1)) ** 2 + f = np.fft.rfftfreq(400, 1 / SR) + return float((spec * f).sum() / (spec.sum() + 1e-12)) + +def stutter(x): + """Level dips per second inside the clip (a looped 60 ms slice gives ~16/s; a held sound ~0).""" + env = envelope(x, 80) # 200 Hz envelope rate + if len(env) < 30: + return 0.0 + env = np.convolve(env, np.ones(3) / 3, 'same') + a, b = len(env) // 10, len(env) - len(env) // 10 + dips = 0 + for k in range(a + 1, b - 1): + if env[k] < env[k - 1] and env[k] <= env[k + 1]: + left = env[max(a, k - 12):k].max(); right = env[k + 1:min(b, k + 13)].max() + if env[k] < 0.63 * min(left, right): + dips += 1 + return dips / ((b - a) / 200.0) + +def flatness(x): + env = envelope(x, 160) + mid = env[len(env) // 5: -len(env) // 5] if len(env) > 10 else env + return float(mid.std() / (mid.mean() + 1e-9)) + +def mel_bank(nfilt=26, nfft=512): + lo, hi = 0, 2595 * np.log10(1 + (SR / 2) / 700) + pts = 700 * (10 ** (np.linspace(lo, hi, nfilt + 2) / 2595) - 1) + bins = np.floor((nfft + 1) * pts / SR).astype(int) + fb = np.zeros((nfilt, nfft // 2 + 1)) + for m in range(1, nfilt + 1): + a, b, c = bins[m - 1], bins[m], bins[m + 1] + for k in range(a, b): fb[m - 1, k] = (k - a) / max(1, b - a) + for k in range(b, c): fb[m - 1, k] = (c - k) / max(1, c - b) + return fb + +FB = mel_bank() + +def mfcc(x): + """Level-normalised log-mel spectral shape (26 bands) of the loud frames of x.""" + fr = frame(x, 400, 80) + keep = envelope_of(fr) > envelope_of(fr).max() * 0.25 + fr = fr[keep] if keep.sum() >= 2 else fr + spec = np.abs(np.fft.rfft(fr * np.hanning(400), 512, axis=1)) ** 2 + mel = np.log(spec @ FB.T + 1e-8) + mel = mel - mel.mean(1, keepdims=True) + m = mel.mean(0) + return m - m.mean() + +def cos(a, b): + return float(a @ b / (np.linalg.norm(a) * np.linalg.norm(b) + 1e-9)) + +# ââ 1. acoustic sanity âââââââââââââââââââââââââââââââââââââââââââââââââââââââ + +def check_acoustics(clips): + log('\n1. Acoustic sanity (dur s / voiced / centroid Hz / stutter / flat)') + fails = [] + for pid in sorted(clips, key=lambda p: (len(p), p)): + x = clips[pid] + voiced_exp, (clo, chi), (dlo, dhi) = EXPECT[pid] + dur = len(x) / SR + vf = voicing_fraction(x) + cen = centroid(x) + st = stutter(x) + fl = flatness(x) if pid in HELD else 0.0 + problems = [] + if not (dlo <= dur <= dhi): problems.append('duration') + if voiced_exp and vf < (0.3 if pid in ('b', 'd', 'g', 'j') else 0.5): problems.append('should be voiced (%.0f%%)' % (vf * 100)) + if not voiced_exp and vf > 0.5 and pid not in ('p', 't', 'k', 'ch'): problems.append('should be voiceless (%.0f%%)' % (vf * 100)) + if not (clo <= cen <= chi): problems.append('centroid') + if pid in HELD and st > 4: problems.append('stutter %.1f dips/s' % st) + if pid in HELD and fl > 0.35: problems.append('uneven level %.2f' % fl) + flag = 'FAIL ' + ', '.join(problems) if problems else 'ok' + log(' %-3s %5.2f %3.0f%% %5.0f %4.2f %4.2f %s' % (pid, dur, vf * 100, cen, st, fl, flag)) + if problems: fails.append(pid) + return fails + +# ââ 2. cross-speaker nearest-neighbour ââââââââââââââââââââââââââââââââââââââ + +def word_onset(x, stop): + """The first sound of a recorded word: from its onset for 60 ms (stop) / 80 ms (continuant).""" + env = envelope(x, 80) + on = np.argmax(env > env.max() * (0.12 if stop else 0.06)) * 80 + n = int((0.06 if stop else 0.08) * SR) + return x[on:on + n] + +def check_cross_speaker(clips, words, stretch): + log('\n2. Cross-speaker match (nearest of the word-onset references)') + refs = {} + stops = {'p', 't', 'k', 'b', 'd', 'g', 'ch', 'j'} + for w in words: + first = w['units'][0][0] if w['units'] and w['units'][0] else None + path = os.path.join(AUDIO, 'words', w['w'] + '.mp3') + if first in EXPECT and os.path.exists(path) and first not in ('a', 'e', 'i', 'o', 'u', 'ee', 'oo', 'uu', 'oa', 'ar', 'or', 'er', 'ai', 'igh', 'ow', 'oi'): + seg = word_onset(decode(path), first in stops) + if len(seg) > 400: + refs.setdefault(first, []).append(mfcc(seg)) + means = {p: np.median(v, 0) for p, v in refs.items() if len(v) >= 2} + fails = [] + for pid in sorted(means): + # Compare the game clip's *onset* region so held and short clips are treated alike. + x = clips[pid][:int((0.06 if pid in stops else 0.08) * SR)] + me = mfcc(x) + ranked = sorted(means, key=lambda q: -cos(me, means[q])) + rank = ranked.index(pid) + 1 + flag = 'ok' if rank == 1 else ('near' if rank <= 3 else 'FAIL') + log(' %-3s refs=%-2d nearest: %-14s rank %d %s' % (pid, len(refs[pid]), ' '.join(ranked[:3]), rank, flag)) + if rank > 3: fails.append(pid) + return fails + +# ââ 3. recognition of assembled words âââââââââââââââââââââââââââââââââââââââ + +def assemble(units, clips, stretch, hold, gap, sr=SR): + parts = [] + fade = int(0.04 * sr) + for unit in units: + for p in unit: + x = clips[p] + if stretch.get(p): + n = int(min(len(x), max(0.16, hold) * sr)) + x = x[:n].copy() + x[-fade:] *= np.linspace(1, 0, fade) + parts.append(x) + parts.append(np.zeros(int(0.015 * sr))) + parts.append(np.zeros(int(gap * sr))) + return np.concatenate([np.zeros(int(0.2 * sr))] + parts + [np.zeros(int(0.4 * sr))]) + +def recognise(rec, x): + from vosk import KaldiRecognizer + pcm = (np.clip(x, -1, 1) * 32767).astype(np.int16).tobytes() + rec.Reset() + rec.AcceptWaveform(pcm) + return json.loads(rec.FinalResult()).get('text', '') + +def check_recognition(clips, words, stretch, passes): + log('\n3. Recognition (Vosk, grammar = the word list)') + sys.path.insert(0, PYLIB) + try: + from vosk import Model, KaldiRecognizer, SetLogLevel + except ImportError: + log(' vosk not installed (pip install --target .cache/pylib vosk); skipped'); return [] + if not os.path.isdir(VOSK_MODEL): + log(' model missing at %s; skipped' % VOSK_MODEL); return [] + SetLogLevel(-1) + model = Model(VOSK_MODEL) + vocab = sorted(set(w['w'] for w in words)) + ['[unk]'] + rec = KaldiRecognizer(model, SR, json.dumps(vocab)) + have = [w for w in words if os.path.exists(os.path.join(AUDIO, 'words', w['w'] + '.mp3')) + and all(p in clips for u in w['units'] for p in u)] + # 'natural' plays the clips at speech rate with no gaps: the fairest test of + # what each clip *is*. The game's own final pass is slower and is reported + # for information (a recogniser trained on speech dislikes drawn-out sounds). + natural = { 'hold': 0.12, 'gap': 0.0 } + conds = [('recorded', None), ('natural', natural), ('blend pass 3', passes[2])] + per_ph = {} + results = {} + os.makedirs(os.path.join(OUT, 'blends'), exist_ok=True) + for name, pas in conds: + hits = 0 + for w in have: + if pas is None: + x = decode(os.path.join(AUDIO, 'words', w['w'] + '.mp3')) + x = np.concatenate([np.zeros(int(0.2 * SR)), x, np.zeros(int(0.4 * SR))]) + else: + x = assemble(w['units'], clips, stretch, pas['hold'], pas['gap']) + if name == 'blend pass 3': + write_wav(os.path.join(OUT, 'blends', w['w'] + '.wav'), x) + text = recognise(rec, x) + ok = text.strip() == w['w'] + hits += ok + results.setdefault(w['w'], {})[name] = text + if name == 'natural': + for u in w['units']: + for p in u: + per_ph.setdefault(p, [0, 0]); per_ph[p][1] += 1; per_ph[p][0] += ok + log(' %-13s %3d / %3d words recognised (%.0f%%)' % (name, hits, len(have), 100 * hits / max(1, len(have)))) + log(' per phoneme (share of natural-rate words containing it that were recognised):') + weak = [] + line = [] + for p in sorted(per_ph, key=lambda q: per_ph[q][0] / per_ph[q][1]): + h, n = per_ph[p] + line.append('%s %d/%d' % (p, h, n)) + if n >= 6 and h / n < 0.25: weak.append(p) + for i in range(0, len(line), 8): + log(' ' + ' '.join(line[i:i + 8])) + misses = [(w, r) for w, r in results.items() if r.get('natural') != w] + log(' natural-rate misses: ' + ', '.join('%s->%s' % (w, r.get('natural') or 'â ') for w, r in misses[:40]) + (' âŚ' if len(misses) > 40 else '')) + json.dump(results, open(os.path.join(OUT, 'recognition.json'), 'w'), indent=1) + return weak + +# ââ 4. spectrogram sheet âââââââââââââââââââââââââââââââââââââââââââââââââââââ + +def spectrogram_sheet(clips): + os.makedirs(OUT, exist_ok=True) + tiles = [] + for pid in sorted(clips, key=lambda p: (len(p), p)): + wav = os.path.join(OUT, 'ph_%s.wav' % pid) + write_wav(wav, clips[pid]) + png = os.path.join(OUT, 'ph_%s.png' % pid) + subprocess.run(['ffmpeg', '-v', 'quiet', '-y', '-i', wav, '-filter_complex', + '[0:a]apad=pad_dur=1.2,atrim=0:1.2,showspectrumpic=s=420x120:legend=0:scale=log[s];' + '[0:a]apad=pad_dur=1.2,atrim=0:1.2,showwavespic=s=420x40[w];[s][w]vstack,' + 'drawtext=text=\'%s\':x=6:y=6:fontsize=22:fontcolor=white' % pid, png], check=True) + tiles.append(png) + cols = 4 + rows = [tiles[i:i + cols] for i in range(0, len(tiles), cols)] + while len(rows[-1]) < cols: rows[-1].append(rows[-1][-1]) + args = ['ffmpeg', '-v', 'quiet', '-y'] + for r in rows: + for t in r: args += ['-i', t] + fc = '' + n = 0 + for ri, r in enumerate(rows): + fc += ''.join('[%d]' % (n + i) for i in range(cols)) + 'hstack=%d[r%d];' % (cols, ri) + n += cols + fc += ''.join('[r%d]' % i for i in range(len(rows))) + 'vstack=%d' % len(rows) + subprocess.run(args + ['-filter_complex', fc, os.path.join(OUT, 'phonemes.png')], check=True) + log('\n4. Spectrogram sheet written to .cache/audit/phonemes.png (%d clips)' % len(tiles)) + +# ââ main âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ + +def main(): + L = lib() + os.makedirs(OUT, exist_ok=True) + clips = {} + for pid in L['ph']: + path = os.path.join(AUDIO, 'phonemes', pid + '.mp3') + if os.path.exists(path): + x = decode(path) + i = 0 + while i < len(x) and abs(x[i]) < 0.005: i += 1 # encoder padding + clips[pid] = x[max(0, i - 8):] + missing = [p for p in L['ph'] if p not in clips] + log('Phoneme clips: %d of %d%s' % (len(clips), len(L['ph']), ('; missing ' + ' '.join(missing)) if missing else '')) + f1 = check_acoustics(clips) + f2 = check_cross_speaker(clips, L['words'], L['stretch']) + f3 = [] if NO_ASR else check_recognition(clips, L['words'], L['stretch'], L['passes']) + spectrogram_sheet(clips) + log('\nSummary: acoustic fails %s | cross-speaker fails %s | weak in recognition %s' % (f1 or 'none', f2 or 'none', f3 or 'none')) + return 1 if (f1 or missing) else 0 + +if __name__ == '__main__': + sys.exit(main())