{
  "slug": "downton-dog",
  "version": 2,
  "format": "html",
  "html": "<!DOCTYPE html>\n<html lang=\"en\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1,viewport-fit=cover\"><title>Downton Dog</title>\n<style>\n:root{--ink:#2b2233;--paper:#f6ead2;--paper2:#eddcb9;--gold:#f2c75c;--gilt:#c89b3c;--ox:#8a2f3b;--moss:#6f8a55;--slate:#6a5a72}\n*{box-sizing:border-box}html,body{margin:0}\nbody{min-height:100vh;background:#cdb6c6;background-image:radial-gradient(circle at 8% 0,#f1d9c0 0 22%,transparent 23%),radial-gradient(circle at 96% 100%,#b6c7a4 0 26%,transparent 27%);color:var(--ink);font-family:Georgia,\"Iowan Old Style\",\"Palatino Linotype\",Palatino,serif;display:flex;justify-content:center;padding:14px 12px 48px}\n.wrap{width:100%;max-width:460px}\nh1{font-family:\"Baloo 2\",\"Fredoka\",\"Nunito\",ui-rounded,\"Arial Rounded MT Bold\",system-ui,sans-serif;font-weight:900;font-size:clamp(36px,10.5vw,54px);line-height:.95;margin:4px 0 0;color:var(--gold);text-align:center;transform:rotate(-1.5deg);text-shadow:3px 3px 0 var(--ink),-2px -2px 0 var(--ink),2px -2px 0 var(--ink),-2px 2px 0 var(--ink),0 5px 0 var(--ink)}\n.sub{text-align:center;font-style:italic;font-size:15px;margin:8px 0 14px;opacity:.82}\n.stage{position:relative;border:4px solid var(--ink);border-radius:26px;box-shadow:0 7px 0 var(--ink);overflow:hidden;background:#9cc7e6}\n.stage svg{display:block;width:100%;height:auto;user-select:none;-webkit-user-select:none}\n.stage.herd svg{touch-action:none}\n.hud{position:absolute;left:10px;top:10px;display:flex;gap:6px;flex-wrap:wrap;pointer-events:none}\n.tag{background:var(--paper);border:3px solid var(--ink);border-radius:999px;padding:3px 10px;font-size:12px;font-weight:800;box-shadow:0 3px 0 var(--ink);font-family:\"Baloo 2\",\"Fredoka\",ui-rounded,system-ui,sans-serif}\n.tag.ox{background:var(--ox);color:var(--paper)}\n.panel{margin-top:18px;background:var(--paper);border:4px solid var(--ink);border-radius:20px;box-shadow:0 6px 0 var(--ink);padding:16px 16px 14px}\n.panel h2{font-family:\"Baloo 2\",\"Fredoka\",ui-rounded,system-ui,sans-serif;font-size:21px;margin:0 0 8px;line-height:1.15}\n.panel h2 small{display:block;font-size:12px;opacity:.6;font-weight:800;margin-bottom:2px}\n.panel p{margin:0 0 10px;line-height:1.55;font-size:15.5px}\n.who{font-style:italic;color:var(--ox)}\n.row{display:flex;gap:8px;flex-wrap:wrap;margin-top:10px}\nbutton{font:inherit;font-family:\"Baloo 2\",\"Fredoka\",ui-rounded,system-ui,sans-serif;font-weight:800;font-size:15px;padding:9px 14px;border:3px solid var(--ink);border-radius:999px;background:var(--paper2);color:var(--ink);box-shadow:0 4px 0 var(--ink);cursor:pointer;transition:transform .07s,box-shadow .07s}\nbutton:active:not(:disabled){transform:translateY(4px);box-shadow:0 0 0 var(--ink)}\nbutton:disabled{opacity:.5;cursor:default}\nbutton.pri{background:var(--ox);color:var(--paper)}\nbutton.gold{background:var(--gold)}\nbutton:focus-visible{outline:3px dashed var(--ox);outline-offset:3px}\ninput{font:inherit;padding:8px 12px;border:3px solid var(--ink);border-radius:999px;background:#fff;color:var(--ink);min-width:0}\n.shout{display:flex;gap:8px;margin-top:12px}.shout input{flex:1}\n.bars{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin:4px 0 10px}\n.bar{font-size:12px;font-weight:800;font-family:\"Baloo 2\",\"Fredoka\",ui-rounded,system-ui,sans-serif}\n.bar i{display:block;height:12px;border:3px solid var(--ink);border-radius:999px;background:#fff;margin-top:3px;overflow:hidden}\n.bar i b{display:block;height:100%;background:var(--moss);transition:width .3s}\n.bar.ox i b{background:var(--ox)}\n.log{font-size:14px;line-height:1.45;max-height:150px;overflow:auto;border-top:2px dashed var(--ink);padding-top:8px;margin-top:8px}\n.log div{margin-bottom:4px}\n.feed{margin-top:14px;font-size:13.5px;line-height:1.55;opacity:.88}\n.feed h3{font-family:\"Baloo 2\",\"Fredoka\",ui-rounded,system-ui,sans-serif;font-size:14px;margin:0 0 4px}\n.feed b{font-family:\"Baloo 2\",\"Fredoka\",ui-rounded,system-ui,sans-serif;font-weight:800}\n.feed .sh{color:var(--ox)}\n.name{margin-top:14px;text-align:center}.name input{width:72%;text-align:center}\n.rules{margin-top:12px;font-size:12.5px;line-height:1.45;opacity:.62;text-align:center}\n.stats{display:grid;grid-template-columns:1fr 1fr;gap:4px 12px;font-size:14px;margin:6px 0 10px}\n.stats span{opacity:.65}\n.lineage div{padding:5px 0;border-bottom:1px dashed var(--ink);font-size:14px}\n.toast{position:absolute;left:50%;bottom:10px;transform:translateX(-50%);background:var(--ink);color:var(--paper);padding:6px 12px;border-radius:999px;font-size:13px;opacity:0;transition:opacity .25s;pointer-events:none;white-space:nowrap;max-width:94%;overflow:hidden;text-overflow:ellipsis}\n.toast.on{opacity:1}\n[data-spot]{cursor:pointer}\n.wag{transform-box:fill-box;transform-origin:100% 100%;animation:wag .5s ease-in-out infinite alternate}\n@keyframes wag{from{transform:rotate(-14deg)}to{transform:rotate(18deg)}}\n.breathe{animation:breathe 2.8s ease-in-out infinite}\n@keyframes breathe{50%{transform:translateY(-2px)}}\n.bob{animation:bob 3s ease-in-out infinite}\n@keyframes bob{50%{transform:translateY(-10px)}}\n.pop{transform-box:fill-box;transform-origin:50% 100%;animation:pop .4s cubic-bezier(.2,1.7,.4,1) both}\n@keyframes pop{from{transform:scale(0)}to{transform:scale(1)}}\n.hit{animation:hit .4s}\n@keyframes hit{30%{transform:translateX(10px) rotate(5deg)}60%{transform:translateX(-5px)}}\n.tw{animation:tw 1.8s ease-in-out infinite alternate}@keyframes tw{from{opacity:.15}to{opacity:1}}\n.q{animation:qb 1.3s ease-in-out infinite}@keyframes qb{50%{transform:translateY(-5px)}}\n.jet{animation:jet 1.1s ease-in-out infinite alternate}@keyframes jet{to{transform:scaleY(1.15)}}\n.smoke{animation:smoke 4s linear infinite}@keyframes smoke{to{transform:translate(8px,-26px);opacity:0}}\n.zz{animation:zz 2.4s ease-out infinite}@keyframes zz{0%{opacity:0;transform:translate(0,0)}30%{opacity:1}100%{opacity:0;transform:translate(14px,-30px)}}\n.stage.fight svg{touch-action:none}\n.stage.shake{animation:shk .28s}\n@keyframes shk{20%{transform:translate(-5px,2px)}50%{transform:translate(5px,-2px)}80%{transform:translate(-3px,1px)}}\n.fl{animation:fl .9s ease-out forwards}@keyframes fl{to{transform:translateY(-36px);opacity:0}}\n.tip{font-size:13px;opacity:.7;margin-top:10px!important}\n.cool{position:relative;overflow:hidden}\n.cool i{position:absolute;left:0;top:0;bottom:0;background:rgba(43,34,51,.18);pointer-events:none}\n@media (prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important}}\n</style></head>\n<body><div class=\"wrap\">\n<h1>Downton Dog</h1>\n<div class=\"sub\">of Hawthorne House, raised by everyone who passes</div>\n<div class=\"stage\" id=\"stage\"><div class=\"hud\" id=\"hud\"></div><div id=\"art\"></div><div class=\"toast\" id=\"toast\"></div></div>\n<div class=\"panel\" id=\"panel\"><p>Opening the gates&hellip;</p></div>\n<div class=\"feed\" id=\"feed\"></div>\n<div class=\"name\"><input id=\"who\" maxlength=\"24\" placeholder=\"your name in the household (optional)\" aria-label=\"your name\"></div>\n<div class=\"rules\">One dog, shared by everyone. A year of his life passes each real day. He goes missing at 00:00 UTC.</div>\n</div>\n<script>\n/* ---------- storage ---------- */\nconst KEY='dog',LKEY='lineage',DAY=864e5;\nconst mem={};\nconst store={\n async get(k){if(window.charm){try{const r=await window.charm.get(k);return(r&&typeof r==='object'&&'value' in r&&!('status' in r))?r.value:r;}catch(e){}}return mem[k]??null;},\n async set(k,v){if(window.charm){try{await window.charm.set(k,v);return;}catch(e){}}mem[k]=v;}\n};\nconst $=id=>document.getElementById(id);\nconst sleep=ms=>new Promise(r=>setTimeout(r,ms));\nconst today=()=>Math.floor(Date.now()/DAY);\nconst rnd=(a,b)=>a+Math.floor(Math.random()*(b-a+1));\nconst clamp=(v,a,b)=>Math.max(a,Math.min(b,v));\nconst esc=s=>String(s).replace(/[&<>\"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;',\"'\":'&#39;'}[c]));\nconst roman=n=>['','','II','III','IV','V','VI','VII','VIII','IX','X','XI','XII'][n]||('#'+n);\nconst who=()=>($('who').value.trim()||'someone in the household').slice(0,24);\n\n/* ---------- world data ---------- */\nconst SPOTS=[\n {id:'gatehouse',n:'the gatehouse',x:22,y:200,miss:'Only the gatekeeper\\u2019s cat, who declines to comment.'},\n {id:'haha',n:'the ha-ha',x:150,y:224,miss:'A startled pheasant. Not a dog.'},\n {id:'fountain',n:'the fountain',x:75,y:262,miss:'A wet footman fishing out a croquet ball.'},\n {id:'arbour',n:'the rose arbour',x:270,y:262,miss:'Rose petals and a very small spider.'},\n {id:'stable',n:'the stable',x:328,y:192,miss:'The grey mare regards you with enormous pity.'},\n {id:'hedge',n:'the yew hedge',x:57,y:330,miss:'Clipping shears, and half a pork pie.'},\n {id:'pond',n:'the pond',x:300,y:392,miss:'Ducks. Alarmed, judgmental ducks.'}\n];\nconst hideIdx=(day,gen)=>(day*31+gen*17)%SPOTS.length;\nconst SHEEP_NAMES=['Morag','Hamish','Fiona','Dougal','Isla','Angus','Eilidh','Rab'];\n\nconst ENEMIES={\n prebble:{n:'Mr Prebble, the Underbutler',hp:16,atk:3,xp:24,art:'human',coat:'#2b2233',shirt:'#fff',hat:'none',prop:'duster',moves:['flicks the feather duster','sniffs with great disapproval','attempts a stern shoo'],relent:'pretends to have been rung for and withdraws.'},\n fluff:{n:'Duchess Fluff, the Persian',hp:20,atk:4,xp:30,art:'cat',moves:['swats without looking','yawns, which somehow hurts','sheds pointedly'],relent:'sighs, and allows you past as if it were her idea.'},\n ashcombe:{n:'Mr Ashcombe, the Head Butler',hp:28,atk:5,xp:44,art:'human',coat:'#171320',shirt:'#fff',hat:'none',prop:'tray',buttons:1,moves:['raps your nose with a salver','delivers a lecture on standards','drops a decanter in your direction'],relent:'is suddenly very interested in the silver.'},\n nigel:{n:'Nigel, the Boot Boy\\u2019s Ferret',hp:18,atk:6,xp:34,art:'ferret',moves:['goes up your trouser','bites an ear, joyfully','ricochets off the dresser'],relent:'is distracted by a sock and departs.'},\n ned:{n:'Ned, a Tipsy Farmhand',hp:30,atk:6,xp:46,art:'human',coat:'#7a5a3c',shirt:'#e8d9b8',hat:'cap',prop:'tankard',drunk:1,moves:['swings a stool and misses the room','explains that you are a wolf','sits down heavily on your tail'],relent:'begins to cry and buys you a pie.'},\n quill:{n:'Mr Quill, the Drunk Postmaster',hp:26,atk:5,xp:42,art:'human',coat:'#2f3f6b',shirt:'#c43c3c',hat:'cap',prop:'bottle',drunk:1,moves:['hits a high note','throws the second-class post','insists on the chorus again'],relent:'finds he has forgotten the verse, and the quarrel.'},\n bartholomew:{n:'Lord Bartholomew, a Stuck-Up Pony',hp:40,atk:7,xp:64,art:'pony',moves:['kicks with great breeding','bites, but politely','says something cutting about your collar'],relent:'cannot be seen quarrelling with a dog and trots off.'},\n hamish:{n:'Hamish, the Laird\\u2019s Ram',hp:46,atk:8,xp:74,art:'ram',moves:['charges like a dropped piano','stamps the heather flat','lowers his horns and his opinion of you'],relent:'remembers a ewe he meant to see about.'},\n goose:{n:'A Highland Goose',hp:30,atk:9,xp:58,art:'goose',moves:['honks directly into your soul','beats you with both wings','pursues you down the pier'],relent:'is lured away by a dropped scone.'},\n grimsby:{n:'Mr Grimsby, the Solicitor',hp:50,atk:8,xp:84,art:'human',coat:'#3b3444',shirt:'#fff',hat:'bowler',prop:'case',buttons:1,moves:['serves you with papers','cites a precedent','bills you for the encounter'],relent:'finds a conflict of interest and recuses himself.'},\n bismarck:{n:'Bismarck, Aldous\\u2019s Dachshund',hp:44,atk:7,xp:78,art:'dach',moves:['barks in a continental accent','guards the deed with his whole length','nips, with ancestral grievance'],relent:'concedes that you are also a dog, and relaxes.'},\n aldous:{n:'Cousin Aldous Pennywhistle',hp:70,atk:10,xp:160,art:'human',coat:'#b98a2e',shirt:'#fff',hat:'top',prop:'cane',buttons:1,moves:['thrashes the air with a very thin cane','shrieks for Grimsby','steps on your paw, deliberately'],relent:'bursts into tears about his mother and is led away.'}\n};\n\nconst STORY=[\n {title:'A Letter from London',lvl:1,beats:[\n  {t:'Hawthorne House, autumn 1912. Mr Hawthorne has read a letter three times, and his hand has stopped scratching your ears.'},\n  {who:'Mr Hawthorne',t:'\\u201cCousin Aldous means to have the house. Says the deed is forfeit. I should like to see him get past you, old boy.\\u201d'},\n  {t:'Mr Prebble is keeping dogs out of the study while the papers are \\u2018reorganised\\u2019. He has a feather duster and a grudge.',fight:'prebble'},\n  {t:'Prebble retreats in his own dust. On the desk: no deed. Only one long white hair, extremely smug.'},\n  {t:'Duchess Fluff, the Persian, lies across the servants\\u2019 stair like a dropped fur stole. She knows something.',fight:'fluff'},\n  {t:'She flicks her tail toward the kitchens. Below stairs, then.'}\n ]},\n {title:'Below Stairs',lvl:2,beats:[\n  {t:'Mrs Tandy the cook regards you over a tray of ginger biscuits.',req:{stat:'mood',min:35,ok:'She slips you three biscuits \\u201cfor the campaign\\u201d.',fail:'She notes your low spirits and gives you one biscuit, sternly.',okFx:{biscuits:3},failFx:{biscuits:1}}},\n  {t:'Behind the pantry door, Mr Ashcombe the head butler is counting coins in a whisper. They are not his.',fight:'ashcombe'},\n  {who:'Mr Ashcombe',t:'\\u201cThe deed went north with the horse dealer, in Tuesday\\u2019s post. I was paid in sovereigns. One does what one must, dog.\\u201d'},\n  {t:'Nigel, the boot boy\\u2019s ferret, erupts from a riding boot. He has never been told no.',fight:'nigel'},\n  {t:'The trail leads out of the scullery, down the lane, and into the village.'}\n ]},\n {title:'The Dog & Partridge',lvl:3,beats:[\n  {t:'Hawthorne-under-Edge has one pub, one post office, and one opinion about dogs in the taproom. It is a drinking day. They all are.'},\n  {t:'Ned the farmhand has had four pints and a theory that you are a wolf. He is holding a stool.',fight:'ned'},\n  {t:'Mr Quill the postmaster is singing about a horse dealer named Pettigrew, gone north with a biting pony. He hates being interrupted.',fight:'quill'},\n  {t:'Behind the pub: Lord Bartholomew, a Shetland pony in a top hat, left behind as \\u201ctoo much trouble\\u201d.',fight:'bartholomew'},\n  {who:'Lord Bartholomew',t:'\\u201cPettigrew has gone to Laird MacCrimmon\\u2019s. The deed buys him a flock. I hope you catch a chill.\\u201d'}\n ]},\n {title:'The Highland Wager',lvl:4,beats:[\n  {t:'The Highlands are very far for a dog who has never passed the gatehouse. You arrive in the back of a fish cart.'},\n  {who:'Laird MacCrimmon',t:'\\u201cPettigrew sold me your deed for forty ewes. I\\u2019ll give it back to a dog that can gather my hill.\\u201d',gate:{stat:'sheep',min:12,fail:'Not enough sheep gathered yet. Train on the lawn and come back.'}},\n  {t:'First, Hamish: a ram the size of a small decision and twice as stubborn.',fight:'hamish'},\n  {t:'The ewes follow. The Laird hands over the deed. It is a copy. \\u201cThe original went south again, for Pennywhistle\\u2019s grand ball.\\u201d'},\n  {t:'A goose on the pier takes exception to your leaving. Geese always do.',fight:'goose'},\n  {t:'Home, then. The fish cart is going anyway.'}\n ]},\n {title:'The Grand Ball',lvl:5,beats:[\n  {t:'Cousin Aldous has thrown a ball at Hawthorne House, which is not yet his. The dogs are meant to be shut in the boot room.'},\n  {t:'Mr Grimsby, solicitor, guards the library with a briefcase and the face of a man who bills by the quarter hour.',fight:'grimsby'},\n  {t:'Inside, Bismarck the dachshund sits on the deed like a loaf on a plate. He is longer than he is wise.',fight:'bismarck'},\n  {who:'Cousin Aldous',t:'\\u201cGet that animal out! That is my house, my deed and, in forty minutes, my dog!\\u201d',fight:'aldous'},\n  {t:'Aldous leaves the way he arrived: in a hurry, and talking. The deed is in your mouth. It tastes of lawyer.'}\n ]},\n {title:'The Deed',lvl:1,beats:[\n  {who:'Mr Hawthorne',t:'\\u201cWell. Well, old boy.\\u201d He sits down on the stairs, which he never does. His hand finds your ears.'},\n  {t:'The roof still leaks and somewhere there is always another cousin. But tonight you are lying across the door, and nobody is getting past you.'},\n  {t:'The house is saved, for now. Houses need saving in every generation. So do dogs.',end:true}\n ]}\n];\nconst SPEAKERS={\n 'Mr Hawthorne':{art:'human',coat:'#6e6250',shirt:'#f6ead2',hat:'none',hair:'#d9d2c4',buttons:1,stache:1},\n 'Laird MacCrimmon':{art:'human',coat:'#5a3a3a',shirt:'#e8d9b8',hat:'cap',hair:'#b84a2a',buttons:1,stache:1},\n };\nSPEAKERS['Mr Ashcombe']=ENEMIES.ashcombe;SPEAKERS['Lord Bartholomew']=ENEMIES.bartholomew;SPEAKERS['Cousin Aldous']=ENEMIES.aldous;\n\n/* ---------- state ---------- */\nlet S=null,L=null,busy=false,refreshing=false,mode='home',B=null,H=null,bubbleText='',bubbleUntil=0,faceOverride=null,faceUntil=0,foundAt=null,foundUntil=0,lastSig='',hitSide=null;\nconst lvl=s=>1+Math.floor(Math.sqrt((s.xp||0)/30));\nconst maxHp=s=>18+lvl(s)*5;\nconst atk=s=>3+lvl(s)*1.5+(s.obedience||0)/20;\nconst years=s=>Math.floor((Date.now()-s.bornAt)/DAY);\nconst lifeEff=s=>s.lifespan+Math.min(3,Math.floor((s.care||0)/25));\nconst stage=s=>{const y=years(s);return y<2?'pup':y<9?'adult':'old';};\nconst isLost=s=>s.status==='alive'&&s.found.day!==today();\nfunction newDog(gen,story,xpCarry){return{v:1,gen,name:'Downton Dog'+(gen>1?' '+roman(gen):''),bornAt:Date.now(),lifespan:12+rnd(0,3),status:'alive',xp:xpCarry||0,mood:60,obedience:10,care:0,biscuits:2,herding:{sheep:0,runs:0},wins:0,losses:0,finds:0,story:story||{ch:0,b:0,loop:0,done:false},found:{day:today(),by:'Mr Hawthorne',spot:'gatehouse'},searched:{day:today(),spots:{}},feed:[{by:'Mr Hawthorne',text:'carried a new pup through the gatehouse.',at:Date.now(),k:'event'}]};}\nasync function load(){\n let s=await store.get(KEY);\n if(!s||typeof s!=='object'||!s.bornAt){s=newDog(1,null,0);await store.set(KEY,s);}\n s.found=s.found||{day:null};s.searched=s.searched||{day:null,spots:{}};s.feed=s.feed||[];s.herding=s.herding||{sheep:0,runs:0};s.story=s.story||{ch:0,b:0,loop:0,done:false};\n if(s.searched.day!==today()){s.searched={day:today(),spots:{}};}\n return s;\n}\nfunction checkDeath(s){if(s.status==='alive'&&years(s)>=lifeEff(s)){s.status='dead';s.diedAt=Date.now();s.finalYears=years(s);return true;}return false;}\nfunction feed(s,text,k){s.feed.unshift({by:who(),text,at:Date.now(),k:k||'event'});s.feed=s.feed.slice(0,14);}\nasync function mut(fn){\n const s=await load();const died=checkDeath(s);\n if(!died)fn(s);\n await store.set(KEY,s);S=s;render(true);return s;\n}\n\n/* ---------- art: the dog ---------- */\nfunction dogSVG(o){\n const G='#f2c75c',D='#d9a93f',K='#2b2233',old=o.stage==='old',M=old?'#ece4d5':'#f8dc8c';\n const sc=o.stage==='pup'?.7:1,hs=o.stage==='pup'?1.28:1;\n const down=o.mood==='sad'||o.mood==='scold';\n const line=(d,w)=>'<path d=\"'+d+'\" fill=\"none\" stroke=\"'+K+'\" stroke-width=\"'+(w+5)+'\" stroke-linecap=\"round\"/><path d=\"'+d+'\" fill=\"none\" stroke=\"'+G+'\" stroke-width=\"'+w+'\" stroke-linecap=\"round\"/>';\n const legs=[-30,-20,12,22].map((x,i)=>line('M'+x+' -26 L'+(x+(i<2?-2:2))+' -2',5)).join('')+[-32,-18,10,24].map(x=>'<ellipse cx=\"'+x+'\" cy=\"-1\" rx=\"6\" ry=\"3.5\" fill=\"'+G+'\" stroke=\"'+K+'\" stroke-width=\"3\"/>').join('');\n const tail='<g class=\"'+(o.mood==='sad'||o.mood==='scold'||o.mood==='sleep'?'':'wag')+'\"><path d=\"M-40 -46 Q-66 -56 -60 -84\" fill=\"none\" stroke=\"'+K+'\" stroke-width=\"14\" stroke-linecap=\"round\"/><path d=\"M-40 -46 Q-66 -56 -60 -84\" fill=\"none\" stroke=\"'+G+'\" stroke-width=\"8\" stroke-linecap=\"round\"/></g>';\n const body='<ellipse cx=\"-6\" cy=\"-40\" rx=\"38\" ry=\"22\" fill=\"'+G+'\" stroke=\"'+K+'\" stroke-width=\"4\"/><ellipse cx=\"-2\" cy=\"-31\" rx=\"22\" ry=\"9\" fill=\"'+M+'\" opacity=\".8\"/>';\n const ear='<path transform=\"rotate('+(down?34:(o.mood==='happy'?-12:-4))+' 12 -78)\" d=\"M12 -78 Q-3 -62 4 -40 Q16 -46 17 -70 Z\" fill=\"'+D+'\" stroke=\"'+K+'\" stroke-width=\"3.5\" stroke-linejoin=\"round\"/>';\n const eyes=r=>[28,40].map(x=>'<circle cx=\"'+x+'\" cy=\"-66\" r=\"'+r+'\" fill=\"'+K+'\"/>').join('');\n let face='';\n if(o.mood==='happy')face=eyes(3.2)+'<path d=\"M46 -52 Q50 -44 58 -50\" fill=\"none\" stroke=\"'+K+'\" stroke-width=\"3\" stroke-linecap=\"round\"/><ellipse cx=\"51\" cy=\"-45\" rx=\"4\" ry=\"5.5\" fill=\"#f08aa0\" stroke=\"'+K+'\" stroke-width=\"2\"/>';\n else if(o.mood==='fierce')face=eyes(3)+'<path d=\"M22 -77 L33 -72 M47 -73 L37 -76\" stroke=\"'+K+'\" stroke-width=\"3.5\" stroke-linecap=\"round\"/><path d=\"M44 -52 Q52 -56 58 -52\" fill=\"none\" stroke=\"'+K+'\" stroke-width=\"3\"/><path d=\"M48 -53 l3 4 l3 -4\" fill=\"#fff\" stroke=\"'+K+'\" stroke-width=\"1.5\"/>';\n else if(o.mood==='sad')face=eyes(3)+'<path d=\"M22 -72 L33 -77 M47 -77 L37 -72\" stroke=\"'+K+'\" stroke-width=\"3\" stroke-linecap=\"round\"/><path d=\"M46 -48 Q52 -52 58 -48\" fill=\"none\" stroke=\"'+K+'\" stroke-width=\"3\"/>';\n else if(o.mood==='scold')face=[28,40].map(x=>'<circle cx=\"'+x+'\" cy=\"-66\" r=\"6\" fill=\"#fff\" stroke=\"'+K+'\" stroke-width=\"2.5\"/><circle cx=\"'+(x+1)+'\" cy=\"-65\" r=\"2.6\" fill=\"'+K+'\"/>').join('')+'<path d=\"M48 -50 Q52 -48 56 -50\" fill=\"none\" stroke=\"'+K+'\" stroke-width=\"3\"/>';\n else if(o.mood==='sleep')face=[28,40].map(x=>'<path d=\"M'+(x-4)+' -66 Q'+x+' -62 '+(x+4)+' -66\" fill=\"none\" stroke=\"'+K+'\" stroke-width=\"3\" stroke-linecap=\"round\"/>').join('')+'<path d=\"M48 -50 Q52 -47 56 -50\" fill=\"none\" stroke=\"'+K+'\" stroke-width=\"3\"/>';\n else face=eyes(3)+'<path d=\"M48 -51 Q52 -48 56 -51\" fill=\"none\" stroke=\"'+K+'\" stroke-width=\"3\"/>';\n const brows=old?'<path d=\"M23 -79 Q28 -83 33 -79 M36 -79 Q41 -83 46 -79\" fill=\"none\" stroke=\"#e9e0d2\" stroke-width=\"3\" stroke-linecap=\"round\"/>':'';\n const head='<g transform=\"translate(26 -58) scale('+hs+') translate(-26 58)\"><circle cx=\"30\" cy=\"-64\" r=\"23\" fill=\"'+G+'\" stroke=\"'+K+'\" stroke-width=\"4\"/><ellipse cx=\"44\" cy=\"-56\" rx=\"15\" ry=\"11\" fill=\"'+M+'\" stroke=\"'+K+'\" stroke-width=\"3.5\"/><ellipse cx=\"55\" cy=\"-60\" rx=\"5.5\" ry=\"4.5\" fill=\"'+K+'\"/>'+face+brows+ear+'</g>';\n const collar='<path d=\"M8 -44 Q20 -36 34 -42\" fill=\"none\" stroke=\"#8a2f3b\" stroke-width=\"6\"/><circle cx=\"22\" cy=\"-37\" r=\"4\" fill=\"#c89b3c\" stroke=\"'+K+'\" stroke-width=\"2\"/>';\n const halo=o.ghost?'<ellipse cx=\"30\" cy=\"-100\" rx=\"22\" ry=\"6\" fill=\"none\" stroke=\"#f2c75c\" stroke-width=\"6\"/>':'';\n return '<g transform=\"scale('+((o.flip?-1:1)*sc)+' '+sc+')\"'+(o.ghost?' opacity=\".7\"':'')+'>'+tail+legs+body+collar+head+halo+'</g>';\n}\n\n/* ---------- art: the household and its enemies ---------- */\nconst K='#2b2233';\nfunction human(p){\n const sk=p.skin||'#f3cfb3',tr=p.trou||'#3a3344';\n const legs='<path d=\"M-10 -30 L-12 -2 M10 -30 L12 -2\" stroke=\"'+K+'\" stroke-width=\"10\" stroke-linecap=\"round\"/><path d=\"M-10 -30 L-12 -2 M10 -30 L12 -2\" stroke=\"'+tr+'\" stroke-width=\"5\" stroke-linecap=\"round\"/><ellipse cx=\"-13\" cy=\"-1\" rx=\"8\" ry=\"3.5\" fill=\"'+K+'\"/><ellipse cx=\"13\" cy=\"-1\" rx=\"8\" ry=\"3.5\" fill=\"'+K+'\"/>';\n const body='<path d=\"M-20 -80 Q-25 -30 -16 -28 H16 Q25 -30 20 -80 Z\" fill=\"'+p.coat+'\" stroke=\"'+K+'\" stroke-width=\"4\" stroke-linejoin=\"round\"/>'+(p.shirt?'<path d=\"M-7 -80 L0 -52 L7 -80 Z\" fill=\"'+p.shirt+'\" stroke=\"'+K+'\" stroke-width=\"2.5\" stroke-linejoin=\"round\"/>':'')+(p.buttons?'<circle cx=\"0\" cy=\"-47\" r=\"2\" fill=\"'+K+'\"/><circle cx=\"0\" cy=\"-39\" r=\"2\" fill=\"'+K+'\"/>':'');\n const arm=(d,hx,hy)=>'<path d=\"'+d+'\" fill=\"none\" stroke=\"'+K+'\" stroke-width=\"10\" stroke-linecap=\"round\"/><path d=\"'+d+'\" fill=\"none\" stroke=\"'+p.coat+'\" stroke-width=\"5\" stroke-linecap=\"round\"/><circle cx=\"'+hx+'\" cy=\"'+hy+'\" r=\"5.5\" fill=\"'+sk+'\" stroke=\"'+K+'\" stroke-width=\"2.5\"/>';\n const arms=arm('M-18 -72 Q-40 -62 -34 -38',-34,-36)+arm('M18 -72 Q44 -66 40 -46',40,-44);\n const props={\n  duster:'<path d=\"M40 -44 L48 -78\" stroke=\"#8a5a2b\" stroke-width=\"3\"/><g fill=\"#e07aa0\" stroke=\"'+K+'\" stroke-width=\"1.5\"><ellipse cx=\"48\" cy=\"-86\" rx=\"7\" ry=\"11\"/><ellipse cx=\"41\" cy=\"-84\" rx=\"5\" ry=\"9\" transform=\"rotate(-25 41 -84)\"/><ellipse cx=\"55\" cy=\"-84\" rx=\"5\" ry=\"9\" transform=\"rotate(25 55 -84)\"/></g>',\n  tray:'<ellipse cx=\"46\" cy=\"-50\" rx=\"16\" ry=\"5\" fill=\"#dfe3ea\" stroke=\"'+K+'\" stroke-width=\"2.5\"/><path d=\"M38 -52 h6 v-8 h-6 z M50 -52 h4 v-6 h-4 z\" fill=\"#c9b7e6\" stroke=\"'+K+'\" stroke-width=\"1.5\"/>',\n  tankard:'<path d=\"M34 -62 h14 v18 h-14 z\" fill=\"#c9c9cf\" stroke=\"'+K+'\" stroke-width=\"2.5\"/><path d=\"M48 -58 q8 0 8 6 q0 6 -8 6\" fill=\"none\" stroke=\"'+K+'\" stroke-width=\"2.5\"/><ellipse cx=\"41\" cy=\"-62\" rx=\"9\" ry=\"4\" fill=\"#fff\"/>',\n  bottle:'<path d=\"M37 -46 v-16 q0 -4 3 -4 h2 v-10 h4 v10 h2 q3 0 3 4 v16 z\" fill=\"#3e6b3a\" stroke=\"'+K+'\" stroke-width=\"2.5\" stroke-linejoin=\"round\"/>',\n  cane:'<path d=\"M40 -44 L46 -2\" stroke=\"'+K+'\" stroke-width=\"4\" stroke-linecap=\"round\"/><circle cx=\"40\" cy=\"-46\" r=\"4\" fill=\"#c89b3c\" stroke=\"'+K+'\" stroke-width=\"2\"/>',\n  'case':'<rect x=\"32\" y=\"-44\" width=\"24\" height=\"18\" rx=\"3\" fill=\"#5a3a22\" stroke=\"'+K+'\" stroke-width=\"2.5\"/><path d=\"M40 -44 v-4 h8 v4\" fill=\"none\" stroke=\"'+K+'\" stroke-width=\"2.5\"/>'\n };\n const prop=props[p.prop]||'';\n const cheeks=p.drunk?'<circle cx=\"-11\" cy=\"-90\" r=\"4\" fill=\"#f08aa0\" opacity=\".8\"/><circle cx=\"11\" cy=\"-90\" r=\"4\" fill=\"#f08aa0\" opacity=\".8\"/>':'';\n const eyes=p.drunk?'<path d=\"M-11 -98 q4 -4 8 0 M3 -98 q4 -4 8 0\" fill=\"none\" stroke=\"'+K+'\" stroke-width=\"2.5\" stroke-linecap=\"round\"/>':'<circle cx=\"-7\" cy=\"-98\" r=\"2.6\" fill=\"'+K+'\"/><circle cx=\"7\" cy=\"-98\" r=\"2.6\" fill=\"'+K+'\"/>';\n const mouth=p.drunk?'<path d=\"M-6 -86 q6 6 12 0\" fill=\"none\" stroke=\"'+K+'\" stroke-width=\"2.5\" stroke-linecap=\"round\"/>':'<path d=\"M-6 -85 q6 -5 12 0\" fill=\"none\" stroke=\"'+K+'\" stroke-width=\"2.5\" stroke-linecap=\"round\"/>';\n const brows='<path d=\"M-12 -106 l9 2 M12 -106 l-9 2\" stroke=\"'+K+'\" stroke-width=\"2.5\" stroke-linecap=\"round\"/>';\n const stache=p.stache||p.prop==='cane'||p.prop==='case'||p.prop==='tray'?'<path d=\"M-8 -89 q8 -4 16 0 q-4 3 -8 2 q-4 1 -8 -2 z\" fill=\"'+K+'\"/>':'';\n const head='<circle cx=\"0\" cy=\"-96\" r=\"19\" fill=\"'+sk+'\" stroke=\"'+K+'\" stroke-width=\"4\"/>'+eyes+mouth+brows+cheeks+stache;\n const hats={\n  top:'<rect x=\"-13\" y=\"-144\" width=\"26\" height=\"30\" fill=\"'+K+'\"/><ellipse cx=\"0\" cy=\"-114\" rx=\"22\" ry=\"5\" fill=\"'+K+'\"/><rect x=\"-13\" y=\"-122\" width=\"26\" height=\"4\" fill=\"#8a2f3b\"/>',\n  bowler:'<path d=\"M-15 -112 q0 -22 15 -22 q15 0 15 22 z\" fill=\"'+K+'\"/><ellipse cx=\"0\" cy=\"-112\" rx=\"22\" ry=\"5\" fill=\"'+K+'\"/>',\n  cap:'<path d=\"M-19 -108 q2 -18 19 -18 q17 0 19 18 z\" fill=\"#5a4a3a\" stroke=\"'+K+'\" stroke-width=\"2.5\"/><path d=\"M-10 -108 h34 q-2 5 -8 5 h-26 z\" fill=\"#4a3a2a\" stroke=\"'+K+'\" stroke-width=\"2\"/>',\n  none:'<path d=\"M-19 -100 q2 -22 19 -22 q17 0 19 22 q-10 -10 -19 -8 q-9 -2 -19 8 z\" fill=\"'+(p.hair||'#5a4a3a')+'\"/>'\n };\n return legs+body+arms+prop+head+(hats[p.hat]||hats.none);\n}\nfunction cat(){const f='#efe6e2';return '<path d=\"M-28 -16 Q-50 -30 -40 -52 Q-30 -62 -24 -48\" fill=\"none\" stroke=\"'+K+'\" stroke-width=\"12\" stroke-linecap=\"round\"/><path d=\"M-28 -16 Q-50 -30 -40 -52 Q-30 -62 -24 -48\" fill=\"none\" stroke=\"'+f+'\" stroke-width=\"7\" stroke-linecap=\"round\"/><ellipse cx=\"-4\" cy=\"-24\" rx=\"34\" ry=\"22\" fill=\"'+f+'\" stroke=\"'+K+'\" stroke-width=\"4\"/><g fill=\"'+f+'\" stroke=\"'+K+'\" stroke-width=\"3\"><circle cx=\"-26\" cy=\"-36\" r=\"9\"/><circle cx=\"-10\" cy=\"-44\" r=\"10\"/><circle cx=\"8\" cy=\"-42\" r=\"9\"/></g><circle cx=\"22\" cy=\"-50\" r=\"19\" fill=\"'+f+'\" stroke=\"'+K+'\" stroke-width=\"4\"/><path d=\"M8 -64 L4 -82 L18 -70 Z M36 -64 L40 -82 L26 -70 Z\" fill=\"'+f+'\" stroke=\"'+K+'\" stroke-width=\"3\" stroke-linejoin=\"round\"/><path d=\"M10 -78 q6 2 10 7 M34 -78 q-6 2 -10 7\" fill=\"none\" stroke=\"#f08aa0\" stroke-width=\"2\"/><path d=\"M12 -54 q6 -3 10 0 M22 -54 q6 -3 10 0\" fill=\"none\" stroke=\"'+K+'\" stroke-width=\"3\" stroke-linecap=\"round\"/><path d=\"M19 -44 l3 3 l3 -3 z\" fill=\"#f08aa0\" stroke=\"'+K+'\" stroke-width=\"1.5\"/><path d=\"M4 -42 h12 M30 -42 h12 M4 -38 h10 M32 -38 h10\" stroke=\"'+K+'\" stroke-width=\"1.5\"/><path d=\"M-6 -44 l-6 -6 l-6 6 l6 6 z\" fill=\"#e07aa0\" stroke=\"'+K+'\" stroke-width=\"2\"/><ellipse cx=\"-14\" cy=\"-2\" rx=\"9\" ry=\"4\" fill=\"'+f+'\" stroke=\"'+K+'\" stroke-width=\"3\"/><ellipse cx=\"14\" cy=\"-2\" rx=\"9\" ry=\"4\" fill=\"'+f+'\" stroke=\"'+K+'\" stroke-width=\"3\"/>';}\nfunction pony(){const c='#8c6b57',m='#3a2a22';const leg=x=>'<path d=\"M'+x+' -32 L'+x+' -2\" stroke=\"'+K+'\" stroke-width=\"11\" stroke-linecap=\"round\"/><path d=\"M'+x+' -32 L'+x+' -2\" stroke=\"'+c+'\" stroke-width=\"6\" stroke-linecap=\"round\"/><ellipse cx=\"'+x+'\" cy=\"-1\" rx=\"6\" ry=\"3\" fill=\"'+K+'\"/>';return '<path d=\"M-36 -44 Q-56 -40 -52 -14\" fill=\"none\" stroke=\"'+K+'\" stroke-width=\"11\" stroke-linecap=\"round\"/><path d=\"M-36 -44 Q-56 -40 -52 -14\" fill=\"none\" stroke=\"'+m+'\" stroke-width=\"6\" stroke-linecap=\"round\"/>'+[-28,-16,12,24].map(leg).join('')+'<ellipse cx=\"-4\" cy=\"-44\" rx=\"38\" ry=\"20\" fill=\"'+c+'\" stroke=\"'+K+'\" stroke-width=\"4\"/><path d=\"M22 -56 Q30 -84 44 -84 L50 -76 Q40 -64 34 -50 Z\" fill=\"'+c+'\" stroke=\"'+K+'\" stroke-width=\"4\" stroke-linejoin=\"round\"/><path d=\"M26 -60 Q34 -86 46 -88\" fill=\"none\" stroke=\"'+m+'\" stroke-width=\"7\" stroke-linecap=\"round\"/><ellipse cx=\"50\" cy=\"-76\" rx=\"18\" ry=\"11\" fill=\"'+c+'\" stroke=\"'+K+'\" stroke-width=\"4\"/><ellipse cx=\"62\" cy=\"-74\" rx=\"7\" ry=\"6\" fill=\"#c9a28a\" stroke=\"'+K+'\" stroke-width=\"3\"/><circle cx=\"48\" cy=\"-80\" r=\"2.6\" fill=\"'+K+'\"/><circle cx=\"48\" cy=\"-80\" r=\"7\" fill=\"none\" stroke=\"#c89b3c\" stroke-width=\"2\"/><path d=\"M53 -73 L58 -90\" stroke=\"#c89b3c\" stroke-width=\"1.5\"/><path d=\"M40 -86 l-4 -10 l8 4 z M50 -88 l2 -10 l5 6 z\" fill=\"'+c+'\" stroke=\"'+K+'\" stroke-width=\"2.5\" stroke-linejoin=\"round\"/><rect x=\"34\" y=\"-120\" width=\"22\" height=\"26\" fill=\"'+K+'\"/><ellipse cx=\"45\" cy=\"-94\" rx=\"18\" ry=\"4\" fill=\"'+K+'\"/><rect x=\"34\" y=\"-100\" width=\"22\" height=\"3\" fill=\"#8a2f3b\"/><path d=\"M60 -70 q4 2 2 5\" fill=\"none\" stroke=\"'+K+'\" stroke-width=\"2\"/>';}\nfunction ram(){const f='#f4efe3';const leg=x=>'<path d=\"M'+x+' -22 L'+x+' -2\" stroke=\"'+K+'\" stroke-width=\"8\" stroke-linecap=\"round\"/><ellipse cx=\"'+x+'\" cy=\"-1\" rx=\"5\" ry=\"2.5\" fill=\"'+K+'\"/>';return [-26,-14,12,24].map(leg).join('')+'<path d=\"M-40 -28 Q-48 -56 -24 -58 Q-16 -74 4 -66 Q22 -74 30 -56 Q46 -50 36 -26 Z\" fill=\"'+f+'\" stroke=\"'+K+'\" stroke-width=\"4\" stroke-linejoin=\"round\"/><g fill=\"'+f+'\" stroke=\"'+K+'\" stroke-width=\"3\"><circle cx=\"-26\" cy=\"-46\" r=\"9\"/><circle cx=\"-8\" cy=\"-56\" r=\"10\"/><circle cx=\"12\" cy=\"-54\" r=\"9\"/><circle cx=\"26\" cy=\"-42\" r=\"8\"/></g><ellipse cx=\"40\" cy=\"-52\" rx=\"16\" ry=\"13\" fill=\"'+K+'\"/><circle cx=\"44\" cy=\"-56\" r=\"2.6\" fill=\"#fff\"/><circle cx=\"45\" cy=\"-56\" r=\"1.2\" fill=\"'+K+'\"/><path d=\"M30 -60 q-14 -8 -12 -22 q4 -12 14 -8 q6 4 2 12\" fill=\"none\" stroke=\"#b89b6a\" stroke-width=\"7\" stroke-linecap=\"round\"/><path d=\"M30 -60 q-14 -8 -12 -22 q4 -12 14 -8 q6 4 2 12\" fill=\"none\" stroke=\"'+K+'\" stroke-width=\"2\"/><path d=\"M44 -44 l6 6 l-10 0 z\" fill=\"'+K+'\"/>';}\nfunction goose(){const w='#fbf7f0';return '<ellipse cx=\"-6\" cy=\"-26\" rx=\"30\" ry=\"17\" fill=\"'+w+'\" stroke=\"'+K+'\" stroke-width=\"4\"/><path d=\"M-22 -36 q-16 -4 -22 2 q10 2 14 8\" fill=\"'+w+'\" stroke=\"'+K+'\" stroke-width=\"3\" stroke-linejoin=\"round\"/><path d=\"M-10 -40 q-20 10 -10 24 q16 -4 24 -14 z\" fill=\"#efe8d8\" stroke=\"'+K+'\" stroke-width=\"3\" stroke-linejoin=\"round\"/><path d=\"M14 -34 Q20 -60 30 -74\" fill=\"none\" stroke=\"'+K+'\" stroke-width=\"15\" stroke-linecap=\"round\"/><path d=\"M14 -34 Q20 -60 30 -74\" fill=\"none\" stroke=\"'+w+'\" stroke-width=\"9\" stroke-linecap=\"round\"/><circle cx=\"32\" cy=\"-78\" r=\"10\" fill=\"'+w+'\" stroke=\"'+K+'\" stroke-width=\"4\"/><path d=\"M40 -80 l16 4 l-16 6 z\" fill=\"#f08a2a\" stroke=\"'+K+'\" stroke-width=\"2.5\" stroke-linejoin=\"round\"/><circle cx=\"34\" cy=\"-82\" r=\"2.4\" fill=\"'+K+'\"/><path d=\"M26 -88 l10 3\" stroke=\"'+K+'\" stroke-width=\"2.5\" stroke-linecap=\"round\"/><path d=\"M-12 -10 l-4 8 M4 -10 l4 8\" stroke=\"#f08a2a\" stroke-width=\"4\" stroke-linecap=\"round\"/><path d=\"M-20 -2 h8 M4 -2 h8\" stroke=\"#f08a2a\" stroke-width=\"4\" stroke-linecap=\"round\"/>';}\nfunction ferret(){const c='#cfb08d';return '<path d=\"M-42 -16 Q-60 -20 -58 -34\" fill=\"none\" stroke=\"'+K+'\" stroke-width=\"12\" stroke-linecap=\"round\"/><path d=\"M-42 -16 Q-60 -20 -58 -34\" fill=\"none\" stroke=\"#9a7a5a\" stroke-width=\"7\" stroke-linecap=\"round\"/><rect x=\"-46\" y=\"-30\" width=\"74\" height=\"24\" rx=\"12\" fill=\"'+c+'\" stroke=\"'+K+'\" stroke-width=\"4\"/><circle cx=\"30\" cy=\"-30\" r=\"14\" fill=\"'+c+'\" stroke=\"'+K+'\" stroke-width=\"4\"/><path d=\"M18 -34 q10 -6 24 0 q-4 10 -12 10 q-8 0 -12 -10 z\" fill=\"#5a3a2a\"/><circle cx=\"26\" cy=\"-31\" r=\"2.4\" fill=\"#fff\"/><circle cx=\"36\" cy=\"-31\" r=\"2.4\" fill=\"#fff\"/><circle cx=\"26.5\" cy=\"-31\" r=\"1.2\" fill=\"'+K+'\"/><circle cx=\"36.5\" cy=\"-31\" r=\"1.2\" fill=\"'+K+'\"/><ellipse cx=\"44\" cy=\"-26\" rx=\"4\" ry=\"3\" fill=\"#f08aa0\" stroke=\"'+K+'\" stroke-width=\"1.5\"/><circle cx=\"20\" cy=\"-42\" r=\"4\" fill=\"'+c+'\" stroke=\"'+K+'\" stroke-width=\"2.5\"/><circle cx=\"36\" cy=\"-42\" r=\"4\" fill=\"'+c+'\" stroke=\"'+K+'\" stroke-width=\"2.5\"/><path d=\"M-34 -6 v5 M-20 -6 v5 M6 -6 v5 M20 -6 v5\" stroke=\"'+K+'\" stroke-width=\"5\" stroke-linecap=\"round\"/>';}\nfunction dach(){const c='#6b3a2a';return '<path d=\"M-50 -36 Q-62 -44 -56 -58\" fill=\"none\" stroke=\"'+K+'\" stroke-width=\"12\" stroke-linecap=\"round\"/><path d=\"M-50 -36 Q-62 -44 -56 -58\" fill=\"none\" stroke=\"'+c+'\" stroke-width=\"6\" stroke-linecap=\"round\"/><path d=\"M-40 -10 v8 M-26 -10 v8 M12 -10 v8 M26 -10 v8\" stroke=\"'+K+'\" stroke-width=\"10\" stroke-linecap=\"round\"/><path d=\"M-40 -10 v8 M-26 -10 v8 M12 -10 v8 M26 -10 v8\" stroke=\"'+c+'\" stroke-width=\"5\" stroke-linecap=\"round\"/><rect x=\"-52\" y=\"-40\" width=\"84\" height=\"30\" rx=\"15\" fill=\"'+c+'\" stroke=\"'+K+'\" stroke-width=\"4\"/><circle cx=\"36\" cy=\"-48\" r=\"16\" fill=\"'+c+'\" stroke=\"'+K+'\" stroke-width=\"4\"/><path d=\"M44 -52 q18 -2 24 6 q-8 4 -22 2 z\" fill=\"'+c+'\" stroke=\"'+K+'\" stroke-width=\"3.5\" stroke-linejoin=\"round\"/><ellipse cx=\"66\" cy=\"-46\" rx=\"4\" ry=\"3.5\" fill=\"'+K+'\"/><circle cx=\"36\" cy=\"-52\" r=\"2.6\" fill=\"#fff\"/><circle cx=\"37\" cy=\"-52\" r=\"1.4\" fill=\"'+K+'\"/><path d=\"M26 -60 q-10 10 -4 26 q6 -4 8 -14 z\" fill=\"#4a2618\" stroke=\"'+K+'\" stroke-width=\"3\" stroke-linejoin=\"round\"/><path d=\"M28 -66 l10 2\" stroke=\"'+K+'\" stroke-width=\"2.5\" stroke-linecap=\"round\"/><path d=\"M14 -32 Q24 -26 34 -34\" fill=\"none\" stroke=\"#2f3f6b\" stroke-width=\"5\"/>';}\nfunction enemyArt(e){switch(e.art){case'human':return human(e);case'cat':return cat();case'pony':return pony();case'ram':return ram();case'goose':return goose();case'ferret':return ferret();case'dach':return dach();}return '';}\nfunction sheepSVG(){return '<path d=\"M-10 -14 L-10 -1 M6 -14 L6 -1\" stroke=\"'+K+'\" stroke-width=\"4\" stroke-linecap=\"round\"/><path d=\"M-22 -16 Q-28 -32 -12 -34 Q-6 -44 6 -38 Q18 -42 20 -30 Q30 -24 20 -14 Z\" fill=\"#fbf6ea\" stroke=\"'+K+'\" stroke-width=\"3\" stroke-linejoin=\"round\"/><circle cx=\"20\" cy=\"-32\" r=\"8\" fill=\"'+K+'\"/><circle cx=\"23\" cy=\"-33\" r=\"1.6\" fill=\"#fff\"/><path d=\"M12 -41 L16 -35 M27 -41 L24 -35\" stroke=\"'+K+'\" stroke-width=\"3\" stroke-linecap=\"round\"/><rect x=\"8\" y=\"-29\" width=\"12\" height=\"6\" rx=\"2\" fill=\"#b4322e\"/><path d=\"M8 -26 H20 M12 -29 V-23 M16 -29 V-23\" stroke=\"#2f6b3a\" stroke-width=\"1.4\"/>';}\n\n/* ---------- intertitles ---------- */\nfunction wrapText(t,max){const out=[];let line='';t.split(' ').forEach(w=>{if((line+' '+w).trim().length>max){out.push(line.trim());line=w;}else line+=' '+w;});if(line.trim())out.push(line.trim());return out;}\nfunction card(text,title,tappable,top){\n const lines=wrapText(text,46).slice(0,4),lh=16,h=20+(title?15:0)+lines.length*lh,y=top?52:432-h;\n let o='<g'+(tappable?' data-act=\"next\" style=\"cursor:pointer\"':'')+' transform=\"translate(20,'+y+')\"><rect width=\"320\" height=\"'+h+'\" rx=\"6\" fill=\"#1f1826\" stroke=\"#c89b3c\" stroke-width=\"2.5\"/><rect x=\"5\" y=\"5\" width=\"310\" height=\"'+(h-10)+'\" rx=\"3\" fill=\"none\" stroke=\"#c89b3c\" stroke-width=\"1\" opacity=\".55\"/>';\n if(title)o+='<text x=\"160\" y=\"18\" text-anchor=\"middle\" font-size=\"10\" font-weight=\"800\" fill=\"#c89b3c\" letter-spacing=\".6\" font-family=\"Baloo 2,Fredoka,ui-rounded,system-ui,sans-serif\">'+esc(title)+'</text>';\n o+='<text x=\"160\" y=\"'+(title?33:21)+'\" text-anchor=\"middle\" font-size=\"12.2\" font-style=\"italic\" fill=\"#f6ead2\" font-family=\"Georgia,Iowan Old Style,Palatino,serif\">'+lines.map((l,i)=>'<tspan x=\"160\" dy=\"'+(i?lh:0)+'\">'+esc(l)+'</tspan>').join('')+'</text>';\n if(tappable)o+='<text x=\"309\" y=\"'+(h-7)+'\" text-anchor=\"end\" font-size=\"9\" fill=\"#c89b3c\" opacity=\".8\" font-family=\"Baloo 2,Fredoka,ui-rounded,system-ui,sans-serif\">tap \\u25B8</text>';\n return o+'</g>';\n}\nfunction storyState(){\n const st=S.story,chap=STORY[st.ch];\n if(st.done)return{kind:'done',title:'the house is saved',text:'For now. Somewhere another cousin is sharpening a pen. Play the plot again and the villains come back nastier.',btn:'Begin the plot again',act:loopStory};\n if(st.b>=chap.beats.length){const nxt=STORY[st.ch+1];if(!nxt)return{kind:'end',text:'The end of the matter.',btn:'Close the book',act:advance};const ok=lvl(S)>=nxt.lvl;\n  return{kind:'gate',title:'end of chapter '+(st.ch+1),text:ok?'Chapter '+(st.ch+2)+', '+nxt.title+', can begin.':'Chapter '+(st.ch+2)+', '+nxt.title+', begins at level '+nxt.lvl+'. He is level '+lvl(S)+': find him, praise him, herd sheep.',btn:ok?'Begin chapter '+(st.ch+2):'Not yet',dis:!ok,act:advance};}\n const beat=chap.beats[st.b];const o={kind:'beat',beat,title:beat.who||('Chapter '+(st.ch+1)+(st.loop?', plot '+(st.loop+1):'')+': '+chap.title),text:beat.t,act:advance};\n if(beat.fight){o.btn='Face '+ENEMIES[beat.fight].n.split(',')[0];o.who=beat.fight;}\n else if(beat.gate){const ok=(S.herding.sheep||0)>=beat.gate.min;o.btn=ok?'Take the wager':'Needs '+beat.gate.min+' sheep ('+(S.herding.sheep||0)+')';o.dis=!ok;o.alt={btn:'Train on the lawn',act:startHerd};}\n else if(beat.end)o.btn='Lie across the door';else o.btn='Continue';\n return o;\n}\n\n/* ---------- art: the scene ---------- */\nfunction phase(){const h=new Date().getHours();return h<5?'night':h<7?'dawn':h<17?'day':h<20?'dusk':'night';}\nconst SKY={dawn:['#cfd8e8','#f6c7a8'],day:['#9cc7e6','#eef0e8'],dusk:['#6e5a9a','#f0b07e'],night:['#1e1b3a','#4a3f6e']};\nfunction bubble(text,x,y,w,size){size=size||15;w=w||Math.min(300,Math.max(70,text.length*size*.6+28));const tail=w/2;return '<g transform=\"translate('+x+','+y+')\"><g class=\"pop\"><path d=\"M0 12 Q0 0 12 0 H'+(w-12)+' Q'+w+' 0 '+w+' 12 V36 Q'+w+' 48 '+(w-12)+' 48 H'+(tail+14)+' L'+tail+' 62 L'+(tail-6)+' 48 H12 Q0 48 0 36 Z\" fill=\"#fff\" stroke=\"'+K+'\" stroke-width=\"3.5\" stroke-linejoin=\"round\"/><text x=\"'+(w/2)+'\" y=\"30\" text-anchor=\"middle\" font-size=\"'+size+'\" font-weight=\"800\" fill=\"'+K+'\" font-family=\"Baloo 2,Fredoka,ui-rounded,system-ui,sans-serif\">'+esc(text)+'</text></g></g>';}\nfunction house(ph,dead){\n const night=ph==='night',lit=night||ph==='dusk',win=lit?'#f6cf6b':ph==='dawn'?'#c9d6e6':'#9fb7c9',stone='#dccbab',stone2='#cfbb97',roof='#6a5a72';\n const w=(x,y,h)=>'<rect x=\"'+x+'\" y=\"'+y+'\" width=\"10\" height=\"'+(h||16)+'\" fill=\"'+win+'\" stroke=\"'+K+'\" stroke-width=\"2\"/><path d=\"M'+(x+5)+' '+y+' v'+(h||16)+' M'+x+' '+(y+(h||16)/2)+' h10\" stroke=\"'+K+'\" stroke-width=\"1.2\"/>';\n let s='';\n s+='<path d=\"M-20 212 Q60 168 150 196 T380 190 V215 H-20 Z\" fill=\"#a8b58a\"/>';\n s+='<g fill=\"#6f8a55\" stroke=\"'+K+'\" stroke-width=\"2.5\"><circle cx=\"12\" cy=\"150\" r=\"14\"/><circle cx=\"30\" cy=\"144\" r=\"18\"/><circle cx=\"350\" cy=\"146\" r=\"16\"/><circle cx=\"334\" cy=\"140\" r=\"12\"/></g><path d=\"M12 162 v30 M30 160 v32 M350 160 v30\" stroke=\"'+K+'\" stroke-width=\"3\"/>';\n s+='<rect x=\"128\" y=\"68\" width=\"10\" height=\"26\" fill=\"#8a2f3b\" stroke=\"'+K+'\" stroke-width=\"2.5\"/><rect x=\"222\" y=\"68\" width=\"10\" height=\"26\" fill=\"#8a2f3b\" stroke=\"'+K+'\" stroke-width=\"2.5\"/><rect x=\"56\" y=\"104\" width=\"8\" height=\"18\" fill=\"#8a2f3b\" stroke=\"'+K+'\" stroke-width=\"2.5\"/><rect x=\"296\" y=\"104\" width=\"8\" height=\"18\" fill=\"#8a2f3b\" stroke=\"'+K+'\" stroke-width=\"2.5\"/>';\n s+='<g opacity=\".55\" fill=\"#fff\"><circle class=\"smoke\" cx=\"133\" cy=\"62\" r=\"4\"/><circle class=\"smoke\" style=\"animation-delay:-1.6s\" cx=\"227\" cy=\"62\" r=\"4\"/></g>';\n s+='<path d=\"M84 118 L122 86 H238 L276 118 Z\" fill=\"'+roof+'\" stroke=\"'+K+'\" stroke-width=\"3.5\" stroke-linejoin=\"round\"/>';\n s+='<path d=\"M36 138 L58 118 H96 V138 Z M324 138 L302 118 H264 V138 Z\" fill=\"'+roof+'\" stroke=\"'+K+'\" stroke-width=\"3.5\" stroke-linejoin=\"round\"/>';\n s+='<rect x=\"40\" y=\"138\" width=\"56\" height=\"70\" fill=\"'+stone2+'\" stroke=\"'+K+'\" stroke-width=\"3.5\"/><rect x=\"264\" y=\"138\" width=\"56\" height=\"70\" fill=\"'+stone2+'\" stroke=\"'+K+'\" stroke-width=\"3.5\"/>';\n s+='<rect x=\"88\" y=\"118\" width=\"184\" height=\"90\" fill=\"'+stone+'\" stroke=\"'+K+'\" stroke-width=\"3.5\"/>';\n s+='<path d=\"M88 118 h184\" stroke=\"'+K+'\" stroke-width=\"3\" stroke-dasharray=\"4 4\"/>';\n s+='<path d=\"M60 170 q-6 -20 10 -24 q6 -14 18 -4 q4 20 -8 30 z\" fill=\"#6f8a55\" opacity=\".85\"/>';\n [100,116,132,148,212,228,244,260].forEach(x=>{s+=w(x,128)+w(x,162);});\n [48,64,80,276,292,308].forEach(x=>{s+=w(x,150)+w(x,180,14);});\n s+='<path d=\"M150 152 L180 130 L210 152 Z\" fill=\"'+stone+'\" stroke=\"'+K+'\" stroke-width=\"3.5\" stroke-linejoin=\"round\"/>';\n [158,170,190,202].forEach(x=>{s+='<rect x=\"'+(x-3)+'\" y=\"152\" width=\"6\" height=\"56\" fill=\"#efe3c8\" stroke=\"'+K+'\" stroke-width=\"2.5\"/>';});\n s+='<rect x=\"172\" y=\"174\" width=\"16\" height=\"34\" fill=\"#4a2618\" stroke=\"'+K+'\" stroke-width=\"3\"/><path d=\"M172 180 a8 8 0 0 1 16 0 z\" fill=\"'+win+'\" stroke=\"'+K+'\" stroke-width=\"2\"/><circle cx=\"184\" cy=\"194\" r=\"1.6\" fill=\"#c89b3c\"/>';\n if(dead)s+='<path d=\"M180 170 l-8 -6 l8 2 l8 -2 z\" fill=\"'+K+'\"/><path d=\"M178 170 l-3 10 M182 170 l3 10\" stroke=\"'+K+'\" stroke-width=\"3\" stroke-linecap=\"round\"/>';\n s+='<path d=\"M150 208 h60 l4 6 h-68 z M146 214 h68 l4 6 h-76 z\" fill=\"#efe3c8\" stroke=\"'+K+'\" stroke-width=\"2.5\" stroke-linejoin=\"round\"/>';\n return s;\n}\nfunction spotsArt(ph,lostInfo,omit){omit=omit||{};\n const night=ph==='night';\n const g=(id,inner)=>'<g data-spot=\"'+id+'\">'+inner+'</g>';\n let s='';\n s+=g('gatehouse','<path d=\"M-4 212 V168 Q20 150 44 168 V212 Z\" fill=\"#cfbb97\" stroke=\"'+K+'\" stroke-width=\"3.5\" stroke-linejoin=\"round\"/><path d=\"M-6 170 Q20 148 46 170\" fill=\"none\" stroke=\"#6a5a72\" stroke-width=\"8\"/><path d=\"M10 212 V190 a10 10 0 0 1 20 0 V212 Z\" fill=\"'+(night?'#f6cf6b':'#4a2618')+'\" stroke=\"'+K+'\" stroke-width=\"3\"/>');\n s+=g('stable','<path d=\"M298 212 V160 L330 144 L362 160 V212 Z\" fill=\"#8a6a46\" stroke=\"'+K+'\" stroke-width=\"3.5\" stroke-linejoin=\"round\"/><path d=\"M296 162 L330 142 L364 162\" fill=\"none\" stroke=\"#5a3a22\" stroke-width=\"7\"/><rect x=\"316\" y=\"178\" width=\"28\" height=\"34\" fill=\"#4a2618\" stroke=\"'+K+'\" stroke-width=\"3\"/><path d=\"M316 178 L344 212 M344 178 L316 212\" stroke=\"#c9a66b\" stroke-width=\"2.5\"/><rect x=\"304\" y=\"168\" width=\"8\" height=\"8\" fill=\"'+(night?'#f6cf6b':'#9fb7c9')+'\" stroke=\"'+K+'\" stroke-width=\"2\"/>');\n s+=g('haha','<path d=\"M0 220 H360\" stroke=\"#5a7042\" stroke-width=\"12\"/><path d=\"M0 220 H360\" stroke=\"'+K+'\" stroke-width=\"3\" stroke-dasharray=\"14 10\"/><rect x=\"60\" y=\"213\" width=\"240\" height=\"14\" fill=\"transparent\"/>');\n if(!omit.fountain)s+=g('fountain','<ellipse cx=\"75\" cy=\"282\" rx=\"34\" ry=\"11\" fill=\"#cfbb97\" stroke=\"'+K+'\" stroke-width=\"3.5\"/><ellipse cx=\"75\" cy=\"279\" rx=\"26\" ry=\"7\" fill=\"#9ec6e0\" stroke=\"'+K+'\" stroke-width=\"2\"/><rect x=\"70\" y=\"252\" width=\"10\" height=\"26\" fill=\"#cfbb97\" stroke=\"'+K+'\" stroke-width=\"3\"/><ellipse cx=\"75\" cy=\"252\" rx=\"14\" ry=\"5\" fill=\"#cfbb97\" stroke=\"'+K+'\" stroke-width=\"3\"/><g class=\"jet\" style=\"transform-box:fill-box;transform-origin:50% 100%\"><path d=\"M75 250 q-6 -14 -2 -24 q6 10 2 24 M75 250 q0 -16 6 -22 q-2 12 -6 22\" fill=\"#9ec6e0\" stroke=\"'+K+'\" stroke-width=\"2\" stroke-linejoin=\"round\"/></g>');\n if(!omit.arbour)s+=g('arbour','<path d=\"M248 288 V268 Q270 244 292 268 V288\" fill=\"none\" stroke=\"'+K+'\" stroke-width=\"10\" stroke-linecap=\"round\"/><path d=\"M248 288 V268 Q270 244 292 268 V288\" fill=\"none\" stroke=\"#fff\" stroke-width=\"5\"/><path d=\"M250 280 l8 -8 M250 270 l10 -4 M290 280 l-8 -8 M290 270 l-10 -4\" stroke=\"#6f8a55\" stroke-width=\"2.5\" stroke-linecap=\"round\"/><g fill=\"#e07aa0\" stroke=\"'+K+'\" stroke-width=\"2\"><circle cx=\"252\" cy=\"268\" r=\"4\"/><circle cx=\"263\" cy=\"254\" r=\"4.5\"/><circle cx=\"278\" cy=\"252\" r=\"4\"/><circle cx=\"289\" cy=\"266\" r=\"4.5\"/><circle cx=\"270\" cy=\"249\" r=\"3.5\"/></g>');\n if(!omit.hedge)s+=g('hedge','<g fill=\"#5f7a4a\" stroke=\"'+K+'\" stroke-width=\"3.5\"><path d=\"M14 346 L30 300 L46 346 Z\"/><ellipse cx=\"60\" cy=\"330\" rx=\"16\" ry=\"20\"/><path d=\"M70 346 L86 304 L102 346 Z\"/></g><path d=\"M30 320 q6 -2 8 4 M60 322 q6 0 8 6\" fill=\"none\" stroke=\"#7f9a6a\" stroke-width=\"2.5\" stroke-linecap=\"round\"/>');\n if(!omit.pond)s+=g('pond','<ellipse cx=\"300\" cy=\"396\" rx=\"46\" ry=\"17\" fill=\"#6f8a55\" stroke=\"'+K+'\" stroke-width=\"3.5\"/><ellipse cx=\"300\" cy=\"394\" rx=\"40\" ry=\"13\" fill=\"#9ec6e0\" stroke=\"'+K+'\" stroke-width=\"2.5\"/><path d=\"M268 392 q6 -3 10 0 M318 398 q6 -3 10 0\" fill=\"none\" stroke=\"#fff\" stroke-width=\"2\" stroke-linecap=\"round\"/><path d=\"M262 384 v-18 M268 386 v-14 M336 386 v-16\" stroke=\"#5a7042\" stroke-width=\"3\" stroke-linecap=\"round\"/><ellipse cx=\"300\" cy=\"392\" rx=\"7\" ry=\"3\" fill=\"#7fb07a\" stroke=\"'+K+'\" stroke-width=\"1.5\"/><circle cx=\"297\" cy=\"389\" r=\"2.5\" fill=\"#f08aa0\"/>');\n if(lostInfo){\n  SPOTS.forEach(sp=>{const by=lostInfo.searched[sp.id];\n   if(by)s+='<g transform=\"translate('+sp.x+','+(sp.y-30)+')\"><path d=\"M-7 -7 l14 14 M7 -7 l-14 14\" stroke=\"#8a2f3b\" stroke-width=\"5\" stroke-linecap=\"round\"/><text x=\"0\" y=\"24\" text-anchor=\"middle\" font-size=\"9\" font-weight=\"800\" fill=\"'+K+'\" font-family=\"Baloo 2,Fredoka,ui-rounded,system-ui,sans-serif\" stroke=\"#f6ead2\" stroke-width=\"3\" paint-order=\"stroke\">'+esc(by.length>12?by.slice(0,11)+'\\u2026':by)+'</text></g>';\n   else s+='<g transform=\"translate('+sp.x+','+(sp.y-34)+')\" pointer-events=\"none\"><g class=\"q\" style=\"animation-delay:'+((sp.x%7)*-.2)+'s\"><circle r=\"12\" fill=\"#f2c75c\" stroke=\"'+K+'\" stroke-width=\"3\"/><text y=\"6\" text-anchor=\"middle\" font-size=\"16\" font-weight=\"900\" fill=\"'+K+'\" font-family=\"Baloo 2,Fredoka,ui-rounded,system-ui,sans-serif\">?</text></g></g>';});\n }\n return s;\n}\nfunction sceneSVG(){\n const ph=phase(),sky=SKY[ph],dead=S.status==='dead',night=ph==='night';\n let s='<svg viewBox=\"0 0 360 440\" role=\"img\" aria-label=\"Hawthorne House and its lawn, with Downton Dog\" id=\"svg\"><defs><linearGradient id=\"sky\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\"><stop offset=\"0\" stop-color=\"'+sky[0]+'\"/><stop offset=\"1\" stop-color=\"'+sky[1]+'\"/></linearGradient></defs>';\n s+='<rect width=\"360\" height=\"440\" fill=\"url(#sky)\"/>';\n if(night){[[30,30],[90,70],[150,24],[230,52],[300,30],[340,80],[60,110],[270,100]].forEach((p,i)=>{s+='<circle class=\"tw\" style=\"animation-delay:'+(i*-.4)+'s\" cx=\"'+p[0]+'\" cy=\"'+p[1]+'\" r=\"1.8\" fill=\"#fff\"/>';});s+='<circle cx=\"300\" cy=\"56\" r=\"18\" fill=\"#f6ead2\" stroke=\"'+K+'\" stroke-width=\"3\"/><circle cx=\"308\" cy=\"50\" r=\"15\" fill=\"'+sky[0]+'\"/>';}\n else if(ph==='dawn'||ph==='dusk')s+='<circle cx=\"'+(ph==='dawn'?70:290)+'\" cy=\"150\" r=\"26\" fill=\"#f6b26a\" stroke=\"'+K+'\" stroke-width=\"3\"/>';\n else s+='<circle cx=\"300\" cy=\"60\" r=\"20\" fill=\"#ffe08a\" stroke=\"'+K+'\" stroke-width=\"3\"/>';\n if(ph==='day'||ph==='dawn')s+='<g fill=\"#fff\" stroke=\"'+K+'\" stroke-width=\"2.5\" opacity=\".9\"><path d=\"M20 60 q-4 -16 12 -14 q6 -14 20 -6 q16 -6 20 10 q10 4 2 10 z\"/><path d=\"M200 44 q-2 -12 10 -10 q6 -10 16 -4 q12 -4 14 8 q6 4 0 6 z\"/></g>';\n s+='<rect x=\"0\" y=\"200\" width=\"360\" height=\"240\" fill=\"'+(night?'#4e6a44':'#9dc57a')+'\"/>';\n s+=house(ph,dead);\n s+='<path d=\"M166 220 L194 220 L224 440 L136 440 Z\" fill=\"#e6d3a8\" stroke=\"'+K+'\" stroke-width=\"3\"/>';\n const lost=isLost(S)&&mode!=='found';\n if(mode==='herd'){s+=spotsArt(ph,null,{fountain:1,arbour:1,hedge:1,pond:1})+herdStatic(night);}\n else s+=spotsArt(ph,lost?{searched:S.searched.spots}:null,mode==='battle'?{fountain:1,arbour:1,hedge:1,pond:1}:null);\n if(dead){s+='<g transform=\"translate(196,122) scale(.62)\"><g class=\"bob\">'+dogSVG({stage:'adult',mood:'calm',ghost:true})+'</g></g>';\n  const y=S.finalYears||years(S);s+=card('Found '+S.finds+' times, '+S.wins+' scraps won, '+(S.herding.sheep||0)+' sheep gathered. He lay across the door every night. Mr Hawthorne has not come down to breakfast.',S.name+', '+y+' years',false);}\n else if(mode==='found'&&foundAt){s+='<g transform=\"translate('+foundAt.x+','+(foundAt.y+16)+') scale(.75)\"><g class=\"pop\">'+dogSVG({stage:stage(S),mood:'happy'})+'</g></g>'+bubble('There you are!',foundAt.x-50,foundAt.y-100,120,14);}\n else if(mode==='battle'&&B){\n  s+='<g id=\"btele\"></g><g id=\"bball\"></g><g id=\"ben\"></g><g id=\"bdog\"></g><g id=\"bfx\"></g><g id=\"bbars\"></g>';\n }\n else if(mode==='herd'){}\n else if(lost){s+=card('He slipped out before breakfast, as he does. Tap the grounds to look. A wrong guess still tells you which way he barked.','missing again',false,true);}\n else{\n  const slp=night&&!faceOverride;\n  const m=faceOverride||(S.mood<30?'sad':slp?'sleep':'happy');\n  const ss=storyState();\n  const spk=ss.who?ENEMIES[ss.who]:(ss.beat&&ss.beat.who&&SPEAKERS[ss.beat.who]);\n  if(spk)s+='<g transform=\"translate(262,342) scale(-.78 .78)\">'+enemyArt(spk)+'</g>';\n  s+='<g transform=\"translate(118,352) scale(.9)\"><g class=\"breathe\">'+dogSVG({stage:stage(S),mood:m})+'</g></g>';\n  if(slp)s+='<g transform=\"translate(150,266)\"><text class=\"zz\" font-size=\"16\" font-weight=\"900\" fill=\"'+K+'\" font-family=\"Baloo 2,Fredoka,ui-rounded,system-ui,sans-serif\">z</text><text class=\"zz\" style=\"animation-delay:-1.2s\" font-size=\"12\" font-weight=\"900\" fill=\"'+K+'\" font-family=\"Baloo 2,Fredoka,ui-rounded,system-ui,sans-serif\">z</text></g>';\n  if(bubbleText&&Date.now()<bubbleUntil)s+=bubble(bubbleText,34,196,0,14);\n  s+=card(ss.text,ss.title,!ss.dis);\n }\n s+='</svg>';\n return s;\n}\n\n/* ---------- herding ---------- */\nconst PEN={x:262,y:300,w:92,h:120,gapTop:336,gapBot:384};\nfunction herdStatic(night){\n const st='#cfbb97';\n let s='<rect x=\"'+(PEN.x+6)+'\" y=\"'+(PEN.y+6)+'\" width=\"'+(PEN.w-12)+'\" height=\"'+(PEN.h-12)+'\" fill=\"'+(night?'#5e7050':'#b8cf8e')+'\"/>';\n const wall=(x1,y1,x2,y2)=>'<path d=\"M'+x1+' '+y1+' L'+x2+' '+y2+'\" stroke=\"'+K+'\" stroke-width=\"12\" stroke-linecap=\"round\"/><path d=\"M'+x1+' '+y1+' L'+x2+' '+y2+'\" stroke=\"'+st+'\" stroke-width=\"7\" stroke-linecap=\"round\" stroke-dasharray=\"10 3\"/>';\n s+=wall(PEN.x,PEN.y,PEN.x+PEN.w,PEN.y)+wall(PEN.x+PEN.w,PEN.y,PEN.x+PEN.w,PEN.y+PEN.h)+wall(PEN.x,PEN.y+PEN.h,PEN.x+PEN.w,PEN.y+PEN.h)+wall(PEN.x,PEN.y,PEN.x,PEN.gapTop)+wall(PEN.x,PEN.gapBot,PEN.x,PEN.y+PEN.h);\n s+='<text x=\"'+(PEN.x+PEN.w/2)+'\" y=\"'+(PEN.y+PEN.h/2+5)+'\" text-anchor=\"middle\" font-size=\"12\" font-weight=\"800\" fill=\"'+K+'\" opacity=\".5\" font-family=\"Baloo 2,Fredoka,ui-rounded,system-ui,sans-serif\">the fold</text>';\n s+='<g id=\"hgate\" opacity=\"0\"><path d=\"M'+PEN.x+' '+PEN.gapTop+' L'+PEN.x+' '+PEN.gapBot+'\" stroke=\"'+K+'\" stroke-width=\"10\" stroke-linecap=\"round\"/><path d=\"M'+PEN.x+' '+PEN.gapTop+' L'+PEN.x+' '+PEN.gapBot+'\" stroke=\"#8a6a46\" stroke-width=\"5\" stroke-linecap=\"round\" stroke-dasharray=\"4 4\"/></g>';\n s+='<g id=\"flock\"></g><g id=\"hdog\"></g><g id=\"hfx\"></g><g id=\"bfx\"></g>';\n return s;\n}\nfunction inPen(p){return p.x>PEN.x+6&&p.x<PEN.x+PEN.w-6&&p.y>PEN.y+6&&p.y<PEN.y+PEN.h-6;}\nfunction hitsWall(x,y){const r=7;const nearX=Math.abs(x-PEN.x)<r,nearRX=Math.abs(x-(PEN.x+PEN.w))<r,inY=y>PEN.y-r&&y<PEN.y+PEN.h+r,inX=x>PEN.x-r&&x<PEN.x+PEN.w+r;\n if(nearRX&&inY)return true;if(inX&&(Math.abs(y-PEN.y)<r||Math.abs(y-(PEN.y+PEN.h))<r))return true;\n if(nearX&&inY&&!(y>PEN.gapTop&&y<PEN.gapBot))return true;return false;}\nfunction startHerd(){\n mode='herd';B=null;\n H={sheep:[],dog:{x:150,y:400,tx:150,ty:400},t:40,last:performance.now(),done:false,raf:0,bark:0,rings:[]};\n const PERS=[{sp:85,fl:85},{sp:90,fl:90},{sp:80,fl:80},{sp:95,fl:95},{sp:88,fl:88},{sp:120,fl:115}];\n for(let i=0;i<6;i++)H.sheep.push({x:40+Math.random()*170,y:250+Math.random()*160,vx:0,vy:0,name:SHEEP_NAMES[i],id:i,sp:PERS[i].sp,fl:PERS[i].fl,inside:false});\n $('art').innerHTML=sceneSVG();hud();renderPanel();renderFeed();\n const svg=$('svg');$('stage').classList.add('herd');\n const toSvg=e=>{const pt=svg.createSVGPoint();pt.x=e.clientX;pt.y=e.clientY;return pt.matrixTransform(svg.getScreenCTM().inverse());};\n const mv=p=>{if(!H||H.done)return;H.dog.tx=clamp(p.x,10,350);H.dog.ty=clamp(p.y,232,430);};\n let down=null;\n svg.addEventListener('pointerdown',e=>{svg.setPointerCapture(e.pointerId);const p=toSvg(e);down={x:p.x,y:p.y,t:performance.now()};mv(p);});\n svg.addEventListener('pointermove',e=>{if(e.pressure>0||e.pointerType==='mouse'&&e.buttons)mv(toSvg(e));});\n svg.addEventListener('pointerup',e=>{if(!H||H.done||!down)return;const p=toSvg(e);const tap=performance.now()-down.t<260&&Math.hypot(p.x-down.x,p.y-down.y)<14;down=null;if(tap)bark();});\n const flock=$('flock'),hd=$('hdog');\n flock.innerHTML=H.sheep.map(s=>'<g id=\"sh'+s.id+'\"><g>'+sheepSVG()+'</g></g>').join('');\n hd.innerHTML='<g id=\"hdg\">'+dogSVG({stage:stage(S),mood:'fierce'})+'</g>';\n setTimeout(()=>{if(H&&!H.done)floatText(H.dog.x,H.dog.y-90,'drag me','#6a5a72');},300);\n setTimeout(()=>{if(H&&!H.done)floatText(H.dog.x,H.dog.y-90,'tap to bark','#6a5a72');},1700);\n setTimeout(()=>{if(H&&!H.done){const sh=H.sheep[5];floatText(sh.x,sh.y-50,'baa','#6a5a72');}},2600);\n H.raf=requestAnimationFrame(herdStep);\n}\nfunction bark(){\n if(!H||H.done||H.bark>0)return;H.bark=1;const d=H.dog;\n H.rings.push({x:d.x,y:d.y-30,t:0});floatText(d.x,d.y-92,'WOOF','#8a2f3b');\n H.sheep.forEach(s=>{const fx=s.x-d.x,fy=s.y-d.y,fd=Math.hypot(fx,fy)||1;if(fd<130){const k=(130-fd)/130*300+120;s.vx+=fx/fd*k;s.vy+=fy/fd*k;}});\n}\nfunction herdStep(now){\n if(!H||mode!=='herd')return;\n const dt=Math.min(.05,(now-H.last)/1000);H.last=now;\n if(!H.done){H.t-=dt;if(H.t<=0){H.t=0;endHerd();return;}}\n H.bark=Math.max(0,H.bark-dt);\n const d=H.dog,sp=175;const ddx=d.tx-d.x,ddy=d.ty-d.y,dl=Math.hypot(ddx,ddy);\n if(dl>2){const m=Math.min(dl,sp*dt);d.x+=ddx/dl*m;d.y+=ddy/dl*m;d.face=ddx<0?-1:1;}\n const cx=H.sheep.reduce((a,s)=>a+s.x,0)/6,cy=H.sheep.reduce((a,s)=>a+s.y,0)/6;\n let n=0;\n H.sheep.forEach(s=>{\n  const inside=inPen(s);\n  let ax=(Math.random()-.5)*(inside?20:60),ay=(Math.random()-.5)*(inside?20:60);\n  const fx=s.x-d.x,fy=s.y-d.y,fd=Math.hypot(fx,fy)||1;\n  if(fd<s.fl){const k=(s.fl-fd)/s.fl*(inside?260:520);ax+=fx/fd*k;ay+=fy/fd*k;}\n  if(!inside){ax+=(cx-s.x)*.25;ay+=(cy-s.y)*.25;}\n  H.sheep.forEach(o=>{if(o!==s){const ox=s.x-o.x,oy=s.y-o.y,od=Math.hypot(ox,oy)||1;if(od<22){ax+=ox/od*90;ay+=oy/od*90;}}});\n  s.vx=(s.vx+ax*dt)*.93;s.vy=(s.vy+ay*dt)*.93;\n  const v=Math.hypot(s.vx,s.vy);if(v>s.sp){s.vx*=s.sp/v;s.vy*=s.sp/v;}\n  let nx=clamp(s.x+s.vx*dt,14,346),ny=clamp(s.y+s.vy*dt,236,428);\n  if(hitsWall(nx,s.y)){nx=s.x;s.vx*=-.4;}if(hitsWall(nx,ny)){ny=s.y;s.vy*=-.4;}\n  s.x=nx;s.y=ny;\n  const nowIn=inPen(s);if(nowIn&&!s.inside)floatText(s.x,s.y-48,s.name+'!','#6f8a55');s.inside=nowIn;if(nowIn)n++;\n  const el=$('sh'+s.id);if(el){el.setAttribute('transform','translate('+s.x.toFixed(1)+','+s.y.toFixed(1)+') scale('+(s.vx<-3?-.7:.7)+' .7)');}\n });\n $('hdog').setAttribute('transform','translate('+d.x.toFixed(1)+','+d.y.toFixed(1)+') scale('+((d.face||1)*.8)+' .8)');\n H.rings=H.rings.filter(r=>(r.t+=dt)<.5);\n $('hfx').innerHTML=H.rings.map(r=>'<circle cx=\"'+r.x+'\" cy=\"'+r.y+'\" r=\"'+(20+r.t*220)+'\" fill=\"none\" stroke=\"'+K+'\" stroke-width=\"'+(4-r.t*7)+'\" opacity=\"'+(1-r.t*2)+'\" stroke-dasharray=\"8 6\"/>').join('');\n const gate=$('hgate');if(gate)gate.setAttribute('opacity',n===6?'1':'0');\n H.pen=n;\n const t=$('hht');if(t)t.textContent=Math.ceil(H.t)+'s';const c=$('hhc');if(c)c.textContent=n+' / 6';\n if(n===6&&!H.done){H.done=true;setTimeout(endHerd,700);return;}\n H.raf=requestAnimationFrame(herdStep);\n}\nasync function endHerd(){\n if(!H)return;const n=H.sheep.filter(inPen).length,names=H.sheep.filter(inPen).map(s=>s.name),left=Math.ceil(H.t);\n H.done=true;cancelAnimationFrame(H.raf);$('stage').classList.remove('herd');mode='home';H=null;\n const bonus=n===6?10+Math.floor(left/3):0;\n await mut(s=>{s.herding.sheep=(s.herding.sheep||0)+n;s.herding.runs=(s.herding.runs||0)+1;s.xp+=n*4+2+bonus;s.care=(s.care||0)+1;s.obedience=clamp((s.obedience||0)+1,0,100);if(n>0)feed(s,'herded '+n+' sheep into the fold'+(n===6?' with '+left+'s to spare.':' ('+names.join(', ')+').'));else feed(s,'tried to herd sheep. The sheep won.');});\n toast(n===6?'The Laird: \\u201cThat\\u2019ll do, dog.\\u201d +'+bonus+' bonus':n>=4?'The Laird: \\u201cNearly a shepherd.\\u201d':n>0?'The Laird: \\u201cThe rest are discussing it.\\u201d':'The Laird: \\u201cMorag is laughing at you.\\u201d');\n faceOverride=n>=4?'happy':'sad';faceUntil=Date.now()+3000;render(true);\n}\n\n/* ---------- battle: real-time on the lawn ---------- */\nconst AR={x0:26,x1:334,y0:244,y1:426};\nconst MOVESETS={\n prebble:{spd:52,keep:94,pats:[{k:'arc',tele:.85,r:74,ang:110,mult:1,cd:1.4,i:0},{k:'swat',tele:.5,r:46,mult:.8,cd:1,i:2}]},\n fluff:{spd:72,keep:86,pats:[{k:'swat',tele:.45,r:48,mult:.9,cd:.9,i:0},{k:'charge',tele:.6,len:120,mult:1,cd:1.4,i:1}]},\n ashcombe:{spd:48,keep:98,pats:[{k:'arc',tele:.7,r:78,ang:120,mult:1,cd:1.2,i:0},{k:'throw',tele:.9,r:36,mult:1.2,cd:1.6,i:2}]},\n nigel:{spd:115,keep:80,pats:[{k:'charge',tele:.45,len:150,mult:.8,cd:.8,i:2},{k:'swat',tele:.35,r:40,mult:.7,cd:.7,i:1}]},\n ned:{spd:42,keep:136,pats:[{k:'throw',tele:1,r:40,mult:1.2,cd:1.5,i:0},{k:'arc',tele:.9,r:82,ang:140,mult:1,cd:1.6,i:2}]},\n quill:{spd:36,keep:156,pats:[{k:'fan',tele:.9,r:32,n:3,mult:.7,cd:1.6,i:1},{k:'ring',tele:.9,R:86,w:26,mult:1,cd:1.8,i:0}]},\n bartholomew:{spd:60,keep:92,pats:[{k:'arc',tele:.7,r:82,ang:100,mult:1.4,cd:1.3,rear:1,i:0},{k:'arc',tele:.5,r:62,ang:80,mult:.8,cd:1,i:1}]},\n hamish:{spd:55,keep:164,pats:[{k:'charge',tele:.8,len:220,mult:1.3,cd:1.6,i:0},{k:'ring',tele:.7,R:72,w:32,mult:.8,cd:1.4,i:1}]},\n goose:{spd:88,keep:106,pats:[{k:'charge',tele:.5,len:160,mult:1,cd:1,i:2},{k:'arc',tele:.5,r:66,ang:150,mult:.8,cd:1,i:1}]},\n grimsby:{spd:45,keep:144,pats:[{k:'fan',tele:.9,r:34,n:3,mult:.8,cd:1.5,i:0},{k:'arc',tele:.7,r:72,ang:110,mult:1.1,cd:1.2,i:2}]},\n bismarck:{spd:78,keep:88,pats:[{k:'charge',tele:.55,len:140,mult:1,cd:1.1,i:1},{k:'swat',tele:.4,r:50,mult:.9,cd:.9,i:2}]},\n aldous:{spd:56,keep:106,pats:[{k:'arc',tele:.7,r:92,ang:130,mult:1.2,cd:1.1,i:0},{k:'ring',tele:.8,R:96,w:28,mult:1,cd:1.5,i:1},{k:'throw',tele:.8,r:42,mult:1.3,cd:1.3,i:2}]}\n};\nconst dist=(a,b)=>Math.hypot(a.x-b.x,a.y-b.y);\nfunction startBattle(id,ch,b){\n const e=Object.assign({},ENEMIES[id]);const loop=S.story.loop||0;e.id=id;e.maxhp=Math.round(e.hp*(1+loop*.5));e.atk=e.atk*(1+loop*.3);e.ms=MOVESETS[id];\n B={e,ehp:e.maxhp,dhp:maxHp(S),over:null,ch,b,\n  dog:{x:110,y:392,tx:110,ty:392,face:1,cd:0,inv:0,dash:0,lunge:0},\n  en:{x:262,y:392,face:-1,state:'idle',t:1.2,pat:null,shape:null,stun:0,charm:0,vx:0,vy:0,hitDone:false,flash:0},\n  ball:{x:180,y:300,vx:0,vy:0},eyes:0,zoomCd:0,last:performance.now(),raf:0,say:'',sayT:0,dodgeArmed:false};\n mode='battle';\n $('art').innerHTML=sceneSVG();hud();renderPanel();renderFeed();\n const svg=$('svg');$('stage').classList.add('fight');\n const toSvg=ev=>{const pt=svg.createSVGPoint();pt.x=ev.clientX;pt.y=ev.clientY;return pt.matrixTransform(svg.getScreenCTM().inverse());};\n let down=null;\n svg.addEventListener('pointerdown',ev=>{svg.setPointerCapture(ev.pointerId);const p=toSvg(ev);down={x:p.x,y:p.y,t:performance.now()};if(B&&!B.over){B.dog.tx=clamp(p.x,AR.x0,AR.x1);B.dog.ty=clamp(p.y,AR.y0,AR.y1);}});\n svg.addEventListener('pointermove',ev=>{if(!B||B.over)return;if(ev.pressure>0||ev.pointerType==='mouse'&&ev.buttons){const p=toSvg(ev);B.dog.tx=clamp(p.x,AR.x0,AR.x1);B.dog.ty=clamp(p.y,AR.y0,AR.y1);}});\n svg.addEventListener('pointerup',ev=>{if(!B||B.over||!down)return;const p=toSvg(ev);const tap=performance.now()-down.t<260&&Math.hypot(p.x-down.x,p.y-down.y)<14;down=null;\n  if(tap){const near=Math.hypot(p.x-B.en.x,p.y-(B.en.y-40))<70||dist(p,B.en)<60;if(near){B.dog.tx=B.en.x;B.dog.ty=B.en.y;bite();}}});\n drawBattleSprites();\n setTimeout(()=>{if(B&&!B.over)floatText(B.dog.x,B.dog.y-92,'drag me','#6a5a72');},300);\n setTimeout(()=>{if(B&&!B.over)floatText(B.en.x,B.en.y-124,'tap to bite','#6a5a72');},1500);\n setTimeout(()=>{if(B&&!B.over)floatText(B.dog.x,B.dog.y-92,'stay out of the red','#8a2f3b');},2700);\n B.raf=requestAnimationFrame(battleStep);\n}\nfunction drawBattleSprites(){\n $('bdog').innerHTML='<g id=\"bdogs\">'+dogSVG({stage:stage(S),mood:'fierce'})+'</g>';\n $('ben').innerHTML='<g id=\"bens\">'+enemyArt(B.e)+'</g>';\n $('bball').innerHTML='<circle r=\"9\" fill=\"#e7a7b4\" stroke=\"'+K+'\" stroke-width=\"3\"/><path d=\"M-9 0 H9 M0 -9 V9\" stroke=\"'+K+'\" stroke-width=\"1.5\" opacity=\".6\"/>';\n}\nfunction floatText(x,y,t,col){const g=document.createElementNS('http://www.w3.org/2000/svg','g');g.setAttribute('transform','translate('+x.toFixed(0)+','+y.toFixed(0)+')');g.innerHTML='<text class=\"fl\" text-anchor=\"middle\" font-size=\"15\" font-weight=\"900\" fill=\"'+(col||K)+'\" stroke=\"#f6ead2\" stroke-width=\"3\" paint-order=\"stroke\" font-family=\"Baloo 2,Fredoka,ui-rounded,system-ui,sans-serif\">'+esc(t)+'</text>';$('bfx').appendChild(g);setTimeout(()=>g.remove(),950);}\nfunction shakeStage(){const st=$('stage');st.classList.remove('shake');void st.offsetWidth;st.classList.add('shake');}\nfunction enemySay(t,secs){B.say=t;B.sayT=secs||1.2;}\nfunction makeShape(pat){\n const en=B.en,dg=B.dog;let a=Math.atan2(dg.y-en.y,dg.x-en.x);\n if(pat.k==='arc'){if(pat.rear)a+=Math.PI;return{k:'arc',cx:en.x,cy:en.y-10,dir:a,r:pat.r,ang:pat.ang*Math.PI/180};}\n if(pat.k==='swat')return{k:'swat',cx:en.x,cy:en.y-10,r:pat.r};\n if(pat.k==='charge')return{k:'charge',x0:en.x,y0:en.y,dx:Math.cos(a),dy:Math.sin(a),len:pat.len};\n if(pat.k==='throw')return{k:'throw',pts:[{x:dg.x,y:dg.y}],r:pat.r};\n if(pat.k==='fan'){const d=Math.max(60,dist(en,dg));const pts=[-.45,0,.45].map(o=>({x:clamp(en.x+Math.cos(a+o)*d,AR.x0,AR.x1),y:clamp(en.y+Math.sin(a+o)*d,AR.y0,AR.y1)}));return{k:'throw',pts,r:pat.r};}\n if(pat.k==='ring')return{k:'ring',cx:en.x,cy:en.y-10,R:pat.R,w:pat.w};\n}\nfunction shapeHits(sh,p){\n if(sh.k==='arc'){const d=Math.hypot(p.x-sh.cx,p.y-sh.cy);if(d>sh.r)return false;let da=Math.atan2(p.y-sh.cy,p.x-sh.cx)-sh.dir;da=Math.atan2(Math.sin(da),Math.cos(da));return Math.abs(da)<sh.ang/2;}\n if(sh.k==='swat')return Math.hypot(p.x-sh.cx,p.y-sh.cy)<sh.r;\n if(sh.k==='throw')return sh.pts.some(q=>Math.hypot(p.x-q.x,p.y-q.y)<sh.r);\n if(sh.k==='ring')return Math.abs(Math.hypot(p.x-sh.cx,p.y-sh.cy)-sh.R)<sh.w/2;\n return false;\n}\nfunction shapeSVG(sh,prog,flash){\n const fill=flash?'#fff':'#8a2f3b',op=flash?.75:.26+prog*.36,st='stroke=\"'+K+'\" stroke-width=\"2\" stroke-dasharray=\"6 5\"';\n if(sh.k==='arc'){const a0=sh.dir-sh.ang/2,a1=sh.dir+sh.ang/2,x0=sh.cx+Math.cos(a0)*sh.r,y0=sh.cy+Math.sin(a0)*sh.r,x1=sh.cx+Math.cos(a1)*sh.r,y1=sh.cy+Math.sin(a1)*sh.r;return '<path d=\"M'+sh.cx+' '+sh.cy+' L'+x0+' '+y0+' A'+sh.r+' '+sh.r+' 0 '+(sh.ang>Math.PI?1:0)+' 1 '+x1+' '+y1+' Z\" fill=\"'+fill+'\" opacity=\"'+op+'\" '+st+'/>';}\n if(sh.k==='swat')return '<circle cx=\"'+sh.cx+'\" cy=\"'+sh.cy+'\" r=\"'+sh.r+'\" fill=\"'+fill+'\" opacity=\"'+op+'\" '+st+'/>';\n if(sh.k==='charge'){const ang=Math.atan2(sh.dy,sh.dx)*180/Math.PI;return '<g transform=\"translate('+sh.x0+','+sh.y0+') rotate('+ang+')\"><rect x=\"0\" y=\"-16\" width=\"'+sh.len+'\" height=\"32\" rx=\"16\" fill=\"'+fill+'\" opacity=\"'+op+'\" '+st+'/><path d=\"M'+(sh.len-18)+' -10 L'+(sh.len-4)+' 0 L'+(sh.len-18)+' 10\" fill=\"none\" stroke=\"'+K+'\" stroke-width=\"2.5\"/></g>';}\n if(sh.k==='throw')return sh.pts.map(q=>'<circle cx=\"'+q.x+'\" cy=\"'+q.y+'\" r=\"'+sh.r+'\" fill=\"'+fill+'\" opacity=\"'+op+'\" '+st+'/>'+(flash?'':'<circle cx=\"'+q.x+'\" cy=\"'+(q.y-120+prog*120)+'\" r=\"6\" fill=\"#5a3a22\" stroke=\"'+K+'\" stroke-width=\"2\"/>')).join('');\n if(sh.k==='ring')return '<path d=\"M'+(sh.cx+sh.R+sh.w/2)+' '+sh.cy+' A'+(sh.R+sh.w/2)+' '+(sh.R+sh.w/2)+' 0 1 0 '+(sh.cx-sh.R-sh.w/2)+' '+sh.cy+' A'+(sh.R+sh.w/2)+' '+(sh.R+sh.w/2)+' 0 1 0 '+(sh.cx+sh.R+sh.w/2)+' '+sh.cy+' M'+(sh.cx+sh.R-sh.w/2)+' '+sh.cy+' A'+(sh.R-sh.w/2)+' '+(sh.R-sh.w/2)+' 0 1 0 '+(sh.cx-sh.R+sh.w/2)+' '+sh.cy+' A'+(sh.R-sh.w/2)+' '+(sh.R-sh.w/2)+' 0 1 0 '+(sh.cx+sh.R-sh.w/2)+' '+sh.cy+'\" fill-rule=\"evenodd\" fill=\"'+fill+'\" opacity=\"'+op+'\" '+st+'/>';\n return '';\n}\nfunction hurtDog(mult,from){\n const d=Math.max(1,Math.round(B.e.atk*mult+rnd(-1,1)));B.dhp-=d;B.dog.inv=.7;\n const ax=B.dog.x-from.x,ay=B.dog.y-from.y,al=Math.hypot(ax,ay)||1;\n B.dog.x=clamp(B.dog.x+ax/al*26,AR.x0,AR.x1);B.dog.y=clamp(B.dog.y+ay/al*26,AR.y0,AR.y1);B.dog.tx=B.dog.x;B.dog.ty=B.dog.y;\n floatText(B.dog.x,B.dog.y-90,'-'+d,'#8a2f3b');shakeStage();\n if(B.dhp<=0){B.dhp=0;endBattle('lose');}\n}\nfunction hurtEnemy(d,label,col){\n B.ehp-=d;B.en.flash=.25;floatText(B.en.x,B.en.y-110,label||('-'+d),col||'#2b2233');\n if(B.ehp<=0){B.ehp=0;endBattle('win');}\n}\nfunction bite(){\n if(!B||B.over||B.dog.cd>0)return;\n const dg=B.dog,en=B.en;\n if(dist(dg,en)>98){floatText(dg.x,dg.y-90,'closer!','#6a5a72');dg.tx=en.x;dg.ty=en.y;return;}\n dg.cd=.45;dg.lunge=.18;\n let d=Math.max(1,Math.round(atk(S)+rnd(-2,2))),lab='-'+d;\n if(en.state==='recover'){d=Math.round(d*1.6);lab='-'+d+' cheeky';}else if(en.charm>0){d=Math.round(d*1.3);lab='-'+d;}\n B.eyes=Math.min(100,B.eyes+16);\n const ax=en.x-dg.x,ay=en.y-dg.y,al=Math.hypot(ax,ay)||1;en.x=clamp(en.x+ax/al*8,AR.x0,AR.x1);en.y=clamp(en.y+ay/al*8,AR.y0,AR.y1);\n hurtEnemy(d,lab);\n}\nfunction zoom(){if(!B||B.over||B.zoomCd>0)return;B.zoomCd=3;B.dog.dash=.35;B.dog.inv=.65;B.eyes=Math.min(100,B.eyes+4);floatText(B.dog.x,B.dog.y-90,'zoom!','#6f8a55');}\nfunction sadEyes(){\n if(!B||B.over||B.eyes<100)return;B.eyes=0;const en=B.en;\n if(B.ehp/B.e.maxhp<.35){floatText(en.x,en.y-110,'relents','#6f8a55');enemySay(B.e.relent,3);endBattle('win');return;}\n en.charm=2.6;en.state='idle';en.t=.4;en.shape=null;floatText(en.x,en.y-110,'\\u2665 \\u2665 \\u2665','#e07aa0');enemySay('oh, those eyes',2);\n}\nasync function biscuit(){\n if(!B||B.over||busy)return;if((S.biscuits||0)<=0){floatText(B.dog.x,B.dog.y-90,'no biscuits','#6a5a72');return;}\n busy=true;try{await mut(s=>{s.biscuits=Math.max(0,(s.biscuits||0)-1);});}finally{busy=false;}\n if(!B||B.over)return;B.dhp=Math.min(maxHp(S),B.dhp+10);floatText(B.dog.x,B.dog.y-90,'+10 biscuit','#6f8a55');\n}\nfunction battleStep(now){\n if(!B||mode!=='battle')return;\n const dt=Math.min(.05,(now-B.last)/1000);B.last=now;\n if(!B.over){\n  const dg=B.dog,en=B.en,e=B.e,ms=e.ms;\n  dg.cd=Math.max(0,dg.cd-dt);dg.inv=Math.max(0,dg.inv-dt);dg.dash=Math.max(0,dg.dash-dt);dg.lunge=Math.max(0,dg.lunge-dt);B.zoomCd=Math.max(0,B.zoomCd-dt);en.flash=Math.max(0,en.flash-dt);B.sayT=Math.max(0,B.sayT-dt);\n  const ddx=dg.tx-dg.x,ddy=dg.ty-dg.y,dl=Math.hypot(ddx,ddy),sp=dg.dash>0?430:150;\n  if(dl>2){const m=Math.min(dl,sp*dt);dg.x+=ddx/dl*m;dg.y+=ddy/dl*m;}\n  dg.face=en.x>=dg.x?1:-1;en.face=dg.x>=en.x?1:-1;\n  {const sd=dist(en,dg);if(sd<44&&en.state!=='strike'){const ax=en.x-dg.x,ay=en.y-dg.y,al=sd||1;en.x=clamp(en.x+ax/al*(44-sd),AR.x0,AR.x1);en.y=clamp(en.y+ay/al*(44-sd),AR.y0,AR.y1);}}\n  if(en.stun>0)en.stun-=dt;\n  else if(en.charm>0)en.charm-=dt;\n  else{\n   const d=dist(en,dg);\n   if(en.state==='idle'){\n    en.t-=dt;\n    if(d>ms.keep){const ax=dg.x-en.x,ay=dg.y-en.y;en.x+=ax/d*ms.spd*dt;en.y+=ay/d*ms.spd*dt;}else if(d<ms.keep*.6){const ax=en.x-dg.x,ay=en.y-dg.y;en.x+=ax/(d||1)*ms.spd*.8*dt;en.y+=ay/(d||1)*ms.spd*.8*dt;}\n    en.x=clamp(en.x,AR.x0,AR.x1);en.y=clamp(en.y,AR.y0,AR.y1);\n    if(en.t<=0&&d<Math.max(ms.keep+60,ms.pats.some(p=>p.k==='throw'||p.k==='fan'||p.k==='charge'||p.k==='ring')?280:0)){\n     en.pat=ms.pats[rnd(0,ms.pats.length-1)];en.shape=makeShape(en.pat);en.state='tele';en.t=en.pat.tele*(B.ehp<e.maxhp*.4&&e.id==='aldous'?.7:1);en.hitDone=false;enemySay(e.moves[en.pat.i],en.pat.tele+.4);\n    }\n   }else if(en.state==='tele'){\n    en.t-=dt;\n    if(en.t<=0){en.state='strike';\n     if(en.pat.k==='charge'){en.t=.3;en.vx=en.shape.dx*en.shape.len/.3;en.vy=en.shape.dy*en.shape.len/.3;}\n     else{en.t=.22;const hit=shapeHits(en.shape,dg);if(hit&&dg.inv<=0)hurtDog(en.pat.mult,{x:en.shape.cx||en.x,y:en.shape.cy||en.y});else if(!hit&&d<190){B.eyes=Math.min(100,B.eyes+10);floatText(dg.x,dg.y-90,'dodged','#6f8a55');}}\n    }\n   }else if(en.state==='strike'){\n    en.t-=dt;\n    if(en.pat.k==='charge'){en.x=clamp(en.x+en.vx*dt,AR.x0,AR.x1);en.y=clamp(en.y+en.vy*dt,AR.y0,AR.y1);\n     if(!en.hitDone&&dist(en,dg)<34){en.hitDone=true;if(dg.inv<=0)hurtDog(en.pat.mult,{x:en.x-en.vx*.05,y:en.y-en.vy*.05});}\n     if(en.t<=0&&!en.hitDone&&d<200){B.eyes=Math.min(100,B.eyes+10);floatText(dg.x,dg.y-90,'dodged','#6f8a55');}}\n    if(en.t<=0){en.state='recover';en.t=en.pat.k==='charge'?.9:.7;en.shape=null;}\n   }else if(en.state==='recover'){en.t-=dt;if(en.t<=0){en.state='idle';en.t=en.pat.cd;}}\n  }\n  // croquet ball\n  const bl=B.ball;bl.vx*=Math.exp(-1.3*dt);bl.vy*=Math.exp(-1.3*dt);bl.x+=bl.vx*dt;bl.y+=bl.vy*dt;\n  if(bl.x<AR.x0){bl.x=AR.x0;bl.vx*=-.7;}if(bl.x>AR.x1){bl.x=AR.x1;bl.vx*=-.7;}if(bl.y<AR.y0){bl.y=AR.y0;bl.vy*=-.7;}if(bl.y>AR.y1){bl.y=AR.y1;bl.vy*=-.7;}\n  const bd=dist(bl,dg);if(bd<28){const px=bl.x-dg.x,py=bl.y-dg.y,pl=bd||1;const pw=dg.dash>0?320:170;bl.vx=px/pl*pw;bl.vy=py/pl*pw;bl.x=dg.x+px/pl*29;bl.y=dg.y+py/pl*29;}\n  const bs=Math.hypot(bl.vx,bl.vy);if(bs>70&&dist(bl,en)<30&&!B.over){const d=3+Math.floor(lvl(S)/2);en.stun=Math.max(en.stun,.7);en.state='idle';en.t=Math.max(en.t,.6);en.shape=null;hurtEnemy(d,'-'+d+' croquet!','#8a2f3b');bl.vx*=-.5;bl.vy*=-.5;B.eyes=Math.min(100,B.eyes+8);}\n }\n battleDraw();\n if(!B.over)B.raf=requestAnimationFrame(battleStep);\n}\nfunction battleDraw(){\n const dg=B.dog,en=B.en;\n const ds=$('bdog'),es=$('ben');\n if(dg.y<en.y&&ds.nextSibling!==es)es.parentNode.insertBefore(ds,es);else if(dg.y>=en.y&&es.nextSibling!==ds)es.parentNode.insertBefore(es,ds);\n const lx=dg.lunge>0?dg.face*12:0;\n ds.setAttribute('transform','translate('+(dg.x+lx).toFixed(1)+','+dg.y.toFixed(1)+') scale('+(dg.face*.9)+' .9)');\n ds.setAttribute('opacity',dg.inv>0&&Math.floor(performance.now()/80)%2?'.45':'1');\n const tilt=en.stun>0?' rotate('+(Math.sin(performance.now()/60)*8)+')':'';\n es.setAttribute('transform','translate('+en.x.toFixed(1)+','+en.y.toFixed(1)+') scale('+(en.face*.85)+' .85)'+tilt);\n es.setAttribute('opacity',B.over==='win'?'0':'1');\n $('bens').style.filter=en.flash>0?'brightness(1.9) saturate(.3)':'';\n $('bball').setAttribute('transform','translate('+B.ball.x.toFixed(1)+','+B.ball.y.toFixed(1)+')');\n let t='';\n if(en.shape&&(en.state==='tele'||en.state==='strike'))t=shapeSVG(en.shape,en.state==='tele'?1-en.t/en.pat.tele:1,en.state==='strike');\n $('btele').innerHTML=t;\n let o='';\n if(en.stun>0)o+='<text x=\"'+en.x+'\" y=\"'+(en.y-120)+'\" text-anchor=\"middle\" font-size=\"18\" fill=\"#f2c75c\" stroke=\"'+K+'\" stroke-width=\"1.5\">\\u2726 \\u2726</text>';\n if(en.charm>0)o+='<text x=\"'+en.x+'\" y=\"'+(en.y-120)+'\" text-anchor=\"middle\" font-size=\"18\" fill=\"#e07aa0\" stroke=\"'+K+'\" stroke-width=\"1.2\">\\u2665</text>';\n const barw=56;o+='<g transform=\"translate('+(en.x-barw/2)+','+(en.y-104)+')\"><rect width=\"'+barw+'\" height=\"7\" rx=\"3.5\" fill=\"#fff\" stroke=\"'+K+'\" stroke-width=\"2\"/><rect width=\"'+(barw*B.ehp/B.e.maxhp)+'\" height=\"7\" rx=\"3.5\" fill=\"#8a2f3b\"/></g>';\n o+='<g transform=\"translate('+(dg.x-barw/2)+','+(dg.y-100)+')\"><rect width=\"'+barw+'\" height=\"7\" rx=\"3.5\" fill=\"#fff\" stroke=\"'+K+'\" stroke-width=\"2\"/><rect width=\"'+(barw*B.dhp/maxHp(S))+'\" height=\"7\" rx=\"3.5\" fill=\"#6f8a55\"/></g>';\n if(B.say&&B.sayT>0){const fs=B.say.length>34?10.5:13,bw=Math.min(336,B.say.length*fs*.62+28);o+=bubble(B.say,clamp(en.x-bw/2,4,356-bw),en.y-190,bw,fs);}\n $('bbars').innerHTML=o;\n const bd=$('bdh'),be=$('beh'),by=$('beye');\n if(bd){bd.style.width=clamp(B.dhp/maxHp(S)*100,0,100)+'%';$('bdt').textContent=Math.round(B.dhp)+' / '+maxHp(S);}\n if(be){be.style.width=clamp(B.ehp/B.e.maxhp*100,0,100)+'%';$('bet').textContent=Math.round(B.ehp)+' / '+B.e.maxhp;}\n if(by){by.style.width=B.eyes+'%';}\n const bz=$('bzoom');if(bz){bz.querySelector('i').style.width=(B.zoomCd/3*100)+'%';}\n const bb=$('bbite');if(bb){bb.querySelector('i').style.width=(dg.cd/.45*100)+'%';}\n const bey=$('beyes');if(bey){bey.disabled=B.eyes<100;}\n}\nasync function endBattle(res){\n if(!B||B.over)return;B.over=res;const e=B.e,ch=B.ch,b=B.b;\n $('stage').classList.remove('fight');\n $('bdogs').innerHTML=dogSVG({stage:stage(S),mood:res==='win'?'happy':'sad'});\n if(res==='win'){enemySay(e.n.split(',')[0]+' '+e.relent,3.5);B.en.shape=null;}else enemySay('',0);\n battleDraw();\n if(res==='win'){\n  const gain=Math.round(e.xp*(1+(S.story.loop||0)*.5));\n  await mut(s=>{s.wins++;s.xp+=gain;s.mood=clamp(s.mood+4,0,100);s.care=(s.care||0)+1;if(s.story.ch===ch&&s.story.b===b)s.story.b++;feed(s,'saw off '+e.n+'.');});\n  toast('+'+gain+' experience. The story moves on.');\n }else{\n  await mut(s=>{s.losses++;s.mood=clamp(s.mood-8,0,100);feed(s,'lost a scrap with '+e.n.split(',')[0]+'.');});\n  toast('You retreat under the hall table to think about it.');\n }\n render(true);\n}\n\n/* ---------- actions ---------- */\nasync function searchSpot(id){\n if(busy||!S||!isLost(S))return;busy=true;\n try{\n  const sp=SPOTS.find(x=>x.id===id);const hid=SPOTS[hideIdx(today(),S.gen)];\n  if(sp.id===hid.id){\n   foundAt={x:sp.x,y:sp.y};mode='found';foundUntil=Date.now()+2200;\n   await mut(s=>{if(s.found.day===today())return;s.found={day:today(),by:who(),spot:sp.id,at:Date.now()};s.finds++;s.mood=clamp(s.mood+6,0,100);s.care=(s.care||0)+3;s.xp+=10;feed(s,'found him at '+sp.n+'.');});\n   toast('Found, at '+sp.n+'. The day can begin.');\n   setTimeout(()=>{if(mode==='found'){mode='home';render(true);}},2200);\n  }else{\n   const dir=hid.x>sp.x?'east':hid.x<sp.x?'west':'somewhere close';\n   await mut(s=>{if(s.searched.day!==today())s.searched={day:today(),spots:{}};if(!s.searched.spots[sp.id])s.searched.spots[sp.id]=who();});\n   toast(sp.miss+' A bark, faint, from the '+dir+'.');\n  }\n }finally{busy=false;}\n}\nasync function shout(){\n const t=$('shoutIn').value.trim().slice(0,40);if(!t||busy)return;busy=true;\n try{\n  const praise=/good|clever|brave|love|well done|yes|hero/i.test(t);\n  $('shoutIn').value='';\n  bubbleText=t.toUpperCase();bubbleUntil=Date.now()+3000;faceOverride=praise?'happy':'scold';faceUntil=Date.now()+3000;\n  await mut(s=>{if(praise){s.mood=clamp(s.mood+3,0,100);s.obedience=clamp(s.obedience+1,0,100);}else{s.obedience=clamp(s.obedience+3,0,100);s.mood=clamp(s.mood-2,0,100);}s.xp+=1;feed(s,t,'shout');});\n  setTimeout(()=>render(true),3100);\n }finally{busy=false;}\n}\nasync function praise(){\n if(busy)return;busy=true;\n try{bubbleText='good boy';bubbleUntil=Date.now()+2500;faceOverride='happy';faceUntil=Date.now()+2500;\n  await mut(s=>{s.mood=clamp(s.mood+4,0,100);s.care=(s.care||0)+1;s.xp+=2;feed(s,'told him he is a good boy.');});\n  setTimeout(()=>render(true),2600);\n }finally{busy=false;}\n}\nasync function advance(){\n if(busy)return;busy=true;\n try{\n  const ch=S.story.ch,b=S.story.b,chap=STORY[ch];\n  if(!chap){busy=false;return;}\n  if(b>=chap.beats.length){\n   if(ch+1>=STORY.length){await mut(s=>{s.story.done=true;});}\n   else if(lvl(S)>=STORY[ch+1].lvl)await mut(s=>{if(s.story.ch===ch){s.story.ch=ch+1;s.story.b=0;feed(s,'opened chapter '+(ch+2)+': '+STORY[ch+1].title+'.');}});\n   busy=false;return;\n  }\n  const beat=chap.beats[b];\n  if(beat.fight){startBattle(beat.fight,ch,b);busy=false;return;}\n  if(beat.gate){if((S.herding.sheep||0)<beat.gate.min){toast(beat.gate.fail);busy=false;return;}}\n  if(beat.req){const ok=(S[beat.req.stat]||0)>=beat.req.min;const fx=ok?beat.req.okFx:beat.req.failFx;toast(ok?beat.req.ok:beat.req.fail);await mut(s=>{Object.keys(fx||{}).forEach(k=>{s[k]=(s[k]||0)+fx[k];});if(s.story.ch===ch&&s.story.b===b)s.story.b++;});busy=false;return;}\n  if(beat.end){await mut(s=>{s.story.done=true;feed(s,'saved Hawthorne House.');});busy=false;return;}\n  await mut(s=>{if(s.story.ch===ch&&s.story.b===b)s.story.b++;});\n }finally{busy=false;}\n}\nasync function loopStory(){await mut(s=>{s.story={ch:0,b:0,loop:(s.story.loop||0)+1,done:false};feed(s,'began the plot again. Another cousin has written.');});}\nasync function newPup(){\n if(busy)return;busy=true;\n try{\n  const s=await load();if(s.status!=='dead'){busy=false;return;}\n  let l=await store.get(LKEY);if(!Array.isArray(l))l=[];\n  l.unshift({gen:s.gen,name:s.name,years:s.finalYears||years(s),finds:s.finds,wins:s.wins,sheep:s.herding.sheep,bornAt:s.bornAt,diedAt:s.diedAt||Date.now()});\n  l=l.slice(0,30);await store.set(LKEY,l);L=l;\n  const n=newDog(s.gen+1,s.story,Math.floor((s.xp||0)*.25));n.feed.unshift({by:'the household',text:'buried '+s.name+' under the yew hedge, aged '+(s.finalYears||years(s))+'.',at:Date.now(),k:'event'});\n  await store.set(KEY,n);S=n;mode='home';render(true);\n }finally{busy=false;}\n}\n\n/* ---------- ui ---------- */\nlet toastT=0;function toast(t){const el=$('toast');el.textContent=t;el.classList.add('on');clearTimeout(toastT);toastT=setTimeout(()=>el.classList.remove('on'),3400);}\nfunction ago(t){if(!t)return'';const s=Math.max(1,Math.floor((Date.now()-t)/1000));if(s<60)return'just now';const m=Math.floor(s/60);if(m<60)return m+'m ago';const h=Math.floor(m/60);if(h<24)return h+'h ago';return Math.floor(h/24)+'d ago';}\nfunction btn(label,cls,fn,dis){const b=document.createElement('button');b.textContent=label;if(cls)b.className=cls;b.onclick=fn;if(dis)b.disabled=true;return b;}\nfunction bar(label,v,max,cls){return '<div class=\"bar '+(cls||'')+'\">'+esc(label)+' '+Math.round(v)+' / '+Math.round(max)+'<i><b style=\"width:'+clamp(v/max*100,0,100)+'%\"></b></i></div>';}\nfunction hud(){\n const y=years(S),st=stage(S);\n let h='<span class=\"tag\">'+esc(S.name)+'</span>'+(mode==='herd'?'':'<span class=\"tag\">'+(S.status==='dead'?'at rest':(st==='pup'?'a puppy':st==='old'?'old boy, '+y:'age '+y))+'</span><span class=\"tag\">level '+lvl(S)+'</span>');\n if(isLost(S)&&mode!=='found')h+='<span class=\"tag ox\">missing</span>';\n if(mode==='herd')h+='<span class=\"tag ox\" id=\"hht\">40s</span><span class=\"tag\" id=\"hhc\">0 / 6</span>';\n $('hud').innerHTML=h;\n}\nfunction renderPanel(){\n const p=$('panel');p.innerHTML='';p.style.display='';\n const add=html=>{const d=document.createElement('div');d.innerHTML=html;while(d.firstChild)p.appendChild(d.firstChild);};\n const row=()=>{const r=document.createElement('div');r.className='row';p.appendChild(r);return r;};\n if(S.status==='dead'){\n  const r=row();r.appendChild(btn('A new pup arrives at the gatehouse','pri',newPup));\n  if(L&&L.length){add('<div class=\"lineage\"><h2 style=\"margin-top:14px\"><small>under the yew hedge</small>the dogs of Hawthorne House</h2>'+L.map(d=>'<div><b>'+esc(d.name)+'</b>, '+d.years+' years &middot; found '+d.finds+' times, '+d.wins+' wins, '+d.sheep+' sheep</div>').join('')+'</div>');}\n  return;\n }\n if(mode==='herd'&&H){const r=row();r.appendChild(btn('Call him off','',()=>{if(H&&!H.done)endHerd();}));return;}\n if(mode==='battle'&&B){\n  const e=B.e;\n  add('<div class=\"bars\"><div class=\"bar\">'+esc(S.name)+' <span id=\"bdt\"></span><i><b id=\"bdh\"></b></i></div><div class=\"bar ox\">'+esc(e.n.split(',')[0])+' <span id=\"bet\"></span><i><b id=\"beh\"></b></i></div></div><div class=\"bar\" style=\"margin-bottom:4px\">sad eyes<i><b id=\"beye\" style=\"background:#e07aa0\"></b></i></div>');\n  const r=row();\n  if(!B.over){\n   const bb=btn('Bite','pri cool',bite);bb.id='bbite';bb.appendChild(document.createElement('i'));r.appendChild(bb);\n   const bz=btn('Zoomies','cool',zoom);bz.id='bzoom';bz.appendChild(document.createElement('i'));r.appendChild(bz);\n   const be=btn('Sad eyes','',sadEyes,true);be.id='beyes';r.appendChild(be);\n   r.appendChild(btn('Biscuit ('+(S.biscuits||0)+')','gold',biscuit,!(S.biscuits>0)));\n  }else r.appendChild(btn(B.over==='win'?'Carry on':'Try again later','pri',()=>{mode='home';B=null;render(true);}));\n  return;\n }\n if(isLost(S)||mode==='found'){p.style.display='none';return;}\n const ss=storyState();\n const r=row();r.appendChild(btn(ss.btn,'pri',ss.act,ss.dis));if(ss.alt)r.appendChild(btn(ss.alt.btn,'gold',ss.alt.act));\n add('<div class=\"shout\"><input id=\"shoutIn\" maxlength=\"40\" placeholder=\"shout something at him\" aria-label=\"shout at the dog\"></div>');\n const r2=row();r2.appendChild(btn('Shout','',shout));r2.appendChild(btn('Good boy','gold',praise));r2.appendChild(btn('Herd sheep','',startHerd));r2.appendChild(btn('The study','',()=>{study=!study;render(true);}));\n $('shoutIn').addEventListener('keydown',e=>{if(e.key==='Enter')shout();});\n if(study){\n  const y=years(S),le=lifeEff(S);\n  add('<div class=\"stats\" style=\"margin-top:14px\"><div><span>age</span> '+y+' ('+stage(S)+')</div><div><span>expected years</span> about '+le+'</div><div><span>level</span> '+lvl(S)+' ('+S.xp+' xp)</div><div><span>mood</span> '+S.mood+'</div><div><span>obedience</span> '+S.obedience+'</div><div><span>care</span> '+(S.care||0)+'</div><div><span>sheep herded</span> '+(S.herding.sheep||0)+'</div><div><span>biscuits</span> '+(S.biscuits||0)+'</div><div><span>found</span> '+S.finds+' times</div><div><span>record</span> '+S.wins+' wins, '+S.losses+' losses</div><div><span>generation</span> '+S.gen+'</div><div><span>found today by</span> '+esc(S.found.by||'')+'</div></div><p style=\"font-size:13px;opacity:.7\">Shouting: obedience up, mood down. Praise: the reverse. Care (finding, praise, herding) adds years. Sad eyes charge faster on a happy dog.</p>');\n }\n}\nlet study=false;\nfunction renderFeed(){\n const f=S.feed||[];if(!f.length){$('feed').innerHTML='';return;}\n $('feed').innerHTML='<h3>Below stairs, lately</h3>'+f.slice(0,6).map(x=>'<div>'+(x.k==='shout'?'<b class=\"sh\">'+esc(x.by)+'</b> shouted \\u201c'+esc(x.text)+'\\u201d':'<b>'+esc(x.by)+'</b> '+esc(x.text))+' <span style=\"opacity:.5\">'+ago(x.at)+'</span></div>').join('');\n}\nfunction render(force){\n if(!S)return;\n if(faceOverride&&Date.now()>faceUntil)faceOverride=null;\n const sig=[mode,S.status,S.xp,S.mood,S.found.day,JSON.stringify(S.searched.spots),phase(),faceOverride,bubbleText&&Date.now()<bubbleUntil,B&&B.over,study,S.story.ch,S.story.b,S.story.done,S.story.loop,S.herding&&S.herding.sheep,S.finds,S.wins].join('|');\n if(sig!==lastSig||force){\n  lastSig=sig;\n  if((mode!=='herd'&&mode!=='battle')||!$('svg')){$('art').innerHTML=sceneSVG();\n   if(isLost(S)&&mode!=='found')$('art').querySelectorAll('[data-spot]').forEach(el=>{el.addEventListener('click',()=>searchSpot(el.getAttribute('data-spot')));});\n   const nx=$('art').querySelector('[data-act=next]');if(nx&&S.status!=='dead'){const ss=storyState();nx.addEventListener('click',()=>{if(!ss.dis)ss.act();});}}\n  hud();renderPanel();renderFeed();\n }\n}\nasync function refresh(){\n if(refreshing)return;refreshing=true;\n try{\n  const s=await load();const died=checkDeath(s);if(died)await store.set(KEY,s);\n  if(!L){const l=await store.get(LKEY);L=Array.isArray(l)?l:[];}\n  const prevGen=S&&S.gen;S=s;\n  if(prevGen&&prevGen!==s.gen){mode='home';B=null;}\n  if(s.status==='dead'&&mode!=='home'){mode='home';if(B&&B.raf)cancelAnimationFrame(B.raf);B=null;$('stage').classList.remove('fight');if(H){cancelAnimationFrame(H.raf);H=null;$('stage').classList.remove('herd');}}\n  render();\n }finally{refreshing=false;}\n}\nif(window.charm&&window.charm.onChange){try{window.charm.onChange(()=>{if(!busy&&mode!=='herd'&&mode!=='battle')refresh();});}catch(e){}}\nlet ticks=0;setInterval(()=>{if(!S||refreshing)return;ticks++;if(ticks%20===0&&mode!=='herd'&&mode!=='battle')refresh();else if(mode!=='herd')render();},1000);\nrefresh();\n</script></body></html>\n"
}