kgames

KGames — free keyboard games for kids
git clone https://www.keyboard.games/code/kgames.git
Log | Files | Refs | README | LICENSE

lib.js (27064B)


      1 (function (root) {
      2   'use strict';
      3 
      4   // ── Mad Libs templates ──────────────────────────────────────────────────────
      5   // Every story is adapted from a public-domain children’s classic (see the
      6   // `source` field — surfaced to parents in the page footer). Kid-friendly
      7   // category labels only (never "noun/verb/adjective").
      8   // `story` uses {key} placeholders matching each blank's `key`.
      9 
     10   const MADLIB_TEMPLATES = [
     11     {
     12       id: 'peter-rabbit',
     13       title: 'The Tale of Peter Rabbit',
     14       source: {
     15         title: 'The Tale of Peter Rabbit',
     16         author: 'Beatrix Potter',
     17         url: 'https://www.gutenberg.org/ebooks/14838',
     18       },
     19       blanks: [
     20         { key: 'name',   label: 'Tell me your name!',              voice: 'Tell me your name!' },
     21         { key: 'food',   label: 'Tell me a yummy food!',           voice: 'Tell me a yummy food!' },
     22         { key: 'color',  label: 'Tell me a color!',                voice: 'Tell me a color!' },
     23         { key: 'animal', label: 'Tell me an animal!',              voice: 'Tell me an animal!' },
     24         { key: 'silly',  label: 'Tell me a silly word!',           voice: 'Tell me a silly word!' },
     25       ],
     26       story: 'Once upon a time there were four little rabbits, and the naughtiest one was named {name}. ' +
     27              'Mother said, "Do not go into Mr. McGregor’s garden!" But {name} squeezed under the gate ' +
     28              'and ate some {food} until his tummy felt funny. Then Mr. McGregor came running, waving a rake! ' +
     29              '{name} lost one {color} shoe among the cabbages and hid in a watering can with a little {animal}. ' +
     30              'He sneezed "{silly}!" and ran all the way home to bed.',
     31     },
     32     {
     33       id: 'three-pigs',
     34       title: 'The Three Little Pigs',
     35       source: {
     36         title: 'The Story of the Three Little Pigs (English Fairy Tales)',
     37         author: 'Joseph Jacobs',
     38         url: 'https://www.gutenberg.org/ebooks/7439',
     39       },
     40       blanks: [
     41         { key: 'name',   label: 'Tell me your name!',              voice: 'Tell me your name!' },
     42         { key: 'stuff',  label: 'Tell me something you can build with!', voice: 'Tell me something you can build with!' },
     43         { key: 'food',   label: 'Tell me a yummy food!',           voice: 'Tell me a yummy food!' },
     44         { key: 'silly',  label: 'Tell me a silly word!',           voice: 'Tell me a silly word!' },
     45       ],
     46       story: 'There were three little pigs, and the cleverest one was named {name}. ' +
     47              'The first built a house of straw, the second a house of sticks, but {name} built a strong house of {stuff}. ' +
     48              'Along came the wolf and called, "Little pig, little pig, let me come in!" ' +
     49              '"Not by the hair of my chinny chin chin!" said {name}. ' +
     50              'So the wolf huffed, and puffed, and shouted "{silly}!" — but the house of {stuff} would not fall down. ' +
     51              'The wolf ran away, and {name} had {food} for supper.',
     52     },
     53     {
     54       id: 'three-bears',
     55       title: 'The Story of the Three Bears',
     56       source: {
     57         title: 'The Story of the Three Bears (English Fairy Tales)',
     58         author: 'Joseph Jacobs',
     59         url: 'https://www.gutenberg.org/ebooks/7439',
     60       },
     61       blanks: [
     62         { key: 'name',   label: 'Tell me a friend’s name!',        voice: 'Tell me a friend’s name!' },
     63         { key: 'food',   label: 'Tell me a yummy food!',           voice: 'Tell me a yummy food!' },
     64         { key: 'animal', label: 'Tell me an animal!',              voice: 'Tell me an animal!' },
     65         { key: 'action', label: 'Tell me something you can do!',   voice: 'Tell me something you can do!' },
     66       ],
     67       story: 'Three bears lived in a little house in the wood, and each one had a bowl of {food}. ' +
     68              'While it cooled, a girl named {name} walked right in. ' +
     69              'She tasted the great big bowl — too hot! The middle bowl — too cold! ' +
     70              'The teeny tiny bowl was just right, so she ate it all up. ' +
     71              'Then she sat in the teeny tiny chair until it went CRASH, and fell fast asleep upstairs. ' +
     72              'When the bears came home the teeny tiny bear squeaked, "Somebody has been eating my {food}!" ' +
     73              '{name} woke up, saw a {animal} peeking through the window, and began to {action} all the way home.',
     74     },
     75     {
     76       id: 'lion-mouse',
     77       title: 'The Lion and the Mouse',
     78       source: {
     79         title: 'The Lion and the Mouse (The Fables of Æsop)',
     80         author: 'Æsop, told anew by Joseph Jacobs',
     81         url: 'https://www.gutenberg.org/ebooks/28',
     82       },
     83       blanks: [
     84         { key: 'name',   label: 'Tell me your name!',              voice: 'Tell me your name!' },
     85         { key: 'place',  label: 'Tell me a place!',                voice: 'Tell me a place!' },
     86         { key: 'silly',  label: 'Tell me a silly word!',           voice: 'Tell me a silly word!' },
     87       ],
     88       story: 'A lion was fast asleep in {place} when a little mouse named {name} ran right across his nose. ' +
     89              'The lion woke with a roar and caught the mouse in his big paw. ' +
     90              '"Please let me go," said {name}, "and one day I will help you!" ' +
     91              'The lion laughed "{silly}!" and let the mouse go. ' +
     92              'Not long after, hunters tied the lion up with ropes — and tiny {name} nibbled every rope through. ' +
     93              'Even a little friend can be a big help.',
     94     },
     95     {
     96       id: 'hare-tortoise',
     97       title: 'The Hare and the Tortoise',
     98       source: {
     99         title: 'The Hare and the Tortoise (The Fables of Æsop)',
    100         author: 'Æsop, told anew by Joseph Jacobs',
    101         url: 'https://www.gutenberg.org/ebooks/28',
    102       },
    103       blanks: [
    104         { key: 'name',   label: 'Tell me your name!',              voice: 'Tell me your name!' },
    105         { key: 'place',  label: 'Tell me a place!',                voice: 'Tell me a place!' },
    106         { key: 'number', label: 'Tell me a number!',               voice: 'Tell me a number!' },
    107         { key: 'food',   label: 'Tell me a yummy food!',           voice: 'Tell me a yummy food!' },
    108       ],
    109       story: 'A hare was bragging how fast he could run, so a slow tortoise named {name} said, ' +
    110              '"I will race you all the way to {place}!" The hare laughed and zoomed ahead. ' +
    111              'He was so far in front that he stopped for a nap and {number} bites of {food}. ' +
    112              'Plod, plod, plod went {name}, never stopping once. ' +
    113              'When the hare woke up and ran his fastest, {name} was already there. Slow and steady wins the race!',
    114     },
    115     {
    116       id: 'town-country-mouse',
    117       title: 'The Town Mouse and the Country Mouse',
    118       source: {
    119         title: 'The Town Mouse and the Country Mouse (The Fables of Æsop)',
    120         author: 'Æsop, told anew by Joseph Jacobs',
    121         url: 'https://www.gutenberg.org/ebooks/28',
    122       },
    123       blanks: [
    124         { key: 'name',   label: 'Tell me a friend’s name!',        voice: 'Tell me a friend’s name!' },
    125         { key: 'food',   label: 'Tell me a yummy food!',           voice: 'Tell me a yummy food!' },
    126         { key: 'animal', label: 'Tell me an animal!',              voice: 'Tell me an animal!' },
    127         { key: 'silly',  label: 'Tell me a silly word!',           voice: 'Tell me a silly word!' },
    128       ],
    129       story: 'A town mouse named {name} came to visit a country mouse, who gave him beans and crusts for dinner. ' +
    130              '"Come to my house in town," said {name}, "and you shall have {food} instead!" ' +
    131              'So off they went, and the table in town was piled high. ' +
    132              'But just as they began to nibble, a big {animal} came in and they had to squeak "{silly}!" and hide in a hole. ' +
    133              'The country mouse said, "I would rather have beans in peace than {food} in fright," and went home.',
    134     },
    135     {
    136       id: 'ugly-duckling',
    137       title: 'The Ugly Duckling',
    138       source: {
    139         title: 'The Ugly Duckling (Andersen’s Fairy Tales)',
    140         author: 'Hans Christian Andersen',
    141         url: 'https://www.gutenberg.org/ebooks/1597',
    142       },
    143       blanks: [
    144         { key: 'name',   label: 'Tell me your name!',              voice: 'Tell me your name!' },
    145         { key: 'color',  label: 'Tell me a color!',                voice: 'Tell me a color!' },
    146         { key: 'food',   label: 'Tell me a yummy food!',           voice: 'Tell me a yummy food!' },
    147         { key: 'silly',  label: 'Tell me a silly word!',           voice: 'Tell me a silly word!' },
    148       ],
    149       story: 'Down by the water, a mother duck’s eggs went crack, crack, crack. ' +
    150              'Out came the ducklings — and the very last one was big and {color}, so everyone called him {name}. ' +
    151              '"You look funny!" said the other birds, and poor {name} waddled away all by himself. ' +
    152              'He was cold all winter, with only a little {food} to eat. ' +
    153              'But in the spring he looked into the water and said "{silly}!" — ' +
    154              'for he was not a duckling at all. He had grown into a beautiful swan.',
    155     },
    156     {
    157       id: 'emperors-clothes',
    158       title: 'The Emperor’s New Clothes',
    159       source: {
    160         title: 'The Emperor’s New Clothes (Andersen’s Fairy Tales)',
    161         author: 'Hans Christian Andersen',
    162         url: 'https://www.gutenberg.org/ebooks/1597',
    163       },
    164       blanks: [
    165         { key: 'name',   label: 'Tell me a friend’s name!',        voice: 'Tell me a friend’s name!' },
    166         { key: 'color',  label: 'Tell me a color!',                voice: 'Tell me a color!' },
    167         { key: 'animal', label: 'Tell me an animal!',              voice: 'Tell me an animal!' },
    168         { key: 'silly',  label: 'Tell me a silly word!',           voice: 'Tell me a silly word!' },
    169       ],
    170       story: 'There was an emperor who loved new clothes more than anything. ' +
    171              'Two tricky tailors said they could sew him a {color} suit so magic that only clever people could see it. ' +
    172              'They sewed with nothing at all! But nobody wanted to look silly, so everyone said, "How fine!" ' +
    173              'The emperor marched down the street in his underwear, with a {animal} following behind. ' +
    174              'Then a little kid named {name} shouted, "{silly}! He has no clothes on!" ' +
    175              'And everybody laughed, because it was true.',
    176     },
    177     {
    178       id: 'elves-shoemaker',
    179       title: 'The Elves and the Shoemaker',
    180       source: {
    181         title: 'The Elves and the Shoemaker (Grimms’ Fairy Tales)',
    182         author: 'Jacob and Wilhelm Grimm',
    183         url: 'https://www.gutenberg.org/ebooks/2591',
    184       },
    185       blanks: [
    186         { key: 'name',   label: 'Tell me your name!',              voice: 'Tell me your name!' },
    187         { key: 'color',  label: 'Tell me a color!',                voice: 'Tell me a color!' },
    188         { key: 'number', label: 'Tell me a number!',               voice: 'Tell me a number!' },
    189         { key: 'action', label: 'Tell me something you can do!',   voice: 'Tell me something you can do!' },
    190       ],
    191       story: 'A shoemaker named {name} had only enough leather left for one pair of shoes, ' +
    192              'so {name} cut it out and went to bed. ' +
    193              'In the morning — surprise! — the shoes were already finished, and they were {color} and perfect. ' +
    194              'This happened again and again, until {name} peeked and saw {number} tiny elves ' +
    195              'hop onto the table and {action} all night long. ' +
    196              'So {name} sewed them tiny coats to say thank you, and the elves danced right out the door.',
    197     },
    198     {
    199       id: 'bremen-musicians',
    200       title: 'The Bremen Town Musicians',
    201       source: {
    202         title: 'The Bremen Town Musicians (Grimms’ Fairy Tales)',
    203         author: 'Jacob and Wilhelm Grimm',
    204         url: 'https://www.gutenberg.org/ebooks/2591',
    205       },
    206       blanks: [
    207         { key: 'name',   label: 'Tell me your name!',              voice: 'Tell me your name!' },
    208         { key: 'animal', label: 'Tell me an animal!',              voice: 'Tell me an animal!' },
    209         { key: 'place',  label: 'Tell me a place!',                voice: 'Tell me a place!' },
    210         { key: 'silly',  label: 'Tell me a silly word!',           voice: 'Tell me a silly word!' },
    211       ],
    212       story: 'An old donkey named {name} set off to be a musician in {place}. ' +
    213              'Along the way a dog, a cat, a rooster, and even a {animal} came too. ' +
    214              'When night fell they found a little house full of robbers, so they stood one on top of the other ' +
    215              'and sang their song: hee-haw, woof, meow, cock-a-doodle-doo, "{silly}!" ' +
    216              'The robbers ran away as fast as they could, and the four friends slept there happily ever after.',
    217     },
    218     {
    219       id: 'frog-prince',
    220       title: 'The Frog Prince',
    221       source: {
    222         title: 'The Frog Prince (Grimms’ Fairy Tales)',
    223         author: 'Jacob and Wilhelm Grimm',
    224         url: 'https://www.gutenberg.org/ebooks/2591',
    225       },
    226       blanks: [
    227         { key: 'name',   label: 'Tell me a friend’s name!',        voice: 'Tell me a friend’s name!' },
    228         { key: 'color',  label: 'Tell me a color!',                voice: 'Tell me a color!' },
    229         { key: 'food',   label: 'Tell me a yummy food!',           voice: 'Tell me a yummy food!' },
    230         { key: 'silly',  label: 'Tell me a silly word!',           voice: 'Tell me a silly word!' },
    231       ],
    232       story: 'A princess named {name} was playing with a {color} golden ball when — plop! — it fell into the well. ' +
    233              '"I will fetch it," said a frog, "if you let me eat {food} from your little plate." ' +
    234              '{name} said yes, and the frog dived down and brought the ball back up. ' +
    235              'That night the frog knocked at the palace door: tap, tap, "{silly}!" ' +
    236              'A promise is a promise, so {name} let him in — and in the morning the frog was a prince.',
    237     },
    238     {
    239       id: 'jack-beanstalk',
    240       title: 'Jack and the Beanstalk',
    241       source: {
    242         title: 'Jack and the Beanstalk (English Fairy Tales)',
    243         author: 'Joseph Jacobs',
    244         url: 'https://www.gutenberg.org/ebooks/7439',
    245       },
    246       blanks: [
    247         { key: 'name',   label: 'Tell me your name!',              voice: 'Tell me your name!' },
    248         { key: 'number', label: 'Tell me a number!',               voice: 'Tell me a number!' },
    249         { key: 'animal', label: 'Tell me an animal!',              voice: 'Tell me an animal!' },
    250         { key: 'food',   label: 'Tell me a yummy food!',           voice: 'Tell me a yummy food!' },
    251       ],
    252       story: 'A boy named {name} traded the family cow for {number} magic beans. ' +
    253              'His mother tossed them out the window — and by morning a beanstalk had grown up past the clouds! ' +
    254              '{name} climbed and climbed, all the way to a giant’s castle in the sky. ' +
    255              'The giant roared, "Fee-fi-fo-fum!" but he was busy eating {food}, ' +
    256              'so {name} grabbed the hen that laid golden eggs and a {animal} that could sing. ' +
    257              'Down the beanstalk he scrambled, chop-chop, and he and his mother were never hungry again.',
    258     },
    259     {
    260       id: 'red-riding-hood',
    261       title: 'Little Red Riding Hood',
    262       source: {
    263         title: 'Little Red Riding Hood (English Fairy Tales)',
    264         author: 'Flora Annie Steel',
    265         url: 'https://www.gutenberg.org/ebooks/17034',
    266       },
    267       blanks: [
    268         { key: 'name',   label: 'Tell me a friend’s name!',        voice: 'Tell me a friend’s name!' },
    269         { key: 'color',  label: 'Tell me a color!',                voice: 'Tell me a color!' },
    270         { key: 'food',   label: 'Tell me a yummy food!',           voice: 'Tell me a yummy food!' },
    271         { key: 'silly',  label: 'Tell me a silly word!',           voice: 'Tell me a silly word!' },
    272       ],
    273       story: 'A little girl named {name} had a {color} hooded cape, so everyone called her Little {color} Riding Hood. ' +
    274              'One morning she carried a basket of {food} through the wood to her grandmother’s house. ' +
    275              'A sly wolf hurried on ahead and hopped into Grandmother’s bed. ' +
    276              '"What big eyes you have!" said {name}. "What big ears you have!" ' +
    277              'Then the woodcutter came in shouting "{silly}!" and the wolf ran off into the trees. ' +
    278              'Grandmother came out of the closet, and they all shared the {food}.',
    279     },
    280     {
    281       id: 'elephants-child',
    282       title: 'The Elephant’s Child',
    283       source: {
    284         title: 'The Elephant’s Child (Just So Stories)',
    285         author: 'Rudyard Kipling',
    286         url: 'https://www.gutenberg.org/ebooks/2781',
    287       },
    288       blanks: [
    289         { key: 'name',   label: 'Tell me your name!',              voice: 'Tell me your name!' },
    290         { key: 'food',   label: 'Tell me a yummy food!',           voice: 'Tell me a yummy food!' },
    291         { key: 'place',  label: 'Tell me a place!',                voice: 'Tell me a place!' },
    292         { key: 'silly',  label: 'Tell me a silly word!',           voice: 'Tell me a silly word!' },
    293       ],
    294       story: 'In the long ago, elephants had no trunks at all — only a little squashy nose. ' +
    295              'But one baby elephant named {name} was full of questions, like ' +
    296              '"What does the crocodile have for dinner?" ' +
    297              'So {name} went all the way to {place} to find out, and the crocodile grabbed that little nose and PULLED. ' +
    298              '{name} pulled back, and pulled, and hollered "{silly}!" — ' +
    299              'and when the crocodile let go, the nose had stretched into a long, useful trunk. ' +
    300              'Now {name} could pick {food} right off the tallest tree.',
    301     },
    302     {
    303       id: 'camel-hump',
    304       title: 'How the Camel Got His Hump',
    305       source: {
    306         title: 'How the Camel Got His Hump (Just So Stories)',
    307         author: 'Rudyard Kipling',
    308         url: 'https://www.gutenberg.org/ebooks/2781',
    309       },
    310       blanks: [
    311         { key: 'name',   label: 'Tell me your name!',              voice: 'Tell me your name!' },
    312         { key: 'action', label: 'Tell me something you can do!',   voice: 'Tell me something you can do!' },
    313         { key: 'number', label: 'Tell me a number!',               voice: 'Tell me a number!' },
    314         { key: 'silly',  label: 'Tell me a silly word!',           voice: 'Tell me a silly word!' },
    315       ],
    316       story: 'At the beginning of the world, a lazy camel named {name} lived in the middle of the desert. ' +
    317              'The horse said, "Come and {action} with us!" The dog said it too, and the ox said it too. ' +
    318              'But {name} only answered, "{silly}!" and would not work at all. ' +
    319              'So the Djinn of the desert came along and gave {name} a great big humph — I mean, hump — ' +
    320              'right on his back. Now {name} can work {number} whole days without stopping for dinner, ' +
    321              'and camels have had humps ever since.',
    322     },
    323     {
    324       id: 'wizard-of-oz',
    325       title: 'The Wonderful Wizard of Oz',
    326       source: {
    327         title: 'The Wonderful Wizard of Oz',
    328         author: 'L. Frank Baum',
    329         url: 'https://www.gutenberg.org/ebooks/55',
    330       },
    331       blanks: [
    332         { key: 'name',   label: 'Tell me your name!',              voice: 'Tell me your name!' },
    333         { key: 'color',  label: 'Tell me a color!',                voice: 'Tell me a color!' },
    334         { key: 'animal', label: 'Tell me an animal!',              voice: 'Tell me an animal!' },
    335         { key: 'action', label: 'Tell me something you can do!',   voice: 'Tell me something you can do!' },
    336       ],
    337       story: 'A cyclone picked up a little house with {name} and a small dog inside, and set it down in the land of Oz. ' +
    338              '"We are not in Kansas any more!" said {name}. ' +
    339              'To get home, {name} followed the road of yellow brick, ' +
    340              'and along the way met a scarecrow, a tin woodman, a lion, and a friendly {animal}. ' +
    341              'In the {color} City the great wizard turned out to be an ordinary man behind a curtain, ' +
    342              'but he could still {action}, and the silver shoes carried {name} all the way home.',
    343     },
    344     {
    345       id: 'alice-rabbit-hole',
    346       title: 'Alice Down the Rabbit Hole',
    347       source: {
    348         title: 'Alice’s Adventures in Wonderland',
    349         author: 'Lewis Carroll',
    350         url: 'https://www.gutenberg.org/ebooks/11',
    351       },
    352       blanks: [
    353         { key: 'name',   label: 'Tell me a friend’s name!',        voice: 'Tell me a friend’s name!' },
    354         { key: 'food',   label: 'Tell me a yummy food!',           voice: 'Tell me a yummy food!' },
    355         { key: 'animal', label: 'Tell me an animal!',              voice: 'Tell me an animal!' },
    356         { key: 'silly',  label: 'Tell me a silly word!',           voice: 'Tell me a silly word!' },
    357       ],
    358       story: 'A girl named {name} saw a white rabbit run past, saying, "Oh dear! I shall be late!" ' +
    359              'Down the rabbit hole she went, tumbling and tumbling. ' +
    360              'At the bottom she found a little bottle marked DRINK ME and grew very small, ' +
    361              'then a bit of {food} marked EAT ME and grew very TALL. ' +
    362              'A grinning {animal} showed her the way to a tea party where everyone shouted "{silly}!" ' +
    363              'and passed the cups around. What a curious dream {name} had that day.',
    364     },
    365     {
    366       id: 'velveteen-rabbit',
    367       title: 'The Velveteen Rabbit',
    368       source: {
    369         title: 'The Velveteen Rabbit',
    370         author: 'Margery Williams Bianco',
    371         url: 'https://www.gutenberg.org/ebooks/11757',
    372       },
    373       blanks: [
    374         { key: 'name',   label: 'Tell me your name!',              voice: 'Tell me your name!' },
    375         { key: 'color',  label: 'Tell me a color!',                voice: 'Tell me a color!' },
    376         { key: 'place',  label: 'Tell me a place!',                voice: 'Tell me a place!' },
    377         { key: 'action', label: 'Tell me something you can do!',   voice: 'Tell me something you can do!' },
    378       ],
    379       story: 'On Christmas morning a boy named {name} found a {color} velveteen rabbit in his stocking. ' +
    380              'The old rocking horse said, "When a child loves you for a long, long time, you become Real." ' +
    381              '{name} took the rabbit everywhere — even to {place} — and hugged him until his fur was rubbed away. ' +
    382              '"Am I Real yet?" asked the rabbit. ' +
    383              'Then one spring day a fairy kissed him, and the rabbit found he could hop and {action} ' +
    384              'with all the wild rabbits. He was Real at last.',
    385     },
    386     {
    387       id: 'tom-kitten',
    388       title: 'The Tale of Tom Kitten',
    389       source: {
    390         title: 'The Tale of Tom Kitten',
    391         author: 'Beatrix Potter',
    392         url: 'https://www.gutenberg.org/ebooks/14837',
    393       },
    394       blanks: [
    395         { key: 'name',   label: 'Tell me a friend’s name!',        voice: 'Tell me a friend’s name!' },
    396         { key: 'color',  label: 'Tell me a color!',                voice: 'Tell me a color!' },
    397         { key: 'food',   label: 'Tell me a yummy food!',           voice: 'Tell me a yummy food!' },
    398         { key: 'animal', label: 'Tell me an animal!',              voice: 'Tell me an animal!' },
    399       ],
    400       story: 'Mrs. Tabitha Twitchit had three kittens, and the naughtiest was named {name}. ' +
    401              'Company was coming for {food}, so she scrubbed their faces and dressed them in {color} clothes. ' +
    402              '"Now keep clean!" she said, and put them out in the garden. ' +
    403              'Of course {name} climbed the wall straightaway, and all the buttons popped off — pop, pop, pop. ' +
    404              'A passing {animal} tried the clothes on and found them a very good fit. ' +
    405              'And that is why the kittens had to stay upstairs when the company came.',
    406     },
    407     {
    408       id: 'mother-goose',
    409       title: 'Hey Diddle Diddle',
    410       source: {
    411         title: 'Hey Diddle Diddle (The Real Mother Goose)',
    412         author: 'compiled by Blanche Fisher Wright',
    413         url: 'https://www.gutenberg.org/ebooks/10607',
    414       },
    415       blanks: [
    416         { key: 'animal', label: 'Tell me an animal!',              voice: 'Tell me an animal!' },
    417         { key: 'place',  label: 'Tell me a place!',                voice: 'Tell me a place!' },
    418         { key: 'name',   label: 'Tell me your name!',              voice: 'Tell me your name!' },
    419         { key: 'silly',  label: 'Tell me a silly word!',           voice: 'Tell me a silly word!' },
    420       ],
    421       story: 'Hey diddle diddle, the {animal} and the fiddle, ' +
    422              'the cow jumped over {place}! ' +
    423              'The little dog laughed to see such fun, and shouted "{silly}!" ' +
    424              'And the dish ran away with the spoon — and with {name}, too.',
    425     },
    426   ];
    427 
    428   // ── Pure helpers ─────────────────────────────────────────────────────────────
    429 
    430   function fillTemplate(template, values) {
    431     return template.story.replace(/\{(\w+)\}/g, function (_, key) {
    432       const v = values[key];
    433       return (v === undefined || v === null || v === '') ? '___' : String(v);
    434     });
    435   }
    436 
    437   function pickTemplate(rng) {
    438     const r = (typeof rng === 'function') ? rng : Math.random;
    439     return MADLIB_TEMPLATES[Math.floor(r() * MADLIB_TEMPLATES.length)];
    440   }
    441 
    442   // Frame-by-frame strings for the live "typing" effect: backspace `from`
    443   // down to the common prefix of `from`/`to`, then type up to `to`.
    444   function typewriterSteps(from, to) {
    445     from = from || '';
    446     to   = to   || '';
    447     let common = 0;
    448     const max = Math.min(from.length, to.length);
    449     while (common < max && from[common] === to[common]) common++;
    450 
    451     const steps = [];
    452     for (let i = from.length - 1; i >= common; i--) steps.push(from.slice(0, i));
    453     for (let i = common + 1; i <= to.length; i++) steps.push(to.slice(0, i));
    454     if (steps.length === 0 || steps[steps.length - 1] !== to) steps.push(to);
    455     return steps;
    456   }
    457 
    458   function normalizeName(str) {
    459     return String(str == null ? '' : str).trim().replace(/\s+/g, ' ').toLowerCase();
    460   }
    461 
    462   function isNameReady(str) {
    463     return normalizeName(str).length >= 2;
    464   }
    465 
    466   function formatTime(ms) {
    467     return (Math.max(0, ms) / 1000).toFixed(2) + ' s';
    468   }
    469 
    470   function isNewRecord(ms, best) {
    471     return best == null || ms < best;
    472   }
    473 
    474   const api = {
    475     MADLIB_TEMPLATES,
    476     fillTemplate,
    477     pickTemplate,
    478     typewriterSteps,
    479     normalizeName,
    480     isNameReady,
    481     formatTime,
    482     isNewRecord,
    483   };
    484   if (typeof module !== 'undefined' && module.exports) module.exports = api;
    485   root.WorkLib = api;
    486 }(typeof globalThis !== 'undefined' ? globalThis : this));